Building a REST API with Spring Boot — Complete Guide
REST APIs are the backbone of modern software — every mobile app, web frontend, and microservice communicates through them. Spring Boot is the most popular Java framework for building them, and for good reason: it handles the HTTP plumbing so you focus on business logic.
Most tutorials show you a Hello World endpoint and call it a day. This article builds a real CRUD API from scratch — Create, Read, Update, Delete — with proper HTTP status codes, request validation, and meaningful responses.
By the end, you'll have a working Product API that follows REST conventions, handles errors gracefully, and is ready to connect to a real database.
The Four HTTP Methods and When to Use Each
REST uses HTTP methods as verbs. GET retrieves data, POST creates new resources, PUT updates existing ones, DELETE removes them. Spring Boot maps these with four annotations. The URL should represent a noun (the resource), never a verb — /products not /getProducts.
import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; import java.util.*; @RestController @RequestMapping("/api/products") // Base URL for all endpoints in this controller public class ProductController { // In-memory store for now — replace with a real DB later private final Map<Long, Product> products = new HashMap<>(); private long nextId = 1; // GET /api/products — returns all products @GetMapping public List<Product> getAllProducts() { return new ArrayList<>(products.values()); } // GET /api/products/5 — returns one product by ID @GetMapping("/{id}") public ResponseEntity<Product> getProduct(@PathVariable Long id) { Product product = products.get(id); if (product == null) { return ResponseEntity.notFound().build(); // 404 } return ResponseEntity.ok(product); // 200 } // POST /api/products — creates a new product @PostMapping public ResponseEntity<Product> createProduct(@RequestBody Product product) { product.setId(nextId++); products.put(product.getId(), product); return ResponseEntity.status(HttpStatus.CREATED).body(product); // 201 } // DELETE /api/products/5 — removes a product @DeleteMapping("/{id}") public ResponseEntity<Void> deleteProduct(@PathVariable Long id) { if (!products.containsKey(id)) { return ResponseEntity.notFound().build(); // 404 } products.remove(id); return ResponseEntity.noContent().build(); // 204 } }
GET /api/products/1 → 200 OK {"id":1, "name":"Laptop", "price":999.0}
GET /api/products/99 → 404 Not Found
DELETE /api/products/1 → 204 No Content
Request Validation with @Valid
Never trust client input. Spring Boot integrates with the Bean Validation API — add @Valid to your @RequestBody and annotate your model fields with constraints. Spring Boot will automatically return a 400 Bad Request with details if validation fails.
import jakarta.validation.constraints.*; public class Product { private Long id; @NotBlank(message = "Product name cannot be empty") @Size(min = 2, max = 100, message = "Name must be 2-100 characters") private String name; @NotNull(message = "Price is required") @Positive(message = "Price must be greater than zero") private Double price; // getters and setters public Long getId() { return id; } public void setId(Long id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } public Double getPrice() { return price; } public void setPrice(Double price) { this.price = price; } }
→ 400 Bad Request
{"name":"Product name cannot be empty", "price":"Price must be greater than zero"}
| HTTP Method | Annotation | Status Code | Use Case |
|---|---|---|---|
| GET | @GetMapping | 200 OK | Retrieve resource(s) |
| POST | @PostMapping | 201 Created | Create new resource |
| PUT | @PutMapping | 200 OK | Replace entire resource |
| PATCH | @PatchMapping | 200 OK | Partially update resource |
| DELETE | @DeleteMapping | 204 No Content | Remove resource |
🎯 Key Takeaways
- @RestController + @RequestMapping is the foundation of every Spring Boot API
- Always return ResponseEntity to control HTTP status codes explicitly
- Use @Valid with Bean Validation annotations to reject bad input early
- REST URLs should be nouns representing resources, never verbs
⚠ Common Mistakes to Avoid
- ✕Mistake 1: Using @RequestParam instead of @PathVariable for resource IDs — /products?id=5 is not RESTful. Use /products/{id} with @PathVariable.
- ✕Mistake 2: Returning the entity directly from the database layer — this exposes internal fields, can cause infinite loops with bidirectional relationships, and leaks implementation details. Use DTOs.
- ✕Mistake 3: Not handling MethodArgumentNotValidException — without a @ControllerAdvice, validation errors return a messy 500. Add an exception handler to return a clean 400.
Interview Questions on This Topic
- QWhat is the difference between @Controller and @RestController?
- QWhen would you use PUT vs PATCH?
- QHow does Spring Boot handle request validation, and what happens when validation fails?
Developer and founder of TheCodeForge. I built this site because I was tired of tutorials that explain what to type without explaining why it works. Every article here is written to make concepts actually click.