Define Error Response Message
When an error occurs in your application, it’s important to send a clear & meaningful response to the client. This response should include details like the error message, status code, & timestamp. To achieve this, we’ll create a custom error response class.
This class will act as a template for all error responses. It will contain fields like `message`, `status`, & `timestamp`. Let’s create this step by step.
Step 1: Create the Error Response Class
We’ll define a simple Java class to represent the error response. This class will use basic fields & a constructor to initialize them.
public class ErrorResponse {
private String message; // Stores the error message
private int status; // Stores the HTTP status code
private long timestamp; // Stores the time when the error occurred
// Constructor to initialize the fields
public ErrorResponse(String message, int status, long timestamp) {
this.message = message;
this.status = status;
this.timestamp = timestamp;
}
// Getters to access the fields
public String getMessage() {
return message;
}
public int getStatus() {
return status;
}
public long getTimestamp() {
return timestamp;
}
}
In this Code:
1. Fields:
- `message`: A string that describes the error.
- `status`: An integer representing the HTTP status code (e.g., 404 for "Not Found").
- `timestamp`: A long value storing the time when the error occurred.
2. Constructor:
- The constructor initializes the fields with the values passed to it.
3. Getters:
- Getters are used to retrieve the values of the fields. This is important because Spring Boot will use these getters to convert the object into JSON.
Step 2: Using the Error Response Class
Once the `ErrorResponse` class is defined, it can be used in your exception-handling logic to send structured error responses. For example, if a user requests a resource that doesn’t exist, you can return an `ErrorResponse` object with a 404 status code & a message like "Resource not found".
This approach ensures that all error responses follow the same structure, making it easier for clients to understand & handle errors.
Create Controller Advice with @ExceptionHandler
In Spring Boot, `@ControllerAdvice` is a powerful annotation that allows you to handle exceptions globally. Instead of writing exception-handling logic in every controller, you can centralize it in one class. This makes your code cleaner & easier to maintain.
The `@ExceptionHandler` annotation is used inside the `@ControllerAdvice` class to define methods that handle specific exceptions. Let’s create a `GlobalExceptionHandler` class step by step.
Step 1: Create the GlobalExceptionHandler Class
We’ll create a class annotated with `@ControllerAdvice`. Inside this class, we’ll define methods to handle exceptions using `@ExceptionHandler`.
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.context.request.WebRequest;
@ControllerAdvice
public class GlobalExceptionHandler {
// Handle generic exceptions
@ExceptionHandler(Exception.class)
public ResponseEntity<ErrorResponse> handleGlobalException(Exception ex, WebRequest request) {
ErrorResponse errorResponse = new ErrorResponse(
ex.getMessage(), // Error message
HttpStatus.INTERNAL_SERVER_ERROR.value(), // HTTP status code (500)
System.currentTimeMillis() // Timestamp
);
return new ResponseEntity<>(errorResponse, HttpStatus.INTERNAL_SERVER_ERROR);
}
// Handle specific exceptions (e.g., ResourceNotFoundException)
@ExceptionHandler(ResourceNotFoundException.class)
public ResponseEntity<ErrorResponse> handleResourceNotFoundException(
ResourceNotFoundException ex, WebRequest request) {
ErrorResponse errorResponse = new ErrorResponse(
ex.getMessage(), // Error message
HttpStatus.NOT_FOUND.value(), // HTTP status code (404)
System.currentTimeMillis() // Timestamp
);
return new ResponseEntity<>(errorResponse, HttpStatus.NOT_FOUND);
}
}
Explanation of the Code
1. `@ControllerAdvice`:
This annotation tells Spring Boot that this class will handle exceptions globally for all controllers.
2. `@ExceptionHandler`:
This annotation is used to define methods that handle specific exceptions. For example, `@ExceptionHandler(Exception.class)` handles all generic exceptions, while `@ExceptionHandler(ResourceNotFoundException.class)` handles a custom exception (we’ll define this later).
3. `ResponseEntity<ErrorResponse>`:
This is the return type of the exception-handling methods. It allows us to send a custom error response along with an HTTP status code.
4. `ErrorResponse`:
We use the `ErrorResponse` class (created earlier) to structure the error response.
Step 2: Define a Custom Exception (Optional)
To demonstrate handling specific exceptions, let’s create a custom exception called `ResourceNotFoundException`.
public class ResourceNotFoundException extends RuntimeException {
public ResourceNotFoundException(String message) {
super(message);
}
}
This exception can be thrown when a requested resource is not found. For example, if a user tries to access a record that doesn’t exist in the database, you can throw this exception.
Step 3: How It Works
- When an exception occurs in any controller, Spring Boot will look for a matching `@ExceptionHandler` method in the `GlobalExceptionHandler` class.
- If a matching method is found, it will execute that method & return the custom error response to the client.
- If no matching method is found, the generic `handleGlobalException` method will handle the exception.
Modify Controller for using @ControllerAdvice
Now that we’ve created the `GlobalExceptionHandler` class, we need to modify our controllers to throw exceptions that will be handled by this global handler. This ensures that all exceptions are managed in one place, making the code cleaner & more maintainable.
Let’s create a simple controller & demonstrate how to use the `@ControllerAdvice` class.
Step 1: Create a Controller
We’ll create a `UserController` class that handles HTTP requests related to users. This controller will throw exceptions in certain scenarios, which will be caught by the `GlobalExceptionHandler`.
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/users")
public class UserController {
// Simulate a method to fetch user by ID
@GetMapping("/{id}")
public String getUserById(@PathVariable int id) {
// Simulate a scenario where the user is not found
if (id != 1) {
throw new ResourceNotFoundException("User not found with ID: " + id);
}
return "User found with ID: " + id;
}
// Simulate a method that throws a generic exception
@GetMapping("/error")
public String triggerError() {
throw new RuntimeException("An unexpected error occurred!");
}
}
In this Code:
1. `@RestController`:
This annotation marks the class as a controller where every method returns a domain object instead of a view.
2. `@RequestMapping("/users")`:
This annotation maps all methods in the controller to the `/users` URL path.
3. `@GetMapping("/{id}")`:
This method handles GET requests to fetch a user by their ID. If the ID is not 1, it throws a `ResourceNotFoundException`.
4. `@GetMapping("/error")`:
This method simulates a scenario where a generic exception is thrown.
Step 2: How It Works with @ControllerAdvice
- When a request is made to `/users/{id}`, the `getUserById` method is called.
- If the ID is not 1, it throws a `ResourceNotFoundException`.
- This exception is caught by the `handleResourceNotFoundException` method in the `GlobalExceptionHandler` class.
- A custom error response is returned to the client with a 404 status code.
- When a request is made to `/users/error`, the `triggerError` method is called.
- It throws a `RuntimeException`.
- This exception is caught by the `handleGlobalException` method in the `GlobalExceptionHandler` class.
- A custom error response is returned to the client with a 500 status code.
Step 3: Testing the Controller
You can test the controller using tools like Postman or directly in your browser.
1. Valid Request:
URL: `http://localhost:8080/users/1`
Response: `"User found with ID: 1"`
2. Invalid Request (User Not Found):
URL: `http://localhost:8080/users/2`
Response:
{
"message": "User not found with ID: 2",
"status": 404,
"timestamp": 1698765432100
}
3. Generic Error:
URL: `http://localhost:8080/users/error`
Response:
{
"message": "An unexpected error occurred!",
"status": 500,
"timestamp": 1698765432200
}
Handling Exceptions
Global exception handling centralizes the error-handling logic. In Spring Boot, we use the @ControllerAdvice annotation.
Example: GlobalExceptionHandler
package com.example.demo.handler;
import com.example.demo.exception.CustomerNotFoundException;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import java.time.LocalDateTime;
import java.util.HashMap;
import java.util.Map;
@ControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(CustomerNotFoundException.class)
public ResponseEntity<Map<String, Object>> handleCustomerNotFoundException(CustomerNotFoundException ex) {
Map<String, Object> response = new HashMap<>();
response.put("timestamp", LocalDateTime.now());
response.put("message", ex.getMessage());
response.put("status", HttpStatus.NOT_FOUND.value());
return new ResponseEntity<>(response, HttpStatus.NOT_FOUND);
}
@ExceptionHandler(Exception.class)
public ResponseEntity<Map<String, Object>> handleGeneralException(Exception ex) {
Map<String, Object> response = new HashMap<>();
response.put("timestamp", LocalDateTime.now());
response.put("message", "An unexpected error occurred");
response.put("status", HttpStatus.INTERNAL_SERVER_ERROR.value());
return new ResponseEntity<>(response, HttpStatus.INTERNAL_SERVER_ERROR);
}
}
Explanation of the Code:
- @ControllerAdvice: Makes this class a global exception handler.
- @ExceptionHandler: Specifies the type of exception to handle.
- Response Structure: Includes a timestamp, message, and HTTP status code.
Output:
When a CustomerNotFoundException is thrown, the API will return:
{
"timestamp": "2025-01-24T10:00:00",
"message": "Customer with ID 1 not found",
"status": 404
}
Frequently Asked Questions
What is the purpose of @ControllerAdvice in Spring Boot?
@ControllerAdvice is used to handle exceptions globally across the entire application, ensuring a consistent error response structure.
How do custom exceptions improve code clarity?
Custom exceptions provide specific context and error messages, making debugging and maintenance easier.
What happens if no exception handler is defined?
If no exception handler is defined, Spring Boot’s default error response will be returned, which may not be user-friendly.
Conclusion
In this article, we covered how to implement global exception handling in a Spring Boot application. We set up a basic project, created a JPA entity, repository, custom exceptions, and a service layer, and centralized error handling using @ControllerAdvice.