By using this site, you agree to the Privacy Policy and Terms of Use.
Accept
World of SoftwareWorld of SoftwareWorld of Software
  • News
  • Software
  • Mobile
  • Computing
  • Gaming
  • Videos
  • More
    • Gadget
    • Web Stories
    • Trending
    • Press Release
Search
  • Privacy
  • Terms
  • Advertise
  • Contact
Copyright © All Rights Reserved. World of Software.
Reading: Designing Reliable API Systems: Exception Handling with Spring Boot’s ControllerAdvice | HackerNoon
Share
Sign In
Notification Show More
Font ResizerAa
World of SoftwareWorld of Software
Font ResizerAa
  • Software
  • Mobile
  • Computing
  • Gadget
  • Gaming
  • Videos
Search
  • News
  • Software
  • Mobile
  • Computing
  • Gaming
  • Videos
  • More
    • Gadget
    • Web Stories
    • Trending
    • Press Release
Have an existing account? Sign In
Follow US
  • Privacy
  • Terms
  • Advertise
  • Contact
Copyright © All Rights Reserved. World of Software.
World of Software > Computing > Designing Reliable API Systems: Exception Handling with Spring Boot’s ControllerAdvice | HackerNoon
Computing

Designing Reliable API Systems: Exception Handling with Spring Boot’s ControllerAdvice | HackerNoon

News Room
Last updated: 2025/11/27 at 7:28 AM
News Room Published 27 November 2025
Share
Designing Reliable API Systems: Exception Handling with Spring Boot’s ControllerAdvice | HackerNoon
SHARE

Reliability is an asset of big financial and analytics platforms. When we redesigned an enterprise platform, we learned that the main thing that made customers unhappy wasn’t performance or the user interface; it was faults that weren’t fixed that destroyed processes. Centralized exception management techniques, such as utilizing @ControllerAdvice in Spring Boot or similar patterns in other frameworks, ensure that systems behave as expected when they are under stress. This discipline is similar to a larger leadership principle: plan for failure before it happens.

Why This Matters

When you build REST APIs in Spring Boot, you’ll quickly face this problem:

“How do I handle errors neatly without writing repetitive try-catch blocks everywhere?”

Imagine having 50+ endpoints — each could fail with a Null Pointer Exception, invalid input, or a missing resource. n Instead of returning messy stack traces, you want consistent, meaningful, and client-friendly error responses.

That’s where @ControllerAdvice comes in — it centralizes all error handling in one place. n Let’s see how to build this step by step, with a real-world example.

Use Case — User Management REST API

We’ll create a simple User API that supports:

  • Fetching users

  • Creating users

  • Simulating failures for testing

    Our goal → build a unified global error-handling layer that returns clean JSON responses.

Step 1: Project Setup

Dependencies (Maven):

<dependency>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<groupId>org.springframework.boot</groupId>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<artifactId>spring-boot-starter-web</artifactId>
&nbsp;&nbsp;&nbsp;&nbsp;</dependency>
&nbsp;&nbsp;&nbsp;&nbsp;<!-- Validation -->
&nbsp;&nbsp;&nbsp;&nbsp;<dependency>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<groupId>org.springframework.boot</groupId>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<artifactId>spring-boot-starter-validation</artifactId>
&nbsp;&nbsp;&nbsp;&nbsp;</dependency>

This brings in web + validation support.

Then create your main app:

@SpringBootApplication
public class ExceptionHandlerDemoApplication {
&nbsp;&nbsp;&nbsp;&nbsp;public static void main(String[] args) {
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;SpringApplication.run(ExceptionHandlerDemoApplication.class, args);
&nbsp;&nbsp;&nbsp;&nbsp;}
}

Step 2: Define the Entity — User.java

@Data
@AllArgsConstructor
@NoArgsConstructor
public class User {
&nbsp;&nbsp;&nbsp;&nbsp;private int id;
&nbsp;&nbsp;&nbsp;&nbsp;@NotBlank(message = "Name cannot be blank")
&nbsp;&nbsp;&nbsp;&nbsp;private String name;
&nbsp;&nbsp;&nbsp;&nbsp;@Min(value = 18, message = "Age must be at least 18")
&nbsp;&nbsp;&nbsp;&nbsp;private int age;
}

**Why: n **We add simple validations so Spring can trigger Method Argument Not Valid Exception automatically when input is invalid.

Step 3: Custom Exception for Missing Data

public class ResourceNotFoundException extends RuntimeException {
&nbsp;&nbsp;&nbsp;&nbsp;public ResourceNotFoundException(String message) {
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;super(message);
&nbsp;&nbsp;&nbsp;&nbsp;}
}
public class InvalidRequestException extends RuntimeException {
&nbsp;&nbsp;&nbsp;&nbsp;public InvalidRequestException(String message) {
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;super(message);
&nbsp;&nbsp;&nbsp;&nbsp;}
}

**Why: n **To represent a “user not found” situation cleanly instead of generic exceptions.

Step 4: Build the Controller

@RestController
@RequestMapping("/api/users")
public class UserController {
&nbsp;&nbsp;&nbsp;&nbsp;// Simple GET that throws ResourceNotFoundException for id > 100
&nbsp;&nbsp;&nbsp;&nbsp;@GetMapping("/{id}")
&nbsp;&nbsp;&nbsp;&nbsp;public String getUser(@PathVariable("id") @Min(1) Integer id) {
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;if (id > 100) {
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;throw new ResourceNotFoundException("User with id " + id + " not found");
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;return "User-" + id;
&nbsp;&nbsp;&nbsp;&nbsp;}
&nbsp;&nbsp;&nbsp;&nbsp;// Create user example to demonstrate validation
&nbsp;&nbsp;&nbsp;&nbsp;public static record CreateUserRequest(
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;@NotBlank(message = "name is required") String name,
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;@Min(value = 18, message = "age must be >= 18") int age) {}
&nbsp;&nbsp;&nbsp;&nbsp;@PostMapping
&nbsp;&nbsp;&nbsp;&nbsp;@ResponseStatus(HttpStatus.CREATED)
&nbsp;&nbsp;&nbsp;&nbsp;public String createUser(@RequestBody @Valid CreateUserRequest body) {
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;if ("bad".equalsIgnoreCase(body.name())) {
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;throw new InvalidRequestException("Name 'bad' is not allowed");
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;return "created:" + body.name();
&nbsp;&nbsp;&nbsp;&nbsp;}
&nbsp;&nbsp;&nbsp;&nbsp;// Endpoint to force a server error for demo
&nbsp;&nbsp;&nbsp;&nbsp;@GetMapping("/boom")
&nbsp;&nbsp;&nbsp;&nbsp;public void boom() {
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;throw new IllegalStateException("simulated server error");
&nbsp;&nbsp;&nbsp;&nbsp;}
}

**Why: n **We create realistic scenarios — not found, validation errors, and runtime errors — that our global handler will manage.

Step 5: Create a Standard Error Model

@Data
@AllArgsConstructor
@NoArgsConstructor
public class ErrorResponse {
&nbsp;&nbsp;&nbsp;&nbsp;private OffsetDateTime timestamp;
&nbsp;&nbsp;&nbsp;&nbsp;private int status;
&nbsp;&nbsp;&nbsp;&nbsp;private String error;
&nbsp;&nbsp;&nbsp;&nbsp;private String message;
&nbsp;&nbsp;&nbsp;&nbsp;private String path;
&nbsp;&nbsp;&nbsp;&nbsp;private List<FieldError> fieldErrors;}

**Why: n **All APIs should return errors in the same structure — this improves monitoring and debugging in production.

Step 6: Implement @ControllerAdvice (Global Handler)

@ControllerAdvice
public class GlobalExceptionHandler {
&nbsp;&nbsp;&nbsp;&nbsp;private final Logger log = LoggerFactory.getLogger(GlobalExceptionHandler.class);
&nbsp;&nbsp;&nbsp;&nbsp;// Handle custom validation exceptions
&nbsp;&nbsp;&nbsp;&nbsp;@ExceptionHandler(InvalidRequestException.class)
&nbsp;&nbsp;&nbsp;&nbsp;public ResponseEntity<ErrorResponse> handleInvalidRequest(InvalidRequestException ex, HttpServletRequest req) {
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;log.debug("InvalidRequestException: {}", ex.getMessage());
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;ErrorResponse body = new ErrorResponse(OffsetDateTime.now(), HttpStatus.BAD_REQUEST.value(),
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;HttpStatus.BAD_REQUEST.getReasonPhrase(), ex.getMessage(), req.getRequestURI());
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(body);
&nbsp;&nbsp;&nbsp;&nbsp;}
&nbsp;&nbsp;&nbsp;&nbsp;// Resource not found -> 404
&nbsp;&nbsp;&nbsp;&nbsp;@ExceptionHandler(ResourceNotFoundException.class)
&nbsp;&nbsp;&nbsp;&nbsp;public ResponseEntity<ErrorResponse> handleNotFound(ResourceNotFoundException ex, HttpServletRequest req) {
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;log.debug("ResourceNotFoundException: {}", ex.getMessage());
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;ErrorResponse body = new ErrorResponse(OffsetDateTime.now(), HttpStatus.NOT_FOUND.value(),
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;HttpStatus.NOT_FOUND.getReasonPhrase(), ex.getMessage(), req.getRequestURI());
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;return ResponseEntity.status(HttpStatus.NOT_FOUND).body(body);
&nbsp;&nbsp;&nbsp;&nbsp;}
&nbsp;&nbsp;&nbsp;&nbsp;// Validation errors from @Valid on request bodies
&nbsp;&nbsp;&nbsp;&nbsp;@ExceptionHandler(MethodArgumentNotValidException.class)
&nbsp;&nbsp;&nbsp;&nbsp;public ResponseEntity<ErrorResponse> handleValidation(MethodArgumentNotValidException ex, HttpServletRequest req) {
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;log.debug("Validation failed: {}", ex.getMessage());
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;List<ErrorResponse.FieldError> fieldErrors = ex.getBindingResult().getFieldErrors().stream()
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;.map(fe -> new ErrorResponse.FieldError(fe.getField(), fe.getRejectedValue(), fe.getDefaultMessage()))
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;.collect(Collectors.toList());
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;ErrorResponse body = new ErrorResponse(OffsetDateTime.now(), HttpStatus.BAD_REQUEST.value(),
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;HttpStatus.BAD_REQUEST.getReasonPhrase(), "Validation failed", req.getRequestURI());
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;body.setFieldErrors(fieldErrors);
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;return ResponseEntity.badRequest().body(body);
&nbsp;&nbsp;&nbsp;&nbsp;}
&nbsp;&nbsp;&nbsp;&nbsp;// Type mismatch for method args (?id=abc)
&nbsp;&nbsp;&nbsp;&nbsp;@ExceptionHandler(MethodArgumentTypeMismatchException.class)
&nbsp;&nbsp;&nbsp;&nbsp;public ResponseEntity<ErrorResponse> handleTypeMismatch(MethodArgumentTypeMismatchException ex, HttpServletRequest req) {
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;log.debug("Type mismatch: {}", ex.getMessage());
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;ErrorResponse body = new ErrorResponse(OffsetDateTime.now(), HttpStatus.BAD_REQUEST.value(),
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;HttpStatus.BAD_REQUEST.getReasonPhrase(), ex.getMessage(), req.getRequestURI());
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;return ResponseEntity.badRequest().body(body);
&nbsp;&nbsp;&nbsp;&nbsp;}
&nbsp;&nbsp;&nbsp;&nbsp;// No handler found (404 for unmatched endpoints)
&nbsp;&nbsp;&nbsp;&nbsp;@ExceptionHandler(NoHandlerFoundException.class)
&nbsp;&nbsp;&nbsp;&nbsp;public ResponseEntity<ErrorResponse> handleNoHandler(NoHandlerFoundException ex, HttpServletRequest req) {
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;log.debug("NoHandlerFound: {} {}", ex.getHttpMethod(), ex.getRequestURL());
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;ErrorResponse body = new ErrorResponse(OffsetDateTime.now(), HttpStatus.NOT_FOUND.value(),
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;HttpStatus.NOT_FOUND.getReasonPhrase(), "Endpoint not found", req.getRequestURL().toString());
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;return ResponseEntity.status(HttpStatus.NOT_FOUND).body(body);
&nbsp;&nbsp;&nbsp;&nbsp;}
&nbsp;&nbsp;&nbsp;&nbsp;// Generic fallback
&nbsp;&nbsp;&nbsp;&nbsp;@ExceptionHandler(Exception.class)
&nbsp;&nbsp;&nbsp;&nbsp;public ResponseEntity<ErrorResponse> handleAll(Exception ex, HttpServletRequest req) {
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;log.error("Unhandled exception: ", ex);
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;ErrorResponse body = new ErrorResponse(OffsetDateTime.now(), HttpStatus.INTERNAL_SERVER_ERROR.value(),
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;HttpStatus.INTERNAL_SERVER_ERROR.getReasonPhrase(), "An internal error occurred", req.getRequestURI());
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(body);
&nbsp;&nbsp;&nbsp;&nbsp;}
}

Why:

  • @ControllerAdvice → Makes it global across all controllers.
  • @ExceptionHandler → Catches specific exception types.
  • The buildResponse() helper keeps the code DRY and clean.

Step 7: Test Scenarios

GET /api/users/1

Response code: 200 -Success

** **

GET /api/users/999

Response code: 404-Resource Not Found

** Postman Response**

Validation Error

POST /api/users

{“name”:””, “age”: 15}

Response:

** Postman Response**

Unexpected Error

GET /api/users/boom

** Postman Response**

Why This Approach Works in Real Projects

| Problem | Solution |
|:—:|:—:|
| Too many try-catch blocks | Centralized handling with @ControllerAdvice |
| Inconsistent responses | Unified ErrorResponse structure |
| Hard to debug | Standardized messages with timestamps and paths |
| Client confusion | Clear, meaningful messages for each failure type |

Real-World Usage Scenarios

  • Banking APIs: Ensure validation errors (like invalid account number) don’t crash the service.
  • E-commerce: Handle product-not-found or payment errors gracefully.
  • Data Microservices: Return structured messages when input data fails validation.
  • API Gateway: Consistent responses across multiple microservices.

Final Thoughts

By combining @ControllerAdvice, @ExceptionHandler, and a simple Error Response model, you get:

  • Clean code
  • Consistent API experience
  • Easier debugging in production

It’s one of the simplest yet most powerful design patterns in Spring Boot development.

Sign Up For Daily Newsletter

Be keep up! Get the latest breaking news delivered straight to your inbox.
By signing up, you agree to our Terms of Use and acknowledge the data practices in our Privacy Policy. You may unsubscribe at any time.
Share This Article
Facebook Twitter Email Print
Share
What do you think?
Love0
Sad0
Happy0
Sleepy0
Angry0
Dead0
Wink0
Previous Article Subscribe Today for a Lifetime Subscription to iPhone Life Insider Subscribe Today for a Lifetime Subscription to iPhone Life Insider
Next Article This 5 Bundle Includes a Dell Latitude and a Microsoft Office Pro 2021 License This $275 Bundle Includes a Dell Latitude and a Microsoft Office Pro 2021 License
Leave a comment

Leave a Reply Cancel reply

Your email address will not be published. Required fields are marked *

Stay Connected

248.1k Like
69.1k Follow
134k Pin
54.3k Follow

Latest News

After 8 Years of Testing Monitors, a Discount Like This Still Excites Me: A Near-Perfect Asus QD-OLED Is 0 Off
After 8 Years of Testing Monitors, a Discount Like This Still Excites Me: A Near-Perfect Asus QD-OLED Is $400 Off
News
Ego-Driven Design: How To Introduce Existential Crisis In Personality-based Agents | HackerNoon
Ego-Driven Design: How To Introduce Existential Crisis In Personality-based Agents | HackerNoon
Computing
GPUs Trade Complexity for Massive Parallelism: What Every Machine Learning Engineer Should Know | HackerNoon
GPUs Trade Complexity for Massive Parallelism: What Every Machine Learning Engineer Should Know | HackerNoon
Computing
Amazon Black Friday 2025: The sale is officially live, and we handpicked 150+ of the best deals
Amazon Black Friday 2025: The sale is officially live, and we handpicked 150+ of the best deals
News

You Might also Like

Ego-Driven Design: How To Introduce Existential Crisis In Personality-based Agents | HackerNoon
Computing

Ego-Driven Design: How To Introduce Existential Crisis In Personality-based Agents | HackerNoon

10 Min Read
GPUs Trade Complexity for Massive Parallelism: What Every Machine Learning Engineer Should Know | HackerNoon
Computing

GPUs Trade Complexity for Massive Parallelism: What Every Machine Learning Engineer Should Know | HackerNoon

11 Min Read
Building Scalable SaaS: My Real-World Journey Using spatie/laravel-multitenancy for Multi-Tenant Arc | HackerNoon
Computing

Building Scalable SaaS: My Real-World Journey Using spatie/laravel-multitenancy for Multi-Tenant Arc | HackerNoon

5 Min Read
Visa brings stablecoin settlements to Africa through Aquanow deal
Computing

Visa brings stablecoin settlements to Africa through Aquanow deal

3 Min Read
//

World of Software is your one-stop website for the latest tech news and updates, follow us now to get the news that matters to you.

Quick Link

  • Privacy Policy
  • Terms of use
  • Advertise
  • Contact

Topics

  • Computing
  • Software
  • Press Release
  • Trending

Sign Up for Our Newsletter

Subscribe to our newsletter to get our newest articles instantly!

World of SoftwareWorld of Software
Follow US
Copyright © All Rights Reserved. World of Software.
Welcome Back!

Sign in to your account

Lost your password?