Fading Coder

One Final Commit for the Last Sprint

Home > Tech > Content

Implementing Robust Global Exception Handling with Unified Response Objects in Spring Boot

Tech May 9 3

A centralized mechanism for error propagation ensures consistent client feedback without cluttering individual controllers. The solution relies on four pillars: defining standard business states, wrapping payloads into a generic DTO, utilizing custom runtime exceptions for expected failures, and intercepting exceptions via @RestControllerAdvice at the controller boundary.

Leveraging ResponseEntity

While custom wrappers handle semantic data, ResponseEntity manages the low-level HTTP protocol details such as headers and status codes. It allows programmatic control over the response envelope using a fluent API.

// Returning a custom map payload with specific HTTP status
ResponseEntity<Map<String, Object>> response = ResponseEntity
    .status(HttpStatus.BAD_REQUEST)
    .body(Map.of("err", "Invalid input", "code", 400));

Key HTTP ranges include:

  • 2xx: Success (e.g., 200 OK, 201 Created).
  • 4xx: Client errors (e.g., 401 Unauthorized, 403 Forbidden, 405 Method Not Allowed).
  • 5xx: Server faults (e.g., 500 Internal Server Error).

Internally, the builder pattern allows chaining methods like .header() or .contentType() before finalizing the object with .body().

Standardizing the Payload

To normalize responses across all endpoints, create a generic class that includes status information, an error message, and the actual data payload. Adding a timestamp aids debugging when reviewing server logs.

public class ApiResponse<T> {
    private Integer code;
    private String message;
    private T data;
    private Long timestamp;

    // Static factory for successful operations
    public static <T> ApiResponse<T> success(T payload) {
        return create(200, "Success", payload);
    }

    // Static factory for failure scenarios
    public static <T> ApiResponse<T> fail(Integer status, String msg) {
        return create(status, msg, null);
    }

    private static <T> ApiResponse<T> create(Integer c, String m, T d) {
        ApiResponse<T> resp = new ApiResponse<>();
        resp.timestamp = System.currentTimeMillis();
        resp.code = c;
        resp.message = m;
        resp.data = d;
        return resp;
    }
}

Centralized Exception Handling

Place this class near your application entry point. Exception resolution order matters: most specific handlers must appear before general catchers to ensure priority handling.

@Slf4j
@RestControllerAdvice
public class GlobalErrorHandler {

    // Priority 1: Handle custom business rule violations
    @ExceptionHandler(BusinessRuleViolation.class)
    public ApiResponse<?> handleBusinessError(BusinessRuleViolation e) {
        log.warn("Business violation detected: {}", e.getMessage());
        return ApiResponse.fail(e.getCode(), e.getMessage());
    }

    // Priority 2: Handle input validation failures from JSR-303 annotations
    @ExceptionHandler(MethodArgumentNotValidException.class)
    public ApiResponse<?> handleValidationFail(MethodArgumentNotValidException e) {
        String detail = e.getBindingResult()
                .getFieldErrors()
                .stream()
                .map(FieldError::getDefaultMessage)
                .collect(Collectors.joining(", "));
        return ApiResponse.fail(422, detail);
    }

    // Priority 3: Handle missing resources (404 equivalent)
    @ExceptionHandler(NoHandlerFoundException.class)
    public ApiResponse<?> handleNotFound(NoHandlerFoundException e) {
        return ApiResponse.fail(404, "Requested resource not found");
    }

    // Priority 4: Catch-all for unexpected system crashes
    @ExceptionHandler(Exception.class)
    public ApiResponse<?> handleSystemCrash(Exception t) {
        log.error("Critical system failure occurred", t);
        return ApiResponse.fail(500, "Internal server error occurred");
    }
}

This setup eliminates repetitive try-catch blocks while ensuring every error maps to a safe, structured JSON response suitable for frontend consumpsion.

Related Articles

Understanding Strong and Weak References in Java

Strong References Strong reference are the most prevalent type of object referencing in Java. When an object has a strong reference pointing to it, the garbage collector will not reclaim its memory. F...

Implement Image Upload Functionality for Django Integrated TinyMCE Editor

Django’s Admin panel is highly user-friendly, and pairing it with TinyMCE, an effective rich text editor, simplifies content management significantly. Combining the two is particular useful for bloggi...

SBUS Signal Analysis and Communication Implementation Using STM32 with Fus Remote Controller

Overview In a recent project, I utilized the SBUS protocol with the Fus remote controller to control a vehicle's basic operations, including movement, lights, and mode switching. This article is aimed...

Leave a Comment

Anonymous

◎Feel free to join the discussion and share your thoughts.