SpringBoot: Unified Response and Exception Handling

Naveen Metta
4 min readJun 6, 2024
credit goes to the owner : https://inspeerity.com/blog/common-http-error-handling-in-spring-boot/
source : inspeerity.com

SpringBoot is a powerful framework that simplifies the development of Java-based applications. One of its significant advantages is the ability to create unified responses and handle exceptions gracefully. In this article, we’ll dive deep into unified response and exception handling in SpringBoot, providing detailed explanations and plenty of Java code examples to help you master these concepts.

What is a Unified Response?

A unified response in the context of web applications refers to a standardized way of structuring the output returned by your API. This standardization ensures consistency across different endpoints, making it easier for clients to understand and consume your API.

Why is it Important?

  • Consistency: Clients can expect the same structure, regardless of the endpoint.
  • Ease of Use: Simplifies error handling on the client side.
  • Maintainability: Easier to manage and update responses across the application.

Implementing Unified Response in SpringBoot

To implement a unified response structure in SpringBoot, you can create a custom response object and use it across your controllers.

Step-by-Step Guide:

  1. Create a Response Wrapper Class
public class ApiResponse<T> {
private String status;
private String message;
private T data;

public ApiResponse(String status, String message, T data) {
this.status = status;
this.message = message;
this.data = data;
}

// Getters and Setters
}
  1. Modify Controller to Use ApiResponse
@RestController
@RequestMapping("/api")
public class UserController {

@GetMapping("/users/{id}")
public ResponseEntity<ApiResponse<User>> getUserById(@PathVariable Long id) {
User user = userService.findById(id);
ApiResponse<User> response = new ApiResponse<>("success", "User fetched successfully", user);
return ResponseEntity.ok(response);
}
}

Exception Handling in SpringBoot

Exception handling is crucial for building robust applications. SpringBoot offers several ways to handle exceptions, ensuring that your application can gracefully manage errors.

Basic Exception Handling

  1. Use @ExceptionHandler in Controllers
@RestController
@RequestMapping("/api")
public class UserController {

@ExceptionHandler(UserNotFoundException.class)
public ResponseEntity<ApiResponse<String>> handleUserNotFoundException(UserNotFoundException ex) {
ApiResponse<String> response = new ApiResponse<>("error", ex.getMessage(), null);
return ResponseEntity.status(HttpStatus.NOT_FOUND).body(response);
}

@GetMapping("/users/{id}")
public ResponseEntity<ApiResponse<User>> getUserById(@PathVariable Long id) {
User user = userService.findById(id);
if (user == null) {
throw new UserNotFoundException("User not found with id: " + id);
}
ApiResponse<User> response = new ApiResponse<>("success", "User fetched successfully", user);
return ResponseEntity.ok(response);
}
}
  1. Global Exception Handling with @ControllerAdvice

For a more centralized approach, you can use @ControllerAdvice.

@ControllerAdvice
public class GlobalExceptionHandler {

@ExceptionHandler(UserNotFoundException.class)
public ResponseEntity<ApiResponse<String>> handleUserNotFoundException(UserNotFoundException ex) {
ApiResponse<String> response = new ApiResponse<>("error", ex.getMessage(), null);
return ResponseEntity.status(HttpStatus.NOT_FOUND).body(response);
}

@ExceptionHandler(Exception.class)
public ResponseEntity<ApiResponse<String>> handleGeneralException(Exception ex) {
ApiResponse<String> response = new ApiResponse<>("error", "An unexpected error occurred", null);
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(response);
}
}

Advanced Exception Handling Techniques

Custom Error Response Structure

You might want to include more details in your error responses, such as error codes or a list of validation errors.

  1. Create a Custom Error Response Class
public class ErrorResponse {
private String status;
private String message;
private List<String> errors;

public ErrorResponse(String status, String message, List<String> errors) {
this.status = status;
this.message = message;
this.errors = errors;
}

// Getters and Setters
}
  1. Modify Exception Handlers to Use ErrorResponse
@ControllerAdvice
public class GlobalExceptionHandler {

@ExceptionHandler(UserNotFoundException.class)
public ResponseEntity<ErrorResponse> handleUserNotFoundException(UserNotFoundException ex) {
List<String> errors = Arrays.asList("User not found with provided ID");
ErrorResponse response = new ErrorResponse("error", ex.getMessage(), errors);
return ResponseEntity.status(HttpStatus.NOT_FOUND).body(response);
}

@ExceptionHandler(MethodArgumentNotValidException.class)
public ResponseEntity<ErrorResponse> handleValidationException(MethodArgumentNotValidException ex) {
List<String> errors = ex.getBindingResult()
.getFieldErrors()
.stream()
.map(DefaultMessageSourceResolvable::getDefaultMessage)
.collect(Collectors.toList());
ErrorResponse response = new ErrorResponse("error", "Validation failed", errors);
return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(response);
}
}

Using @ResponseStatus for Exception Handling

Sometimes, it’s useful to set the HTTP status directly on the exception class.

  1. Custom Exception with @ResponseStatus
@ResponseStatus(HttpStatus.NOT_FOUND)
public class UserNotFoundException extends RuntimeException {
public UserNotFoundException(String message) {
super(message);
}
}
  1. Controller without Explicit Exception Handling
@RestController
@RequestMapping("/api")
public class UserController {

@GetMapping("/users/{id}")
public ResponseEntity<ApiResponse<User>> getUserById(@PathVariable Long id) {
User user = userService.findById(id);
if (user == null) {
throw new UserNotFoundException("User not found with id: " + id);
}
ApiResponse<User> response = new ApiResponse<>("success", "User fetched successfully", user);
return ResponseEntity.ok(response);
}
}

Best Practices for Exception Handling

  1. Always Provide Meaningful Messages: Error messages should be clear and provide enough context to understand what went wrong.
  2. Avoid Leaking Internal Details: Don’t expose stack traces or internal exception messages to clients. Always wrap them in user-friendly messages.
  3. Use Appropriate HTTP Status Codes: Make sure the status codes you return match the nature of the error (e.g., 400 for bad requests, 404 for not found, 500 for server errors).

Conclusion

Implementing unified response and exception handling in SpringBoot not only makes your application more robust and maintainable but also enhances the client experience by providing consistent and meaningful responses. By following the steps and best practices outlined in this guide, you can effectively manage errors and deliver a more reliable API.

If you have any questions or need further clarification, feel free to ask in the comments. Happy coding!

--

--

Naveen Metta

I'm a Full Stack Developer with 2.5 years of experience. feel free to reach out for any help : mettanaveen701@gmail.com