JAVA Contents

RFC7807 style errors

Implement RFC7807 Problem Details in Spring Boot with a global error strategy, stable error codes, traceability and secure responses.

On this page

Why Inconsistent Error Handling Breaks Production Systems

In production systems, errors are not just failures. They are contracts. If each controller returns a different error shape, if stack traces leak in some cases, if validation errors are sometimes strings and sometimes JSON, your system becomes operationally untraceable. Production symptoms: - Client teams cannot reliably parse errors. - Monitoring cannot classify failure types. - Support cannot correlate user reports with logs. - Sensitive implementation details leak in responses. Error handling must be centralized, deterministic, and safe.

Incident Scenario: 500 Errors With No Traceability

After a deployment, error rates increased. Monitoring showed HTTP 500 spikes. However: - Some endpoints returned plain text. - Others returned HTML error pages. - Some leaked stack traces. - No request identifier was included. Root cause analysis took hours because the error surface was inconsistent. This is preventable.

What RFC7807 Standardizes

RFC7807 defines a standard JSON structure for HTTP errors. Core fields: - type: a URI identifying the error category - title: short, human-readable summary - status: HTTP status code - detail: human-readable explanation - instance: specific occurrence reference Production systems should extend this with: - errorCode: stable, machine-readable identifier - traceId or requestId - fieldErrors for validation failures The structure must be stable across all endpoints.

Anti-Pattern: Returning Raw Exceptions

@ExceptionHandler(Exception.class)
public ResponseEntity handle(Exception ex) {
    return ResponseEntity.status(500).body(ex.toString());
}
This is dangerous because: - Internal class names leak - SQL fragments may appear - File paths and environment hints may leak - Clients receive unstable formats Production rule: Never expose internal exception details in responses.

Correct Pattern: Centralized Global Exception Handler

import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;

import java.util.LinkedHashMap;
import java.util.Map;

@RestControllerAdvice
public class GlobalExceptionHandler {

    @ExceptionHandler(MethodArgumentNotValidException.class)
    public ResponseEntity> handleValidation(MethodArgumentNotValidException ex) {
        Map body = new LinkedHashMap<>();
        body.put("type", "https://api.example.com/problems/validation");
        body.put("title", "Validation failed");
        body.put("status", 400);
        body.put("detail", "Request validation failed");

        Map fieldErrors = new LinkedHashMap<>();
        ex.getBindingResult().getFieldErrors()
            .forEach(err -> fieldErrors.put(err.getField(), err.getDefaultMessage()));

        body.put("errors", fieldErrors);

        return ResponseEntity.badRequest().body(body);
    }

    @ExceptionHandler(ResourceNotFoundException.class)
    public ResponseEntity> handleNotFound(ResourceNotFoundException ex) {
        Map body = new LinkedHashMap<>();
        body.put("type", "https://api.example.com/problems/not-found");
        body.put("title", "Resource not found");
        body.put("status", 404);
        body.put("detail", "The requested resource does not exist");
        return ResponseEntity.status(404).body(body);
    }

    @ExceptionHandler(Exception.class)
    public ResponseEntity> handleInternal(Exception ex) {
        Map body = new LinkedHashMap<>();
        body.put("type", "https://api.example.com/problems/internal");
        body.put("title", "Internal server error");
        body.put("status", 500);
        body.put("detail", "Unexpected error occurred");
        return ResponseEntity.status(500).body(body);
    }
}
Notice: - Internal details are not exposed. - Response shape is consistent. - Status codes are correct. - Validation errors are field-level.

Error Code Strategy

Human-readable messages are not stable identifiers. Introduce a stable errorCode: - USER_NOT_FOUND - INVALID_EMAIL_FORMAT - PAYMENT_TIMEOUT Clients depend on errorCode, not detail message. Changing detail text must not break clients.

Security Considerations

Never expose: - SQL queries - Stack traces - Database schema names - Internal hostnames - Token fragments - Full request payloads Mask PII in logs. Sanitize error messages.

Operational Observability

Every error response should include: - requestId or traceId - consistent status mapping - stable type URI Monitoring dashboards can then group: - validation errors - business conflicts - infrastructure failures Without consistent structure, observability is compromised.

Checklist

- Use a single global exception handler - Implement RFC7807-compliant structure - Never expose stack traces - Use stable errorCode values - Return correct HTTP status codes - Include requestId or traceId - Log full details internally, not externally - Ensure error format is consistent across services In production, error handling is part of your public API. Treat it as a contract, not a side effect.