Home Java Building a REST API with Spring Boot — Complete Guide

Building a REST API with Spring Boot — Complete Guide

⚡ Quick Answer
A REST API is like a waiter in a restaurant. You (the client) tell the waiter what you want, the waiter goes to the kitchen (server), and brings back your order. Spring Boot is the kitchen — it handles receiving orders, preparing responses, and sending them back. @RestController is the waiter. The menu is your list of endpoints.

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.

ProductController.java · JAVA
1234567891011121314151617181920212223242526272829303132333435363738394041424344454647
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
    }
}
▶ Output
POST /api/products → 201 Created {"id":1, "name":"Laptop", "price":999.0}
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
⚠️
HTTP Status Codes Matter:Always return the right status code. 201 for created, 204 for deleted, 404 for not found. Clients rely on these to handle responses correctly — returning 200 for everything is a REST anti-pattern.

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.

Product.java · JAVA
123456789101112131415161718192021
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; }
}
▶ Output
POST /api/products with body {"name":"", "price":-5}
→ 400 Bad Request
{"name":"Product name cannot be empty", "price":"Price must be greater than zero"}
⚠️
Watch Out:Bean Validation requires spring-boot-starter-validation in your pom.xml — it's NOT included in spring-boot-starter-web. Forgetting this means @Valid silently does nothing.
HTTP MethodAnnotationStatus CodeUse Case
GET@GetMapping200 OKRetrieve resource(s)
POST@PostMapping201 CreatedCreate new resource
PUT@PutMapping200 OKReplace entire resource
PATCH@PatchMapping200 OKPartially update resource
DELETE@DeleteMapping204 No ContentRemove 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?
🔥
Naren Founder & Author

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.

← PreviousSpring Boot Auto-Configuration ExplainedNext →Spring Boot Annotations Cheat Sheet
Forged with 🔥 at TheCodeForge.io — Where Developers Are Forged