Exception handling is a critical part of any application, and with Spring Boot, you can implement robust and clean exception handling mechanisms with minimal effort. This article walks you through the best practices for exception handling in Spring Boot and provides a complete working example.
Why Exception Handling Matters
- Ensures predictable application behavior
- Provides meaningful error messages to clients
- Prevents leaking internal stack traces or sensitive information
- Improves debugging and monitoring
Best Practices Overview
- Use Custom Exception Classes
- Avoid Catching Generic Exceptions
- Use
@ControllerAdvice
for Global Handling - Leverage
@ExceptionHandler
- Standardize Error Responses
- Log Exceptions Properly
- Avoid Revealing Internal Info
Project Setup
Spring Boot version: 3.x
Dependencies (in pom.xml
)
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
</dependencies>
Example Use Case: Book Management API
Step 1: Define a Book Entity
public class Book {
private Long id;
private String title;
private String author;
// Getters and setters
}
Step 2: Create Custom Exceptions
public class BookNotFoundException extends RuntimeException {
public BookNotFoundException(Long id) {
super("Book with ID " + id + " not found");
}
}
public class InvalidBookException extends RuntimeException {
public InvalidBookException(String message) {
super(message);
}
}
Step 3: Create a Standard Error Response
public class ErrorResponse {
private String message;
private String details;
private LocalDateTime timestamp;
public ErrorResponse(String message, String details) {
this.message = message;
this.details = details;
this.timestamp = LocalDateTime.now();
}
// Getters
}
Step 4: Global Exception Handler with @ControllerAdvice
@RestControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(BookNotFoundException.class)
public ResponseEntity<ErrorResponse> handleBookNotFound(BookNotFoundException ex, WebRequest request) {
ErrorResponse response = new ErrorResponse(ex.getMessage(), request.getDescription(false));
return new ResponseEntity<>(response, HttpStatus.NOT_FOUND);
}
@ExceptionHandler(InvalidBookException.class)
public ResponseEntity<ErrorResponse> handleInvalidBook(InvalidBookException ex, WebRequest request) {
ErrorResponse response = new ErrorResponse(ex.getMessage(), request.getDescription(false));
return new ResponseEntity<>(response, HttpStatus.BAD_REQUEST);
}
@ExceptionHandler(Exception.class)
public ResponseEntity<ErrorResponse> handleGlobalException(Exception ex, WebRequest request) {
ErrorResponse response = new ErrorResponse("Internal server error", request.getDescription(false));
// Log the exception (ex) here
return new ResponseEntity<>(response, HttpStatus.INTERNAL_SERVER_ERROR);
}
}
Step 5: Book Controller
@RestController
@RequestMapping("/api/books")
public class BookController {
private static final Map<Long, Book> BOOK_REPO = new HashMap<>();
@GetMapping("/{id}")
public Book getBookById(@PathVariable Long id) {
if (!BOOK_REPO.containsKey(id)) {
throw new BookNotFoundException(id);
}
return BOOK_REPO.get(id);
}
@PostMapping
public Book createBook(@RequestBody Book book) {
if (book.getTitle() == null || book.getTitle().isBlank()) {
throw new InvalidBookException("Title is required");
}
long newId = BOOK_REPO.size() + 1L;
book.setId(newId);
BOOK_REPO.put(newId, book);
return book;
}
}
Testing the API
Get Book (Not Found)
GET /api/books/99
Response:
{
"message": "Book with ID 99 not found",
"details": "uri=/api/books/99",
"timestamp": "2025-06-02T14:45:00"
}
Create Book (Bad Request)
POST /api/books
Content-Type: application/json
{
"title": "",
"author": "Unknown"
}
Response:
{
"message": "Title is required",
"details": "uri=/api/books",
"timestamp": "2025-06-02T14:46:00"
}
Final Thoughts
Proper exception handling in Spring Boot helps create reliable, secure, and user-friendly APIs. Key takeaways:
- Always validate input and throw meaningful exceptions
- Use
@ControllerAdvice
for global and centralized handling - Return consistent and structured error responses
- Never expose internal exception details or stack traces to clients
Full Directory Structure
src/
├── main/
│ ├── java/com/example/demo/
│ │ ├── Book.java
│ │ ├── BookController.java
│ │ ├── BookNotFoundException.java
│ │ ├── InvalidBookException.java
│ │ ├── ErrorResponse.java
│ │ └── GlobalExceptionHandler.java
└── resources/
└── application.properties