Learn how to map different exceptions to HTTP status codes in Spring Boot using @ControllerAdvice and @ExceptionHandler. Beginner-friendly, end-to-end Java 21 example.
Introduction
Imagine you order food online and the app simply says “Something went wrong”—no hint whether the restaurant is closed, the item is unavailable, or payment failed. Frustrating, right?
The same frustration happens when APIs return wrong or generic HTTP status codes.
In Spring Boot, throwing exceptions is easy—but mapping them correctly to meaningful HTTP status codes is what separates a beginner API from a professional one. A 404 should mean not found, a 400 should mean bad input, and a 500 should mean server failure.
In this blog, you’ll learn how to map different exceptions to different HTTP status codes in Spring Boot, using clear explanations, real-world analogies, and an end-to-end Java 21 example.
Core Concepts
What Does “Mapping Exceptions” Mean?
Think of exceptions as signals and HTTP status codes as traffic lights 🚦.
| Situation | Exception | HTTP Status |
|---|---|---|
| Resource not found | UserNotFoundException |
404 NOT_FOUND |
| Invalid input | IllegalArgumentException |
400 BAD_REQUEST |
| Business rule violation | BusinessException |
409 CONFLICT |
| Unexpected failure | Exception |
500 INTERNAL_SERVER_ERROR |
Mapping exceptions means:
Telling Spring Boot which HTTP status code to return when a specific exception occurs
Why Is This Important?
Proper exception-to-status mapping gives you:
✅ Clear communication with API consumers
✅ Easier debugging
✅ REST-compliant APIs
✅ Cleaner controller code
And the best part?
👉 You do this centrally, not in every controller.
The Key Tools
- Custom Exceptions – Represent business problems
-
@ControllerAdvice– Global exception interceptor -
@ExceptionHandler– Maps exception → HTTP response -
ResponseEntity– Full control over status & body
Code Examples (End-to-End)
Let’s build a complete working example step by step.
🧱 Example 1: Mapping Custom Exceptions to HTTP Status Codes
Step 1: Create Custom Exceptions
package com.example.demo.exception;
/**
* Thrown when a user is not found in the system.
*/
public class UserNotFoundException extends RuntimeException {
public UserNotFoundException(String message) {
super(message);
}
}
package com.example.demo.exception;
/**
* Thrown when a business rule is violated.
*/
public class BusinessException extends RuntimeException {
public BusinessException(String message) {
super(message);
}
}
Step 2: Service Layer (Where Exceptions Are Thrown)
package com.example.demo.service;
import com.example.demo.exception.BusinessException;
import com.example.demo.exception.UserNotFoundException;
import org.springframework.stereotype.Service;
@Service
public class UserService {
public String getUserById(Long id) {
if (id <= 0) {
throw new IllegalArgumentException("User ID must be greater than zero");
}
if (id == 404) {
throw new UserNotFoundException("User not found with id " + id);
}
if (id == 409) {
throw new BusinessException("User account is locked");
}
return "User fetched successfully";
}
}
Step 3: REST Controller (Clean & Simple)
package com.example.demo.controller;
import com.example.demo.service.UserService;
import org.springframework.web.bind.annotation.*;
@RestController
@RequestMapping("/users")
public class UserController {
private final UserService userService;
public UserController(UserService userService) {
this.userService = userService;
}
@GetMapping("/{id}")
public String getUser(@PathVariable Long id) {
return userService.getUserById(id);
}
}
📌 Notice:
No try-catch blocks in the controller ✔
This is the power of global exception handling.
🧠 Example 2: Global Exception Mapping with @ControllerAdvice
Step 4: Global Exception Handler
package com.example.demo.exception;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import java.time.LocalDateTime;
import java.util.Map;
@ControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(UserNotFoundException.class)
public ResponseEntity<Map<String, Object>> handleUserNotFound(
UserNotFoundException ex) {
return ResponseEntity.status(HttpStatus.NOT_FOUND)
.body(buildErrorResponse(ex.getMessage(), HttpStatus.NOT_FOUND));
}
@ExceptionHandler(IllegalArgumentException.class)
public ResponseEntity<Map<String, Object>> handleBadRequest(
IllegalArgumentException ex) {
return ResponseEntity.status(HttpStatus.BAD_REQUEST)
.body(buildErrorResponse(ex.getMessage(), HttpStatus.BAD_REQUEST));
}
@ExceptionHandler(BusinessException.class)
public ResponseEntity<Map<String, Object>> handleBusinessException(
BusinessException ex) {
return ResponseEntity.status(HttpStatus.CONFLICT)
.body(buildErrorResponse(ex.getMessage(), HttpStatus.CONFLICT));
}
@ExceptionHandler(Exception.class)
public ResponseEntity<Map<String, Object>> handleGenericException(
Exception ex) {
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
.body(buildErrorResponse(
"Unexpected error occurred",
HttpStatus.INTERNAL_SERVER_ERROR));
}
private Map<String, Object> buildErrorResponse(String message, HttpStatus status) {
return Map.of(
"timestamp", LocalDateTime.now(),
"status", status.value(),
"error", status.getReasonPhrase(),
"message", message
);
}
}
✅ Final API Behavior
| Request | Result |
|---|---|
http://localhost:8080/users/0 |
400 BAD_REQUEST |
http://localhost:8080/users/404 |
404 NOT_FOUND |
http://localhost:8080/users/409 |
409 CONFLICT |
http://localhost:8080/users/1 |
200 OK |
| http://localhost:8080/ | 500 INTERNAL_SERVER_ERROR |
✔ Clean
✔ Predictable
✔ REST-compliant
This is mapping exceptions to HTTP status codes in Spring Boot done right.
Best Practices
Create meaningful custom exceptions
Avoid throwing genericRuntimeExceptioneverywhere.Handle exceptions globally
Never clutter controllers with try-catch blocks.Use correct HTTP status codes
400 ≠ 404 ≠ 409 ≠ 500— each has a purpose.Never expose internal stack traces
Log them internally, return safe messages to clients.Use a consistent error response format
Makes life easier for frontend & API consumers.
Conclusion
Mapping exceptions correctly is a core skill for any Spring Boot developer.
By combining:
- Custom exceptions
@ControllerAdvice@ExceptionHandler- Proper HTTP status codes
you can build clean, professional, and consumer-friendly APIs.
If you’re serious about Java programming and want to learn Java with real-world practices, mastering mapping exceptions to HTTP status codes in Spring Boot is non-negotiable.
Call to Action
💬 Have questions about exception handling or REST API design?
👇 Drop them in the comments!
Want advanced topics like:
- RFC 7807 (Problem Details)
- API Gateway–friendly error models
- Exception handling with Apigee
Just ask — happy to help 🚀
🔗 Helpful Resources
Top comments (0)