Definition
- REST – stands for Representational State Transfer, an architectural style for networked applications defined by Roy Fielding in his 2000 doctoral dissertation.
- Not a protocol – REST is a set of constraints applied over HTTP, not a protocol or a standard like SOAP.
- Six constraints – client-server separation, statelessness, cacheability, a layered system, a uniform interface based on resources, and optional code-on-demand.
- Uniform interface – resources are identified by URIs, manipulated through representations like JSON, messages are self-descriptive via headers, and HATEOAS links guide the client.
- HATEOAS – Hypermedia As The Engine Of Application State, where responses carry links to the next possible actions so clients discover operations instead of hardcoding URLs.
- RESTful vs RPC – RESTful APIs use resource nouns like
/accounts/SAV2001with HTTP verbs, while RPC-style APIs use action verbs like/getAccountand treat HTTP as a tunnel.
Think of National Bank of India's online system as a very disciplined post office. Every account, every transaction, every customer is a thing with its own address, like /accounts/SAV2001 for Rajesh's savings account. To interact with a thing you use standard actions that everyone already understands: GET to read, POST to create, PUT to replace, DELETE to remove. An RPC-style system instead would have custom addresses like /getRajeshBalance and /doTransferNow that nobody can guess. REST says: name the nouns, use the standard verbs.
Understanding the Concepts
What is REST?
REST stands for Representational State Transfer. It is an architectural style for building networked applications, defined by Roy Fielding in his 2000 doctoral dissertation. REST is not a protocol like SOAP and not a standard with a formal specification. It is a set of constraints that, when applied over HTTP, produce scalable, cacheable, loosely coupled systems.
The core idea is that every meaningful business thing is a resource with a stable URI, and the client interacts with that resource using standard HTTP methods.
// REST treats every business entity as a resource with a URI.
// Rajesh's savings account at National Bank of India:
// Resource: /api/v1/accounts/SAV2001
// Read: GET /api/v1/accounts/SAV2001
// Replace: PUT /api/v1/accounts/SAV2001
// Delete: DELETE /api/v1/accounts/SAV2001
// A collection of all transfers:
// List: GET /api/v1/transfers
// Create: POST /api/v1/transfers
Why does REST exist?
Before REST, most distributed APIs used RPC-style designs where the server exposed custom operations and the client had to know every operation name in advance. This created tight coupling: the client was tied to the server's method signatures, caching was impossible because every call was a unique operation, and there was no standard way for intermediaries like load balancers or CDNs to understand the traffic.
Roy Fielding observed that the web itself scaled to billions of users because it followed a set of constraints: resources identified by URLs, standard verbs like GET, stateless interactions, and cacheability. REST formalises those constraints so any API can gain the same benefits.
// RPC-style (before REST) – tight coupling, no caching, custom verbs.
// POST /api/getAccountBalance body: {"accountId": "SAV2001"}
// POST /api/createTransfer body: {"from": "SAV2001", "to": "SAV2002", "amount": 50000}
// POST /api/deleteAccount body: {"accountId": "SAV2001"}
// REST-style – standard verbs, cacheable, discoverable.
// GET /api/v1/accounts/SAV2001
// POST /api/v1/transfers body: {"from": "SAV2001", "to": "SAV2002", "amount": 50000}
// DELETE /api/v1/accounts/SAV2001
What are the six constraints of REST?
REST defines six constraints. Each constraint enables a specific scalability or decoupling benefit.
- Client-server separation – the client and the server are independent. The mobile app does not know how the server stores data, and the server does not care what device the client runs on.
- Statelessness – every request carries everything the server needs to process it, including the authentication token. The server keeps no session memory between requests.
- Cacheability – responses declare whether they can be cached using headers like
Cache-Control. A balance lookup might saymax-age=30, allowing reuse for thirty seconds. - Layered system – the client cannot tell whether it is talking to the real server or an intermediary like a load balancer, gateway, or CDN.
- Uniform interface – the heart of REST. It includes resource identification in URIs, manipulation through representations, self-descriptive messages via
Content-Typeheaders, and HATEOAS. - Code-on-demand (optional) – the server may send executable code like JavaScript. Most APIs skip this constraint.
package com.bank.controller;
import org.springframework.http.CacheControl;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import java.util.concurrent.TimeUnit;
@RestController
@RequestMapping("/api/v1/accounts")
public class AccountController {
// Statelessness: JWT token arrives in every request, no server session.
// Cacheability: Cache-Control header allows 30-second reuse.
@GetMapping("/{id}")
public ResponseEntity<AccountResponse> getAccount(@PathVariable String id) {
AccountResponse account = accountService.findById(id);
return ResponseEntity.ok()
.cacheControl(CacheControl.maxAge(30, TimeUnit.SECONDS))
.body(account);
}
}
How does the uniform interface work?
The uniform interface has four parts. First, resource identification: every resource has a unique URI like /accounts/SAV2001. Second, manipulation through representations: you send and receive JSON, but the server stores data however it wants. Third, self-descriptive messages: the Content-Type: application/json header tells the client how to parse the body. Fourth, HATEOAS: the response includes links to the next possible actions.
// Self-descriptive message: Content-Type tells the client the body is JSON.
// HATEOAS: the response carries links to related actions.
// GET /api/v1/accounts/SAV2001
// Response:
// {
// "accountId": "SAV2001",
// "holder": "Rajesh",
// "balance": 500000.00,
// "_links": {
// "self": {"href": "/api/v1/accounts/SAV2001"},
// "transactions": {"href": "/api/v1/accounts/SAV2001/transactions"},
// "transfer": {"href": "/api/v1/accounts/SAV2001/transfers"}
// }
// }
How does REST differ from RPC-style HTTP?
An RPC-style API uses custom action verbs in the URL like POST /getUserById or POST /transferMoney, treats HTTP as a dumb transport tunnel, usually returns 200 even for errors, and the client must know every custom endpoint name. A RESTful API uses resource nouns with standard HTTP verbs, returns correct status codes, and the URL structure is predictable.
| Aspect | REST | RPC-style HTTP |
|---|---|---|
| URL design | Nouns: /accounts/SAV2001 | Verbs: /getAccount |
| HTTP methods | GET, POST, PUT, DELETE carry meaning | POST for everything |
| Status codes | 404 for missing, 201 for created | 200 for everything |
| Cacheability | GET responses are cacheable | Nothing is cacheable |
| Discoverability | URLs are predictable | Must read documentation |
// REST: the verb is the HTTP method, the noun is the URL.
@GetMapping("/accounts/{id}")
public AccountResponse get(@PathVariable String id) {
return accountService.findById(id);
}
// RPC-style (avoid): the verb is in the URL, everything is POST.
@PostMapping("/getAccount")
public AccountResponse getAccount(@RequestBody Map<String, String> body) {
return accountService.findById(body.get("id"));
}
What is the Richardson Maturity Model?
The Richardson Maturity Model, defined by Leonard Richardson, grades APIs on four levels of REST compliance.
- Level 0 – The swamp of POX – one URI, one HTTP method (POST), plain XML. Example:
POST /apiwith every action in the body. - Level 1 – Resources – individual URIs for each resource, but still only POST. Example:
POST /accounts/SAV2001. - Level 2 – HTTP verbs – correct use of GET, POST, PUT, DELETE and status codes. This is where most production APIs live.
- Level 3 – Hypermedia (HATEOAS) – responses carry links to next actions. Full REST as Fielding defined it.
// Level 0: one endpoint, one verb.
// POST /api body: {"action": "getAccount", "id": "SAV2001"}
// Level 1: individual URIs, still POST.
// POST /accounts/SAV2001 body: {"action": "read"}
// Level 2: correct verbs and status codes.
// GET /accounts/SAV2001 → 200 OK
// POST /transfers → 201 Created
// Level 3: HATEOAS links in every response.
// GET /accounts/SAV2001 → 200 OK + _links: {transactions, transfer, freeze}
What are common mistakes when designing REST APIs?
The most common mistake is using verbs in URLs instead of nouns. Another is returning 200 for every response, even errors, which blinds monitoring and caches. A third is storing session state on the server, which breaks statelessness and horizontal scaling.
// Wrong: verb in URL, always 200, session state.
@PostMapping("/doTransfer")
public Map<String, Object> doTransfer(@RequestBody TransferRequest req,
HttpSession session) {
// Uses server-side session – breaks statelessness
String userId = (String) session.getAttribute("userId");
transferService.transfer(userId, req);
return Map.of("success", true); // Always 200, even on failure
}
// Correct: noun URL, proper status, stateless JWT.
@PostMapping("/transfers")
public ResponseEntity<TransferResponse> create(
@RequestHeader("Authorization") String token,
@Valid @RequestBody TransferRequest req) {
String userId = jwtService.extractUserId(token); // Stateless: token carries identity
TransferResponse resp = transferService.create(userId, req);
return ResponseEntity.created(URI.create("/api/v1/transfers/" + resp.id()))
.body(resp); // 201 Created with Location header
}
When should you use REST?
REST is the right choice when you need a public or partner-facing API that many different clients will consume, when cacheability matters, when you want standard tooling like Swagger and Postman to work out of the box, and when horizontal scaling is a priority. Most CRUD-oriented web services and mobile backends are a natural fit.
When should you NOT use REST?
REST is not ideal for real-time bidirectional communication where WebSockets or gRPC streaming are better, for high-throughput internal service-to-service calls where gRPC's binary protocol is faster, or for operations that do not map naturally to resources, like a complex calculation or a workflow orchestration, where an RPC style may be clearer.
// REST is ideal for CRUD on resources:
// GET /accounts/SAV2001, POST /transfers, DELETE /beneficiaries/BEN-77
// Not ideal for:
// Real-time stock price streaming → use WebSocket or gRPC streaming
// Internal high-throughput service calls → use gRPC with protobuf
// Complex multi-step workflows → consider a command/event model
What should a senior engineer know about REST?
A senior engineer knows that REST is not just JSON over HTTP. Many APIs calling themselves REST are actually RPC with pretty URLs. True REST leans on HTTP semantics: correct verbs, correct status codes, caching headers, content negotiation, and ideally hypermedia links. A senior also knows the trade-offs: REST's text-based JSON is heavier than gRPC's binary protobuf, its request-response model does not support streaming natively, and its statelessness means every request carries a token, adding overhead.
// Senior-level: a truly RESTful controller uses content negotiation,
// caching, proper status codes, and HATEOAS links.
@GetMapping(value = "/{id}", produces = {"application/json", "application/xml"})
public ResponseEntity<EntityModel<AccountResponse>> get(@PathVariable String id) {
AccountResponse dto = accountService.findById(id);
EntityModel<AccountResponse> model = EntityModel.of(dto);
model.add(linkTo(methodOn(AccountController.class).get(id)).withSelfRel());
return ResponseEntity.ok()
.cacheControl(CacheControl.maxAge(30, TimeUnit.SECONDS))
.eTag("\"" + dto.version() + "\"")
.body(model);
}
Visual Overview
┌─────────────────────────────────────────────────────────┐
│ REST Architecture │
├─────────────────────────────────────────────────────────┤
│ │
│ Client (Mobile App) │
│ │ │
│ │ GET /api/v1/accounts/SAV2001 │
│ │ Authorization: Bearer <jwt> │
│ ▼ │
│ ┌──────────┐ ┌──────────┐ ┌──────────────┐ │
│ │ CDN / │───▶│ API │───▶│ Application │ │
│ │ Cache │ │ Gateway │ │ Server │ │
│ └──────────┘ └──────────┘ └──────────────┘ │
│ (Layered) (Layered) (Stateless) │
│ │
│ Response: │
│ 200 OK │
│ Cache-Control: max-age=30 │
│ Content-Type: application/json │
│ ETag: "v7" │
│ { │
│ "accountId": "SAV2001", │
│ "balance": 500000.00, │
│ "_links": { │
│ "self": {"/api/v1/accounts/SAV2001"}, │
│ "transactions": {"/api/v1/.../transactions"} │
│ } │
│ } │
└─────────────────────────────────────────────────────────┘
┌──────────────────────────────────────────────────┐
│ Richardson Maturity Model │
├──────────────────────────────────────────────────┤
│ Level 3: HATEOAS links in responses │
│ ▲ │
│ Level 2: HTTP verbs + status codes ◀── Most │
│ ▲ APIs │
│ Level 1: Individual resource URIs │
│ ▲ │
│ Level 0: One URI, POST everything (RPC) │
└──────────────────────────────────────────────────┘
Key Takeaway
REST stands for Representational State Transfer. It is an architectural style defined by Roy Fielding, not a protocol, and it is built on six constraints: client-server separation, statelessness, cacheability, a layered system, a uniform interface, optional code-on-demand, and hypermedia as the engine of application state. When these constraints are applied over HTTP, you get a system that scales well, caches naturally, and stays loosely coupled.
The most important idea is the uniform interface. In REST you model your system as resources and give each a stable URL made of nouns. In the banking work at National Bank of India, reading Rajesh's account is a GET on /accounts/SAV2001, creating a transfer is a POST to the /transfers collection, and removing a beneficiary is a DELETE on /beneficiaries/BEN-77. The HTTP verb carries the action, so a developer who has never seen the API can still guess what each endpoint does.
Statelessness is what enables scaling. Because every request carries its own JWT token, the server keeps no session memory, so any of three service instances can serve any request. Cacheability comes from returning a Cache-Control header on the balance read, so the mobile app reuses a balance for thirty seconds instead of hitting the database. These two constraints together let the system handle millions of requests through horizontal scaling and caching layers.
The difference from an RPC-style HTTP API is sharp. RPC puts actions in the URL like /getBalance, treats HTTP as a tunnel, and often returns 200 even for errors. REST uses resource nouns, the correct verbs, and the correct status codes, so the whole ecosystem of proxies, caches, and monitoring tools understands it without custom logic. When someone says their API is REST just because it returns JSON, check whether they actually use resource URLs, proper verbs, status codes, and caching headers, because that is what REST really means.
Remember one sentence: REST organises APIs around resource nouns acted on by standard HTTP verbs, with stateless requests and cacheable responses, so the entire web infrastructure works for you automatically.
---