Centralized and Loosely Coupled Exception Handling in Spring Boot
Narinder Rana
?? Sr. Software Engineer | ?? Full Stack | ?? ?? Spring Boot & Microservices | ?? Flutter App Development | ?? Scalable & Secure Solutions
As developers, we strive to create applications that are maintainable and scalable. One critical aspect of achieving this is to handle exceptions and errors in a centralized and loosely coupled manner. In a Spring Boot application, this can be efficiently implemented using a few straightforward steps. Here's how:
1. Define Custom Exception Classes
Start by defining custom exception classes to represent different error conditions in your application.
public class ResourceNotFoundException extends RuntimeException {
public ResourceNotFoundException(String message) {
super(message);
}
}
public class InvalidInputException extends RuntimeException {
public InvalidInputException(String message) {
super(message);
}
}
2. Create an Exception Response Class
Next, define a class to represent the structure of your error responses. This class encapsulates the error message, details, and a timestamp.
import java.util.Date;
public class ErrorResponse {
private String message;
private String details;
private Date timestamp;
public ErrorResponse(String message, String details) {
this.message = message;
this.details = details;
this.timestamp = new Date();
}
// Getters and setters
}
3. Implement a Centralized Exception Handler
Use @ControllerAdvice to create a global exception handler. This will intercept exceptions thrown by any controller and handle them in a centralized manner.
领英推荐
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 {
@ExceptionHandler(ResourceNotFoundException.class)
public ResponseEntity<ErrorResponse> handleResourceNotFoundException(ResourceNotFoundException ex, WebRequest request) {
ErrorResponse errorResponse = new ErrorResponse(ex.getMessage(), request.getDescription(false));
return new ResponseEntity<>(errorResponse, HttpStatus.NOT_FOUND);
}
@ExceptionHandler(InvalidInputException.class)
public ResponseEntity<ErrorResponse> handleInvalidInputException(InvalidInputException ex, WebRequest request) {
ErrorResponse errorResponse = new ErrorResponse(ex.getMessage(), request.getDescription(false));
return new ResponseEntity<>(errorResponse, HttpStatus.BAD_REQUEST);
}
@ExceptionHandler(Exception.class)
public ResponseEntity<ErrorResponse> handleGlobalException(Exception ex, WebRequest request) {
ErrorResponse errorResponse = new ErrorResponse(ex.getMessage(), request.getDescription(false));
return new ResponseEntity<>(errorResponse, HttpStatus.INTERNAL_SERVER_ERROR);
}
}
4. Use Exceptions in Your Services and Controllers
Throw the custom exceptions in your services and controllers where necessary. This ensures that your application logic remains clean and the error handling is managed centrally.
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
@RestController
@RequestMapping("/api/resources")
public class ResourceController {
@GetMapping("/{id}")
public ResponseEntity<Resource> getResourceById(@PathVariable Long id) {
Resource resource = resourceService.findById(id);
if (resource == null) {
throw new ResourceNotFoundException("Resource not found with id " + id);
}
return new ResponseEntity<>(resource, HttpStatus.OK);
}
@PostMapping
public ResponseEntity<Resource> createResource(@RequestBody Resource resource) {
if (resource.getName() == null || resource.getName().isEmpty()) {
throw new InvalidInputException("Resource name is required");
}
Resource createdResource = resourceService.save(resource);
return new ResponseEntity<>(createdResource, HttpStatus.CREATED);
}
}
5. Test Your Exception Handling
Finally, write unit and integration tests to verify that your exception handling works as expected.
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.http.MediaType;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
import com.fasterxml.jackson.databind.ObjectMapper;
@SpringBootTest
public class ResourceControllerTests {
@Autowired
private MockMvc mockMvc;
@Test
public void testResourceNotFound() throws Exception {
mockMvc.perform(get("/api/resources/999"))
.andExpect(status().isNotFound())
.andExpect(jsonPath("$.message").value("Resource not found with id 999"));
}
@Test
public void testInvalidInput() throws Exception {
Resource resource = new Resource();
mockMvc.perform(post("/api/resources")
.contentType(MediaType.APPLICATION_JSON)
.content(new ObjectMapper().writeValueAsString(resource)))
.andExpect(status().isBadRequest())
.andExpect(jsonPath("$.message").value("Resource name is required"));
}
}
Conclusion
Centralizing exception handling in Spring Boot using @ControllerAdvice not only simplifies the management of errors but also ensures that your application remains loosely coupled. By defining custom exceptions and handling them globally, you can create more maintainable and robust applications. This approach allows different components to depend on well-defined exceptions without being tied to specific error handling logic, making your code cleaner and easier to maintain.
#SpringBoot #Java #ExceptionHandling #SoftwareDevelopment #Programming #CodeQuality #CleanCode #TechTips #Developers #BackendDevelopment #JavaProgramming