Spring Boot has become the default way to ship production Java services. It wires up an embedded server, sensible defaults, and dependency injection so you can focus on the domain instead of boilerplate.
A minimal controller
A REST controller maps HTTP requests to methods. Keep controllers thin — they should translate between HTTP and your service layer, nothing more.
@RestController
@RequestMapping("/api/products")
public class ProductController {
private final ProductService service;
public ProductController(ProductService service) {
this.service = service;
}
@GetMapping("/{id}")
public ProductResponse getById(@PathVariable Long id) {
return service.findById(id);
}
@PostMapping
@ResponseStatus(HttpStatus.CREATED)
public ProductResponse create(@Valid @RequestBody CreateProductRequest request) {
return service.create(request);
}
}Use DTOs, not entities
Exposing JPA entities directly leaks your database schema into your API and invites lazy-loading surprises. Define request and response records instead.
public record CreateProductRequest(
@NotBlank String name,
@Positive BigDecimal price
) {}
public record ProductResponse(
Long id,
String name,
BigDecimal price
) {}Centralize error handling
A @RestControllerAdvice keeps error formatting in one place so every endpoint
returns consistent problem responses.
@RestControllerAdvice
public class ApiExceptionHandler {
@ExceptionHandler(EntityNotFoundException.class)
@ResponseStatus(HttpStatus.NOT_FOUND)
public ProblemDetail handleNotFound(EntityNotFoundException ex) {
return ProblemDetail.forStatusAndDetail(HttpStatus.NOT_FOUND, ex.getMessage());
}
}Takeaways
- Keep controllers thin and push logic into services.
- Never expose entities directly — map to DTOs or records.
- Handle errors centrally so responses stay consistent.
With these three habits, your Spring Boot APIs stay predictable as the codebase grows.