The Problem with MVC Spaghetti
When a Spring Boot project grows, it's tempting to put business logic in controllers or let entities bleed into API responses. This leads to tightly coupled, untestable code. Clean Architecture solves this.
Layer Separation
com.portfolio/
├── controller/ → HTTP layer (receives requests, returns responses)
├── service/ → Business logic (the core of your app)
├── repository/ → Data access (JPA repositories)
├── entity/ → JPA entities (maps to DB tables)
├── dto/
│ ├── request/ → What the API receives (validation here)
│ └── response/ → What the API returns (never expose entities)
└── exception/ → Global error handling
DTOs as API Contracts
Never return JPA entities directly from your controllers. Use DTOs to control exactly what data is exposed.
Global Exception Handling
@RestControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(ResourceNotFoundException.class)
public ResponseEntity<ApiResponse<Void>> handleNotFound(
ResourceNotFoundException ex) {
return ResponseEntity.status(404)
.body(ApiResponse.error(ex.getMessage()));
}
}
Why This Matters
With this structure, you can swap your database without touching a single controller. You can unit test service classes in isolation. And onboarding new developers becomes much easier.