Understand the centralized error handling system in the E-Commerce Backend API
This guide explains the error handling architecture, including the GlobalExceptionHandler and custom exceptions for validation and business logic errors.
The ContrasenaVO class throws this exception for validation failures:
public ContrasenaVO(String value) { if (value == null || value.isEmpty()) { throw new InvalidPasswordException("La contraseña no puede estar vacía"); } if (value.length() < 8) { throw new InvalidPasswordException("La contraseña debe tener al menos 8 caracteres"); } if (value.length() > 64) { throw new InvalidPasswordException("La contraseña no puede superar los 64 caracteres"); } if (!value.matches(".*[A-Za-z].*") || !value.matches(".*\\d.*")) { throw new InvalidPasswordException( "La contraseña debe contener al menos una letra y un número" ); } this.value = value;}
curl -X POST http://localhost:8080/registro \ -H "Content-Type: application/json" \ -d '{"usuario":"john","contrasena":""}'# Response: 400 Bad Request# La contraseña no puede estar vacía
Create custom exceptions for different error scenarios:
public class UsuarioNotFoundException extends RuntimeException {}public class UsuarioAlreadyExistsException extends RuntimeException {}public class InvalidCredentialsException extends RuntimeException {}
2
Centralize Exception Handling
Keep all @ExceptionHandler methods in GlobalExceptionHandler for consistency.
3
Return Appropriate Status Codes
Use correct HTTP status codes:
400: Client validation errors
401: Authentication failures
404: Resource not found
409: Conflicts (duplicates)
500: Server errors
4
Provide Clear Error Messages
Error messages should be:
Descriptive and actionable
User-friendly (translated if needed)
Free of sensitive information
// Good"La contraseña debe tener al menos 8 caracteres"// Bad"Validation failed at line 23 in ContrasenaVO.java"
public record ErrorResponse( String timestamp, int status, String error, String message, String path) {}@ExceptionHandler(InvalidPasswordException.class)public ResponseEntity<ErrorResponse> handleInvalidPasswordException( InvalidPasswordException ex, HttpServletRequest request) { ErrorResponse error = new ErrorResponse( LocalDateTime.now().toString(), 400, "Bad Request", ex.getMessage(), request.getRequestURI() ); return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(error);}
Response:
{ "timestamp": "2026-03-06T10:39:00", "status": 400, "error": "Bad Request", "message": "La contraseña debe tener al menos 8 caracteres", "path": "/registro"}
public record RegistrarUsuarioRequest( @NotBlank(message = "El usuario no puede estar vacío") @Size(min = 3, max = 50, message = "El usuario debe tener entre 3 y 50 caracteres") String usuario, @NotBlank(message = "La contraseña no puede estar vacía") String contrasena) {}// Controller@PostMapping("/registro")public UsuarioPOJO registroUsuario(@Valid @RequestBody RegistrarUsuarioRequest request) { // ...}// Exception Handler@ExceptionHandler(MethodArgumentNotValidException.class)public ResponseEntity<Map<String, String>> handleValidationExceptions( MethodArgumentNotValidException ex) { Map<String, String> errors = new HashMap<>(); ex.getBindingResult().getFieldErrors().forEach(error -> errors.put(error.getField(), error.getDefaultMessage()) ); return ResponseEntity.badRequest().body(errors);}
@Testvoid testInvalidPasswordExceptionHandler() { // Arrange GlobalExceptionHandler handler = new GlobalExceptionHandler(); InvalidPasswordException exception = new InvalidPasswordException( "La contraseña debe tener al menos 8 caracteres" ); // Act ResponseEntity<String> response = handler.handleInvalidPasswordException(exception); // Assert assertEquals(HttpStatus.BAD_REQUEST, response.getStatusCode()); assertEquals("La contraseña debe tener al menos 8 caracteres", response.getBody());}
public UsuarioPOJO findById(Long id) { return repository.findById(id) .orElseThrow(() -> new UsuarioNotFoundException( "Usuario con id " + id + " no encontrado" ));}
4
Test the Handler
curl -X GET http://localhost:8080/usuarios/999# Response: 404 Not Found# Usuario con id 999 no encontrado