Spring Boot
Capstone – Article 5: Global Exception Handling (Expense Tracker)
By Utility Zone · 2026-01-27T18:32:01.447634
1. Introduction
Errors will happen. What matters is how your API responds when they do.
Without global exception handling:
- Stack traces leak to clients
- Error responses are inconsistent
- Controllers become messy
In this article, we’ll build clean, centralized, professional error handling.
2. Problems With Naive Error Handling
Example (❌ bad practice):
if (user == null) {
throw new RuntimeException("User not found");
}
Problems:
- Always returns 500
- No clear error structure
- Hard for frontend to handle
We need meaningful HTTP status codes + structured errors.
3. Creating Custom Exceptions
3.1 Create Exception Package
com.example.expensetracker.exception
3.2 UserNotFoundException
package com.example.expensetracker.exception;
public class UserNotFoundException extends RuntimeException {
public UserNotFoundException(String message) {
super(message);
}
}
4. Throwing Exceptions From Service Layer
Update UserService:
public User getUserById(Long id) {
return userRepository.findById(id)
.orElseThrow(() -> new UserNotFoundException("User not found with id " + id));
}
✔ Business layer throws exceptions
✔ Controller stays clean
5. Creating Global Exception Handler
5.1 @RestControllerAdvice
package com.example.expensetracker.exception;
import org.springframework.http.*;
import org.springframework.web.bind.annotation.*;
import java.time.LocalDateTime;
import java.util.HashMap;
import java.util.Map;
@RestControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(UserNotFoundException.class)
public ResponseEntity<Map<String, Object>> handleUserNotFound(UserNotFoundException ex) {
Map<String, Object> error = new HashMap<>();
error.put("timestamp", LocalDateTime.now());
error.put("status", HttpStatus.NOT_FOUND.value());
error.put("error", "Not Found");
error.put("message", ex.getMessage());
return ResponseEntity.status(HttpStatus.NOT_FOUND).body(error);
}
}
6. Handling Validation Errors
Spring throws:
MethodArgumentNotValidException
Handle it globally:
@ExceptionHandler(MethodArgumentNotValidException.class)
public ResponseEntity<Map<String, String>> handleValidationErrors(
MethodArgumentNotValidException ex) {
Map<String, String> errors = new HashMap<>();
ex.getBindingResult().getFieldErrors()
.forEach(error -> errors.put(error.getField(), error.getDefaultMessage()));
return ResponseEntity.badRequest().body(errors);
}
Produces clear field-level errors.
7. Handling Generic Exceptions
@ExceptionHandler(Exception.class)
public ResponseEntity<Map<String, Object>> handleGenericException(Exception ex) {
Map<String, Object> error = new HashMap<>();
error.put("timestamp", LocalDateTime.now());
error.put("status", HttpStatus.INTERNAL_SERVER_ERROR.value());
error.put("error", "Internal Server Error");
error.put("message", "Something went wrong");
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(error);
}
✔ Prevents stack traces from leaking
✔ Safe default response
8. Testing Error Responses
8.1 User Not Found
GET /users/999
Response:
{
"timestamp": "2026-01-27T10:30:00",
"status": 404,
"error": "Not Found",
"message": "User not found with id 999"
}
8.2 Validation Error
POST /users
Response:
{
"email": "Email must be valid",
"password": "Password must be at least 6 characters"
}
9. Why This Matters in Interviews
Interviewers look for:
✔ Proper HTTP status codes
✔ Clean error structure
✔ Centralized handling
✔ No try-catch in controllers
This article alone can level up your backend profile.
10. Common Mistakes
❌ Catching exceptions in controllers
❌ Returning 200 for errors
❌ Exposing stack traces
❌ Inconsistent error formats
11. Git Commit (Important)
Commit your changes:
git add .
git commit -m "Add global exception handling for user APIs"
12. What You Should Have Now
At this point:
- Clean error responses
- Centralized exception handling
- Proper HTTP status codes
- Enterprise-style API behavior
13. What’s Next?
➡ Capstone – Article 6: Password Encryption & Login Preparation
- Password hashing
- BCrypt
- Preparing for authentication
Type Next when you’re ready 🚀