Skip to main content

Overview

The User Management System uses a centralized error handling approach with Spring’s @RestControllerAdvice to catch and format exceptions consistently across all endpoints.

GlobalExceptionHandler

All validation and application exceptions are handled by the GlobalExceptionHandler class. Location: src/main/java/dev/juanJe/userManagementSystem/config/GlobalExceptionHandler.java
@RestControllerAdvice
public class GlobalExceptionHandler {
    @ExceptionHandler(MethodArgumentNotValidException.class)
    public ResponseEntity<ErrorResponseDTO> handlerValidationExceptions(
        MethodArgumentNotValidException exception
    ) {
        List<String> errors = new ArrayList<>();
        
        for (FieldError error : exception.getBindingResult().getFieldErrors()) {
            errors.add(error.getDefaultMessage());
        }
        
        ErrorResponseDTO errorResponse = new ErrorResponseDTO(
            "Error de validacion",
            HttpStatus.BAD_REQUEST.value(),
            errors
        );
        
        return ResponseEntity.badRequest().body(errorResponse);
    }
}

How It Works

1

Exception occurs

When a validation error or exception is thrown in any controller, Spring catches it.
2

Handler matches exception type

The @ExceptionHandler annotation maps specific exception types to handler methods.
3

Error details extracted

Field errors are collected from the exception’s BindingResult.
4

Response formatted

An ErrorResponseDTO is created with the error details and returned to the client.

Error Response Format

All errors follow a standard JSON structure defined by ErrorResponseDTO. Location: src/main/java/dev/juanJe/userManagementSystem/dto/ErrorResponseDTO.java

Structure

public class ErrorResponseDTO {
    private String message;      // High-level error description
    private int status;          // HTTP status code
    private LocalDateTime timestamp; // When the error occurred
    private List<String> errors;    // Detailed error messages
}

Example Response

{
  "message": "Error de validacion",
  "status": 400,
  "timestamp": "2026-03-03T10:30:45.123",
  "errors": [
    "Username obligatorio",
    "El email debe ser valido",
    "La password debe tener entre 8 y 16 caracteres"
  ]
}
The timestamp field is automatically populated when the ErrorResponseDTO is created using the constructor with three parameters.

Common Error Scenarios

Validation Errors (400 Bad Request)

Triggered when request body fails Jakarta Validation constraints.
Scenario: Client sends incomplete user registration dataRequest:
{
  "username": "",
  "email": "[email protected]"
}
Response:
{
  "message": "Error de validacion",
  "status": 400,
  "timestamp": "2026-03-03T10:30:45.123",
  "errors": [
    "Username obligatorio",
    "Password obligatorio"
  ]
}
HTTP Status: 400 BAD REQUEST
Scenario: Client provides malformed email addressRequest:
{
  "username": "john_doe",
  "email": "invalid-email",
  "password": "SecurePass1"
}
Response:
{
  "message": "Error de validacion",
  "status": 400,
  "timestamp": "2026-03-03T10:31:22.456",
  "errors": [
    "El email debe ser valido"
  ]
}
HTTP Status: 400 BAD REQUEST
Scenario: Username doesn’t meet minimum length requirementRequest:
{
  "username": "ab",
  "email": "[email protected]",
  "password": "SecurePass1"
}
Response:
{
  "message": "Error de validacion",
  "status": 400,
  "timestamp": "2026-03-03T10:32:10.789",
  "errors": [
    "El username debe tener entre 3 y 20 caracteres"
  ]
}
HTTP Status: 400 BAD REQUEST
Scenario: Password doesn’t meet complexity requirementsRequest:
{
  "username": "john_doe",
  "email": "[email protected]",
  "password": "password"
}
Response:
{
  "message": "Error de validacion",
  "status": 400,
  "timestamp": "2026-03-03T10:33:05.234",
  "errors": [
    "La password debe tener al menos una numero, una letra minuscula y una letra mayuscula"
  ]
}
HTTP Status: 400 BAD REQUEST

Multiple Validation Errors

When multiple fields fail validation, all errors are returned together:
{
  "message": "Error de validacion",
  "status": 400,
  "timestamp": "2026-03-03T10:35:20.567",
  "errors": [
    "Username obligatorio",
    "El email debe ser valido",
    "La password debe tener entre 8 y 16 caracteres",
    "La password debe tener al menos una numero, una letra minuscula y una letra mayuscula"
  ]
}

HTTP Status Codes

The API uses standard HTTP status codes:
Status CodeMeaningWhen Used
400Bad RequestValidation errors, malformed requests
401UnauthorizedMissing or invalid authentication token
403ForbiddenValid token but insufficient permissions
404Not FoundResource doesn’t exist
409ConflictDuplicate username or email
500Internal Server ErrorUnexpected server errors
Currently, the GlobalExceptionHandler explicitly handles validation errors (400). Other status codes may be returned by Spring Security filters or custom exception handlers.

Handling Errors in Client Applications

JavaScript/TypeScript Example

try {
  const response = await fetch('/api/signup', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({
      username: 'jo',
      email: 'invalid-email',
      password: 'weak'
    })
  });
  
  if (!response.ok) {
    const errorData = await response.json();
    
    // Display all validation errors
    errorData.errors.forEach(error => {
      console.error(error);
    });
    
    // Or show a summary
    alert(`${errorData.message}: ${errorData.errors.join(', ')}`);
  }
} catch (error) {
  console.error('Network error:', error);
}

Java Client Example

try {
    // Attempt API call
    SignupResponseDTO response = apiClient.signup(createUserDTO);
} catch (HttpClientErrorException.BadRequest e) {
    // Parse error response
    ObjectMapper mapper = new ObjectMapper();
    ErrorResponseDTO error = mapper.readValue(
        e.getResponseBodyAsString(), 
        ErrorResponseDTO.class
    );
    
    // Handle validation errors
    error.getErrors().forEach(System.err::println);
}

Extending Error Handling

To handle additional exception types, add new @ExceptionHandler methods:
@RestControllerAdvice
public class GlobalExceptionHandler {
    
    // Existing validation handler...
    
    @ExceptionHandler(ResponseStatusException.class)
    public ResponseEntity<ErrorResponseDTO> handleResponseStatusException(
        ResponseStatusException exception
    ) {
        ErrorResponseDTO errorResponse = new ErrorResponseDTO(
            exception.getReason(),
            exception.getStatusCode().value(),
            List.of(exception.getMessage())
        );
        
        return ResponseEntity
            .status(exception.getStatusCode())
            .body(errorResponse);
    }
    
    @ExceptionHandler(Exception.class)
    public ResponseEntity<ErrorResponseDTO> handleGenericException(
        Exception exception
    ) {
        ErrorResponseDTO errorResponse = new ErrorResponseDTO(
            "Internal server error",
            HttpStatus.INTERNAL_SERVER_ERROR.value(),
            List.of(exception.getMessage())
        );
        
        return ResponseEntity
            .status(HttpStatus.INTERNAL_SERVER_ERROR)
            .body(errorResponse);
    }
}
When adding custom exception handlers, place more specific handlers before generic ones to ensure proper exception matching.

Build docs developers (and LLMs) love