Handling Exceptions with @ExceptionHandler Annotation Spring boot
When building web applications with Spring Boot, handling exceptions effectively is key to providing a seamless user experience. Poor exception management can lead to vague error messages, miscommunication between client and server, or application crashes. This is where the @ExceptionHandler
annotation comes into play, offering a way to manage and customize how your application communicates errors.
This post takes you through the essential aspects of working with the @ExceptionHandler
annotation in Spring Boot. You’ll learn how to use it for local error handling, map specific exceptions to custom responses, return meaningful HTTP status codes, and structure JSON response bodies with ResponseEntity
.
By the end of this article, you’ll have a solid foundation for handling exceptions in a clean, maintainable way while providing useful feedback to end users.
Table of Contents
- What is the @ExceptionHandler Annotation?
- Using @ExceptionHandler for Local Handling
- Mapping Specific Exceptions to Custom Responses
- Returning Meaningful HTTP Status Codes
- JSON Response Body with ResponseEntity
- Summary
What is the @ExceptionHandler Annotation?
The Role of @ExceptionHandler
The @ExceptionHandler
annotation in Spring Boot is used to define methods that handle specific exceptions. Rather than letting exceptions bubble up and trigger a global error page or response, @ExceptionHandler
allows you to handle errors locally and provide custom responses for a better user experience.
Why Use @ExceptionHandler?
There are several reasons developers rely on @ExceptionHandler
for custom error handling:
- Precision: Handle exceptions based on their type, allowing fine-grained control.
- Clarity: Provide clear, actionable error messages to users or client systems.
- Separation of Concerns: Manage exception-handling logic separately from core business logic, improving maintainability.
Now, let’s explore how @ExceptionHandler
works in practice.
Using @ExceptionHandler for Local Handling
Default Exception Handling Behavior
By default, if an exception occurs in a controller method, Spring Boot uses the default error page or returns a JSON-formatted response with minimal information about the error. While this behavior is suitable during development, customizing error handling is essential for production systems.
Local Exception Handling with @ExceptionHandler
Using @ExceptionHandler
, you can create methods that handle exceptions locally within a single controller. Here’s how it works:
Example:
Suppose you’re building a UserController
and want to handle a scenario where a user isn’t found in the database.
Code:
@RestController
@RequestMapping("/api/users")
public class UserController {
@GetMapping("/{id}")
public User getUserById(@PathVariable Long id) {
return userService.findById(id)
.orElseThrow(() -> new UserNotFoundException("User not found with ID " + id));
}
@ExceptionHandler(UserNotFoundException.class)
public ResponseEntity<String> handleUserNotFoundException(UserNotFoundException ex) {
return new ResponseEntity<>(ex.getMessage(), HttpStatus.NOT_FOUND);
}
}
How It Works:
- If a
UserNotFoundException
is thrown inside thegetUserById
method, thehandleUserNotFoundException
method intercepts it. - A custom message and HTTP status (
404 Not Found
) are returned to the client instead of a generic error response.
This localized approach keeps the exception handling logic close to its related controller.
Advantages of Local Handling
- Keeps related logic together for easier debugging.
- Focuses on exceptions relevant to a specific controller or module.
However, for common exceptions across multiple controllers, global handling is a better approach (e.g., using @ControllerAdvice
).
Mapping Specific Exceptions to Custom Responses
Every application has unique requirements for how errors should be communicated. Mapping exceptions to tailored responses helps convey the right level of detail based on the exception type.
Matching Specific Exceptions
You can use separate methods to handle different exception types:
Example:
@RestController
public class ProductController {
@GetMapping("/{id}")
public Product getProductById(@PathVariable Long id) throws Exception {
if (id == 0) {
throw new IllegalArgumentException("ID cannot be zero.");
}
if (id > 100) {
throw new ProductNotFoundException("Product not found with ID " + id);
}
return productService.getProductById(id);
}
@ExceptionHandler(IllegalArgumentException.class)
public ResponseEntity<String> handleIllegalArgument(IllegalArgumentException ex) {
return new ResponseEntity<>(ex.getMessage(), HttpStatus.BAD_REQUEST);
}
@ExceptionHandler(ProductNotFoundException.class)
public ResponseEntity<String> handleProductNotFound(ProductNotFoundException ex) {
return new ResponseEntity<>(ex.getMessage(), HttpStatus.NOT_FOUND);
}
}
How It Works:
- For
IllegalArgumentException
, the response includes a400 Bad Request
status. - For
ProductNotFoundException
, the response includes a404 Not Found
status.
This mapping makes responses both meaningful and useful for debugging or client-side error handling.
Best Practices for Custom Mappings
- Include exception details that help users or systems resolve the issue.
- Avoid exposing sensitive information (such as stack traces or internal IDs).
Returning Meaningful HTTP Status Codes
HTTP status codes indicate the outcome of a request and help clients understand what went wrong (or right). Pairing exceptions with appropriate HTTP status codes adds clarity.
Common HTTP Status Codes
Here’s a list of HTTP status codes commonly used with exceptions:
Status Code | Description | Example |
---|---|---|
400 | Bad Request | Invalid input data. |
401 | Unauthorized | Missing or invalid authentication. |
403 | Forbidden | User lacks permission. |
404 | Not Found | Resource not found. |
500 | Internal Server Error | Unexpected server-side error. |
Example:
@ExceptionHandler(DataIntegrityViolationException.class)
public ResponseEntity<String> handleDataIntegrityViolation(DataIntegrityViolationException ex) {
return new ResponseEntity<>("Data integrity violation occurred.", HttpStatus.CONFLICT);
}
Using meaningful status codes helps clients or API consumers handle errors more effectively.
JSON Response Body with ResponseEntity
Providing detailed JSON responses along with HTTP status codes further enhances the client’s ability to interpret errors.
Example:
You can use ResponseEntity
to customize both the HTTP status and response body.
Code:
@ExceptionHandler(UserNotFoundException.class)
public ResponseEntity<Map<String, Object>> handleUserNotFound(UserNotFoundException ex) {
Map<String, Object> errorDetails = new HashMap<>();
errorDetails.put("timestamp", LocalDateTime.now());
errorDetails.put("message", ex.getMessage());
errorDetails.put("status", HttpStatus.NOT_FOUND.value());
return new ResponseEntity<>(errorDetails, HttpStatus.NOT_FOUND);
}
Sample Response:
{
"timestamp": "2025-06-12T15:40:21.123",
"message": "User not found with ID 42",
"status": 404
}
Structuring Custom Response Models
For more complex scenarios, consider creating a custom error response model:
ErrorResponse.java:
public class ErrorResponse {
private LocalDateTime timestamp;
private String message;
private int status;
// Getters and Setters
}
@ExceptionHandler Implementation:
@ExceptionHandler(Exception.class)
public ResponseEntity<ErrorResponse> handleGenericException(Exception ex) {
ErrorResponse error = new ErrorResponse();
error.setTimestamp(LocalDateTime.now());
error.setMessage("An unexpected error occurred.");
error.setStatus(HttpStatus.INTERNAL_SERVER_ERROR.value());
return new ResponseEntity<>(error, HttpStatus.INTERNAL_SERVER_ERROR);
}
This approach ensures consistency in error responses across your application.
Summary
Effective exception handling is vital for creating robust and user-friendly Spring Boot applications. Here’s what we covered:
- Use
@ExceptionHandler
to manage local exception handling within controllers. - Map specific exceptions to meaningful custom responses, ensuring clarity and precision.
- Pair exceptions with appropriate HTTP status codes to communicate errors effectively.
- Use
ResponseEntity
to return detailed JSON error responses tailored to your application’s needs.
By following these strategies, you’ll make your applications more resilient, maintainable, and user-centric. Put these practices into your next Spring Boot project and elevate your exception-handling game!