23. Global Exception Handling & Error Contracts

Validation Errors, Parsing & Production Safety

Master handling MethodArgumentNotValidException, HttpMessageNotReadableException, generic Exception fallbacks, and preventing production stack trace leaks.

Validation Errors, Parsing & Production Safety

Centralized exception handlers must handle binding failures, JSON parsing errors, path parameter conversion bugs, and catch-all unexpected runtime crashes safely.


1. Handling Validation Failures (MethodArgumentNotValidException)

When @Valid evaluation fails on a @RequestBody DTO, Spring MVC raises MethodArgumentNotValidException. Extracting field-level error messages allows the handler to build a rich error response:

public class ValidationErrorResponseDto extends ErrorResponseDto {
    private final Map<String, String> fieldErrors;

    public ValidationErrorResponseDto(int statusCode, String error, String message, String path, Map<String, String> fieldErrors) {
        super(statusCode, error, message, path);
        this.fieldErrors = fieldErrors;
    }

    public Map<String, String> getFieldErrors() { return fieldErrors; }
}
@ExceptionHandler(MethodArgumentNotValidException.class)
public ResponseEntity<ValidationErrorResponseDto> handleValidationErrors(
        MethodArgumentNotValidException ex, HttpServletRequest request) {

    Map<String, String> fieldErrors = new HashMap<>();
    ex.getBindingResult().getFieldErrors().forEach(error -> 
        fieldErrors.put(error.getField(), error.getDefaultMessage())
    );

    ValidationErrorResponseDto response = new ValidationErrorResponseDto(
            HttpStatus.BAD_REQUEST.value(),
            HttpStatus.BAD_REQUEST.getReasonPhrase(),
            "Validation failed",
            request.getRequestURI(),
            fieldErrors
    );

    return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(response);
}

2. Handling Request Parsing & Type Mismatch Failures

1. Malformed JSON (HttpMessageNotReadableException)

Triggers when a client sends invalid JSON (e.g. { "age": "twenty" } for an Integer field):

@ExceptionHandler(HttpMessageNotReadableException.class)
public ResponseEntity<ErrorResponseDto> handleJsonParsingError(
        HttpMessageNotReadableException ex, HttpServletRequest request) {

    ErrorResponseDto response = new ErrorResponseDto(
            HttpStatus.BAD_REQUEST.value(),
            HttpStatus.BAD_REQUEST.getReasonPhrase(),
            "Invalid JSON payload or data type mismatch",
            request.getRequestURI()
    );
    return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(response);
}

2. Path Variable Type Mismatch (MethodArgumentTypeMismatchException)

Triggers when a client passes invalid path types (e.g. GET /api/students/abc where id is Long):

@ExceptionHandler(MethodArgumentTypeMismatchException.class)
public ResponseEntity<ErrorResponseDto> handleTypeMismatch(
        MethodArgumentTypeMismatchException ex, HttpServletRequest request) {

    ErrorResponseDto response = new ErrorResponseDto(
            HttpStatus.BAD_REQUEST.value(),
            HttpStatus.BAD_REQUEST.getReasonPhrase(),
            "Invalid value for parameter '" + ex.getName() + "'",
            request.getRequestURI()
    );
    return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(response);
}

3. Generic Exception Fallback & Production Security

SECURITY RULE: Never return ex.getMessage() or stack traces in generic Exception.class handlers! Exposing raw messages risks leaking internal SQL queries, table names, database passwords, or filesystem paths to external attackers.

// SAFE PRODUCTION FALLBACK
@ExceptionHandler(Exception.class)
public ResponseEntity<ErrorResponseDto> handleGenericException(
        Exception ex, HttpServletRequest request) {

    // Log the full stack trace internally for developers
    // logger.error("Unhandled exception occurred", ex);

    // Return a sanitized, generic error message to external clients
    ErrorResponseDto response = new ErrorResponseDto(
            HttpStatus.INTERNAL_SERVER_ERROR.value(),
            HttpStatus.INTERNAL_SERVER_ERROR.getReasonPhrase(),
            "An unexpected server error occurred. Please try again later.",
            request.getRequestURI()
    );
    return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(response);
}

4. Exception Handler Ordering Hierarchy

Spring MVC selects the most specific handler matching the thrown exception class:

HANDLING ORDER HIERARCHY:
├── ResourceNotFoundException       ──► Maps to 404 (Most specific business handler)
├── DuplicateResourceException      ──► Maps to 409 (Specific business conflict handler)
├── MethodArgumentNotValidException  ──► Maps to 400 + Field Errors (Validation handler)
├── HttpMessageNotReadableException  ──► Maps to 400 (Parsing failure handler)
└── Exception                       ──► Maps to 500 (Catch-all safety net fallback)

❓ Interactive Self-Assessment

Knowledge Check

Why is returning ex.getMessage() inside a catch-all Exception handler dangerous in production?

Refactoring Monolithic Controller Error Logic

A developer has written inline null checks and manual error maps inside a controller method:

@GetMapping("/{id}")
public ResponseEntity<?> getStudent(@PathVariable Long id) {
    Student s = studentService.find(id);
    if (s == null) {
        Map<String, String> err = new HashMap<>();
        err.put("error", "Student missing");
        return ResponseEntity.status(404).body(err);
    }
    return ResponseEntity.ok(s);
}

Refactor this endpoint into a clean Service-Controller-Advice architecture.

On this page