Spring Boot: REST API Quick Start Guide
Last updated: 2026-08-26
REST APIs are the universal language of microservices—they use HTTP verbs to manipulate resources and JSON to transfer data, making them simple, standardized, and efficient.
1. What You'll Learn
@RestController/@RequestMapping/@GetMapping/@PostMappingAnnotation System- Path variable
@PathVariable, request parameter@RequestParam, request body@RequestBody - HTTP Status Codes and
ResponseEntityCustom Responses - Testing REST APIs with Postman / curl
- Alice implements the four operations for orders: creation, query, update, and deletion
2. A True Story from an API Developer
(1) Pain Point: Inconsistent API Specifications
Alice joined an existing project team and discovered that the REST API design was chaotic: some methods used GET to modify data, URL naming was inconsistent (/getOrder, /order/list, /deleteOrderById), and the response formats were not standardized—some returned JSON, some returned XML, and error responses were plain text. Bob, a front-end developer, had to consult the back-end team every time he integrated a new API, wasting a significant amount of time on communication.
(2) RESTful Approach
REST uses a set of uniform conventions to define API design:
@RestController
@RequestMapping("/api/orders")
public class OrderController {
@GetMapping("/{id}") // GET /api/orders/123
@PostMapping // POST /api/orders
@PutMapping("/{id}") // PUT /api/orders/123
@DeleteMapping("/{id}") // DELETE /api/orders/123
}
(3) Revenue
After Alice refactored the OrderFlow API to follow a RESTful style—with consistent URL naming conventions and clearly defined HTTP verb semantics—Bob, a front-end developer, remarked, "You can tell how to use it just by looking at the URL." As a result, the time required to integrate with the API was reduced from an average of 2 hours to 15 minutes.
3. Core Concepts of REST
(1) The Six Principles of REST
REST (Representational State Transfer) defines the core constraints of this architectural style:
graph TD
A[REST Constraints] --> B[Client-Server<br/>Separation of Concerns]
A --> C[Stateless<br/>Each Request Contains<br/>All Needed Info]
A --> D[Cacheable<br/>Responses Define<br/>Cacheability]
A --> E[Uniform Interface<br/>Consistent API Design]
A --> F[Layered System<br/>Client Cannot Tell<br/>If Connected Directly]
A --> G[Code on Demand<br/>Optional: Server Can<br/>Send Executable Code]
(2) Mapping HTTP Verbs to CRUD Operations
| HTTP Verb | Operation | Idempotency | Security | Typical URL |
|---|---|---|---|---|
| GET | Query | Yes | Yes | /api/orders |
| POST | Created | No | No | /api/orders |
| PUT | Full Update | Yes | No | /api/orders/123 |
| PATCH | Partial Update | No | No | /api/orders/123 |
| DELETE | Delete | Yes | No | /api/orders/123 |
4. @RestController and Request Mapping
(1) How @RestController Works
@RestController = @Controller + @ResponseBody indicates that the return values of all methods in this class are written directly into the HTTP response body (JSON by default).
(1) ▶ Example: Basic OrderController
@RestController
@RequestMapping("/api/orders")
public class OrderController {
private final List<Map<String, Object>> orders = new ArrayList<>();
@GetMapping
public List<Map<String, Object>> listOrders() {
return orders;
}
@PostMapping
public Map<String, Object> createOrder(
@RequestBody Map<String, Object> order) {
order.put("id", (long) (orders.size() + 1));
order.put("status", "PENDING");
orders.add(order);
return order;
}
}
Output:
// Execution Successful
(2) Request Parameter Binding
(2) ▶ Example: @PathVariable and @RequestParam
@RestController
@RequestMapping("/api/orders")
public class OrderController {
@GetMapping("/{id}")
public Map<String, Object> getOrder(@PathVariable Long id) {
return Map.of("id", id, "status", "PENDING");
}
@GetMapping
public List<Map<String, Object>> searchOrders(
@RequestParam(defaultValue = "0") int page,
@RequestParam(defaultValue = "20") int size,
@RequestParam(required = false) String status) {
return List.of(Map.of(
"page", page, "size", size, "status", status
));
}
}
Output:
// Execution Successful
| Comment | Purpose | Example | URL |
|---|---|---|---|
@PathVariable |
Extract variables from the path | @PathVariable Long id |
/api/orders/123 |
@RequestParam |
Extract Query Parameters | @RequestParam String status |
/api/orders?status=PENDING |
@RequestBody |
Extract JSON from request body | @RequestBody OrderRequest req |
POST body |
@RequestHeader |
Extract Request Headers | @RequestHeader String auth |
Header: Authorization |
(3) ▶ Example: Using a DTO to Receive the Request Body
public record CreateOrderRequest(
Long productId,
Integer quantity,
String shippingAddress
) {}
@RestController
@RequestMapping("/api/orders")
public class OrderController {
@PostMapping
public ResponseEntity<Map<String, Object>> createOrder(
@RequestBody CreateOrderRequest request) {
Map<String, Object> order = Map.of(
"productId", request.productId(),
"quantity", request.quantity(),
"shippingAddress", request.shippingAddress(),
"status", "PENDING"
);
return ResponseEntity
.status(HttpStatus.CREATED)
.body(order);
}
}
Output:
// Execution Successful
5. ResponseEntity and HTTP Status Codes
(1) Using ResponseEntity
ResponseEntity Gives you full control over HTTP responses: status codes, response headers, and response bodies.
| Method | Applicable Scenarios | Flexibility |
|---|---|---|
| Returns an object directly | Simple success response | Low (fixed at 200) |
ResponseEntity.ok(body) |
Must be set to 200 | Medium |
ResponseEntity.status(CREATED).body(body) |
Resource creation returned 201 | High |
ResponseEntity.notFound().build() |
Resource not found—returns 404 | High |
(1) ▶ Example: Complete CRUD Operations
@RestController
@RequestMapping("/api/orders")
public class OrderController {
private final Map<Long, Map<String, Object>> orderStore = new ConcurrentHashMap<>();
private final AtomicLong idGenerator = new AtomicLong(1);
@PostMapping
public ResponseEntity<Map<String, Object>> create(
@RequestBody CreateOrderRequest request) {
Long id = idGenerator.getAndIncrement();
Map<String, Object> order = new HashMap<>();
order.put("id", id);
order.put("productId", request.productId());
order.put("quantity", request.quantity());
order.put("status", "PENDING");
orderStore.put(id, order);
return ResponseEntity.status(HttpStatus.CREATED).body(order);
}
@GetMapping("/{id}")
public ResponseEntity<Map<String, Object>> getOne(@PathVariable Long id) {
Map<String, Object> order = orderStore.get(id);
if (order == null) {
return ResponseEntity.notFound().build();
}
return ResponseEntity.ok(order);
}
@PutMapping("/{id}")
public ResponseEntity<Map<String, Object>> update(
@PathVariable Long id,
@RequestBody CreateOrderRequest request) {
if (!orderStore.containsKey(id)) {
return ResponseEntity.notFound().build();
}
Map<String, Object> order = new HashMap<>();
order.put("id", id);
order.put("productId", request.productId());
order.put("quantity", request.quantity());
order.put("status", "CONFIRMED");
orderStore.put(id, order);
return ResponseEntity.ok(order);
}
@DeleteMapping("/{id}")
public ResponseEntity<Void> delete(@PathVariable Long id) {
if (orderStore.remove(id) == null) {
return ResponseEntity.notFound().build();
}
return ResponseEntity.noContent().build();
}
}
Output:
// Execution Successful
6. API Testing Methods
(1) curl test command
(1) ▶ Example: Testing CRUD APIs with curl
# Create order
curl -X POST http://localhost:8080/api/orders \
-H "Content-Type: application/json" \
-d '{"productId":1, "quantity":3, "shippingAddress":"123 Main St"}'
# Get order
curl http://localhost:8080/api/orders/1
# List orders with pagination
curl "http://localhost:8080/api/orders?page=0&size=10"
# Update order
curl -X PUT http://localhost:8080/api/orders/1 \
-H "Content-Type: application/json" \
-d '{"productId":2, "quantity":5, "shippingAddress":"456 Oak Ave"}'
# Delete order
curl -X DELETE http://localhost:8080/api/orders/1
Output:
{"status":"ok","data":{}}
| HTTP Status Code | Meaning | When Returned |
|---|---|---|
| 200 OK | Success | GET / PUT Success |
| 201 Created | Created | POST Resource created successfully |
| 204 No Content | No content | DELETE successful |
| 400 Bad Request | Request Error | Parameter Validation Failed |
| 404 Not Found | Not Found | Resource Does Not Exist |
7. RESTful API Design Specifications
(1) URL Naming Conventions
| Rule | Incorrect Form | Correct Form |
|---|---|---|
| Use of Plural Nouns | /getOrder |
/api/orders |
| Nested Resource Relationships | /orderItems?orderId=1 |
/api/orders/1/items |
| Use Paths Instead of Queries | /api/orders?id=1 |
/api/orders/1 |
| Version Control | None | /api/v1/orders |
| Query Parameters for Filtering | /api/pendingOrders |
/api/orders?status=PENDING |
8. Comprehensive Example: OrderFlow Order CRUD API
package com.orderflow.controller;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicLong;
public record CreateOrderRequest(
Long productId, Integer quantity, String shippingAddress
) {}
@RestController
@RequestMapping("/api/v1/orders")
public class OrderController {
private final Map<Long, Map<String, Object>> store = new ConcurrentHashMap<>();
private final AtomicLong seq = new AtomicLong(1);
@PostMapping
public ResponseEntity<Map<String, Object>> create(
@RequestBody CreateOrderRequest req) {
Long id = seq.getAndIncrement();
Map<String, Object> order = new LinkedHashMap<>();
order.put("id", id);
order.put("productId", req.productId());
order.put("quantity", req.quantity());
order.put("shippingAddress", req.shippingAddress());
order.put("status", "PENDING");
order.put("createdAt", java.time.Instant.now());
store.put(id, order);
return ResponseEntity.status(HttpStatus.CREATED).body(order);
}
@GetMapping
public List<Map<String, Object>> list(
@RequestParam(defaultValue = "0") int page,
@RequestParam(defaultValue = "20") int size) {
return store.values().stream()
.skip((long) page * size).limit(size).toList();
}
@GetMapping("/{id}")
public ResponseEntity<Map<String, Object>> get(@PathVariable Long id) {
Map<String, Object> order = store.get(id);
return order != null ? ResponseEntity.ok(order)
: ResponseEntity.notFound().build();
}
@DeleteMapping("/{id}")
public ResponseEntity<Void> delete(@PathVariable Long id) {
return store.remove(id) != null
? ResponseEntity.noContent().build()
: ResponseEntity.notFound().build();
}
}
❓ FAQ
ResponseEntity or just return the object directly?ResponseEntity when you need to set custom status codes (such as 201 or 404) or response headers.application.yml or use @JsonFormat(pattern = "yyyy-MM-dd") on the field./api/v1/orders), which is simple and intuitive. You can also use header-based version control (Accept: application/vnd.orderflow.v1+json), which is more RESTful but more complex to implement.📖 Summary
- REST maps CRUD operations to HTTP verbs: GET for retrieval, POST for creation, PUT for update, and DELETE for deletion
@RestController=@Controller+@ResponseBody, returns JSON@PathVariableretrieves the path variable,@RequestParamretrieves the query parameters, and@RequestBodyretrieves the request bodyResponseEntityFull control over response status codes, headers, and body- RESTful URLs use plural nouns; nesting indicates relationships between resources; query parameters are used for filtering
📝 Exercises
-
Basic Exercise (Difficulty ⭐): Implement CRUD APIs for the Product resource in OrderFlow, including
GET /api/v1/products,POST /api/v1/products,GET /api/v1/products/{id}, andDELETE /api/v1/products/{id}. -
Advanced Problem (Difficulty: ⭐⭐): Add the
PATCH /api/v1/orders/{id}/statusendpoint to the Order API that updates only the order status field. Consider the difference between PATCH and PUT. -
Challenge (Difficulty: ⭐⭐⭐): Implement the
GET /api/v1/orders/{id}/itemsnested resource interface to return all order items under a specified order, with support for pagination parameters.