Spring Boot Exception Handling Annotations
Spring Boot gives you a small set of annotations for turning exceptions into clean HTTP responses. Each one has a clear job: one catches an exception, one makes handlers global, one sets the status code, and one decides how the response body is written.
This page is an annotation-by-annotation reference: what each annotation does, its attributes, and how they work together. For the overall strategy, including error formats, logging and testing, see the companion guide Spring Boot Exception Handling.
1. The Annotations at a Glance
| Annotation | Placed on | Purpose |
|---|---|---|
@ExceptionHandler | A method | Handles one or more exception types and builds the response |
@ControllerAdvice | A class | Makes its handler methods apply to many controllers |
@RestControllerAdvice | A class | @ControllerAdvice + @ResponseBody, for REST APIs |
@ResponseStatus | An exception class or a method | Sets the HTTP status code of the response |
@ResponseBody | A method or a class | Writes the return value as the response body, usually JSON |
@Order | An advice class | Decides which advice class is checked first |
All of them except @Order, which comes from Spring’s core module, live in Spring’s web packages. They work in both Spring MVC and Spring WebFlux applications.
2. The Example Application
The examples on this page use a small course-enrollment API. Students enroll in courses, and a few things can go wrong:
public class CourseNotFoundException extends RuntimeException {
public CourseNotFoundException(String courseCode) {
super("Course " + courseCode + " does not exist");
}
}
public class CourseFullException extends RuntimeException {
public CourseFullException(String courseCode, int capacity) {
super("Course " + courseCode + " is full (" + capacity + " seats)");
}
}
@RestController
@RequestMapping("/api/enrollments")
public class EnrollmentController {
private final EnrollmentService enrollmentService;
public EnrollmentController(EnrollmentService enrollmentService) {
this.enrollmentService = enrollmentService;
}
@PostMapping
@ResponseStatus(HttpStatus.CREATED)
public Enrollment enroll(@RequestBody EnrollmentRequest request) {
// may throw CourseNotFoundException or CourseFullException
return enrollmentService.enroll(request.studentId(), request.courseCode());
}
}
3. @ExceptionHandler
@ExceptionHandler marks a method that runs when a matching exception is thrown. Placed inside a controller, it handles exceptions from that controller only:
@RestController
@RequestMapping("/api/enrollments")
public class EnrollmentController {
// ... endpoints as above ...
@ExceptionHandler(CourseFullException.class)
public ResponseEntity<String> handleCourseFull(CourseFullException ex) {
return ResponseEntity.status(HttpStatus.CONFLICT).body(ex.getMessage());
}
}
3.1 Handling several exception types
List several classes in the annotation. The parameter must be a type they all share:
@ExceptionHandler({CourseNotFoundException.class, StudentNotFoundException.class})
public ResponseEntity<String> handleNotFound(RuntimeException ex) {
return ResponseEntity.status(HttpStatus.NOT_FOUND).body(ex.getMessage());
}
3.2 Letting Spring infer the type
If the annotation has no value, Spring uses the type of the exception parameter:
@ExceptionHandler // handles CourseFullException, taken from the parameter
public ResponseEntity<String> handleCourseFull(CourseFullException ex) {
return ResponseEntity.status(HttpStatus.CONFLICT).body(ex.getMessage());
}
| Attribute | Meaning |
|---|---|
value | The exception classes to handle; optional when the parameter type is enough |
produces | Media types the handler can produce, for choosing between handlers by Accept header (Spring Framework 6.2+) |
4. What a Handler Method Can Receive and Return
Handler methods are flexible. Spring supplies any of these parameters automatically, in any order:
| Parameter type | What you get |
|---|---|
| The exception (or a supertype) | The exception that was thrown |
HttpServletRequest / WebRequest | Details of the current request, such as the path |
HttpServletResponse | Direct access to the response, for example to add headers |
Locale | The client’s language, for translated messages |
HttpMethod | GET, POST and so on |
Principal | The logged-in user, if any |
And it can return any of these:
| Return type | Result |
|---|---|
ResponseEntity<T> | Full control over status, headers and body |
ProblemDetail | A standard RFC 9457 error body; the status is taken from the object |
Any object + @ResponseBody | The object is written as JSON with status 200, unless @ResponseStatus sets another |
String (without @ResponseBody) | A view name, for HTML error pages |
void | An empty body; combine with @ResponseStatus or write to HttpServletResponse yourself |
Here is a handler that uses several parameters at once:
@ExceptionHandler
public ResponseEntity<ProblemDetail> handleCourseFull(CourseFullException ex,
HttpServletRequest request,
Locale locale) {
ProblemDetail problem = ProblemDetail.forStatusAndDetail(HttpStatus.CONFLICT, ex.getMessage());
problem.setInstance(URI.create(request.getRequestURI()));
problem.setProperty("locale", locale.toLanguageTag());
return ResponseEntity.status(HttpStatus.CONFLICT)
.header("X-Waitlist-Available", "true")
.body(problem);
}
5. How Spring Chooses a Handler
When an exception is thrown, Spring looks for a handler in this order:
- The controller that threw it. A local
@ExceptionHandleralways wins over global ones. - Advice classes, in their
@Order, checking only those whose scope includes this controller. - Built-in resolvers, which honour
@ResponseStatuson the exception class and Spring’s own exception types. - The
/errorendpoint, if nothing else handled it.
Within one class, the closest matching type wins. Given these two handlers, a CourseFullException goes to the first one, and any other RuntimeException goes to the second:
@ExceptionHandler(CourseFullException.class) // more specific: chosen for CourseFullException
public ResponseEntity<String> handleCourseFull(CourseFullException ex) { ... }
@ExceptionHandler(RuntimeException.class) // less specific: everything else
public ResponseEntity<String> handleOther(RuntimeException ex) { ... }
Spring also checks the exception’s causes. If a CourseFullException is wrapped inside another exception, the handler for CourseFullException can still match it.
6. @ControllerAdvice
@ControllerAdvice turns a class into a shared helper for controllers. Its @ExceptionHandler methods then apply to every controller, so you write each handler once:
@ControllerAdvice
public class EnrollmentExceptionHandler {
@ExceptionHandler
@ResponseBody
public ResponseEntity<String> handleNotFound(CourseNotFoundException ex) {
return ResponseEntity.status(HttpStatus.NOT_FOUND).body(ex.getMessage());
}
@ExceptionHandler
@ResponseBody
public ResponseEntity<String> handleCourseFull(CourseFullException ex) {
return ResponseEntity.status(HttpStatus.CONFLICT).body(ex.getMessage());
}
}
Use its attributes to limit which controllers it applies to:
| Attribute | Applies the advice to | Example |
|---|---|---|
basePackages | Controllers in these packages | basePackages = "com.javabytechie.school.api" |
basePackageClasses | Controllers in the packages of these classes (safe when packages are renamed) | basePackageClasses = EnrollmentController.class |
assignableTypes | These controller classes and their subclasses | assignableTypes = {EnrollmentController.class} |
annotations | Controllers carrying this annotation | annotations = RestController.class |
Advice classes can also hold @InitBinder and @ModelAttribute methods, which are shared across controllers in the same way.
7. @RestControllerAdvice
Writing @ResponseBody on every handler gets repetitive. @RestControllerAdvice combines @ControllerAdvice and @ResponseBody, so every handler writes its return value as the body:
@RestControllerAdvice
public class EnrollmentExceptionHandler {
@ExceptionHandler
public ProblemDetail handleNotFound(CourseNotFoundException ex) {
return ProblemDetail.forStatusAndDetail(HttpStatus.NOT_FOUND, ex.getMessage());
}
@ExceptionHandler
public ProblemDetail handleCourseFull(CourseFullException ex) {
ProblemDetail problem = ProblemDetail.forStatusAndDetail(HttpStatus.CONFLICT, ex.getMessage());
problem.setTitle("Course full");
return problem;
}
}
This is the usual choice for REST APIs. It relates to @ControllerAdvice exactly as @RestController relates to @Controller.
8. @ResponseStatus on Exception Classes
Put @ResponseStatus on an exception class to give it a fixed HTTP status. Whenever that exception escapes a controller and no @ExceptionHandler catches it, Spring uses that status:
@ResponseStatus(HttpStatus.FORBIDDEN)
public class EnrollmentClosedException extends RuntimeException {
public EnrollmentClosedException(String term) {
super("Enrollment for " + term + " has closed");
}
}
No handler is needed. A client that enrolls too late receives 403 from Spring Boot’s standard error response:
{
"timestamp": "2026-10-04T09:30:00.000+00:00",
"status": 403,
"error": "Forbidden",
"path": "/api/enrollments"
}
| Attribute | Meaning |
|---|---|
value / code | The HTTP status; the two are aliases, so use either one |
reason | A fixed reason text; Spring then sends the error with sendError |
The exception message is hidden by default, because Spring Boot does not include messages in error responses unless server.error.include-message=always is set. Use this annotation for simple cases. When you need a custom body, write an @ExceptionHandler instead.
9. @ResponseStatus on Handler Methods
On a handler method, @ResponseStatus sets the status of the response the method returns. It is useful when the handler returns a plain object rather than a ResponseEntity:
public record ErrorMessage(String code, String message) {
}
@RestControllerAdvice
public class EnrollmentExceptionHandler {
@ExceptionHandler
@ResponseStatus(HttpStatus.CONFLICT)
public ErrorMessage handleCourseFull(CourseFullException ex) {
return new ErrorMessage("COURSE_FULL", ex.getMessage());
}
}
The same annotation also works on normal controller methods, such as @ResponseStatus(HttpStatus.CREATED) on the enroll() method in section 2.
10. @ResponseBody
@ResponseBody tells Spring to convert a method’s return value into the response body, using JSON by default, instead of treating it as the name of an HTML template.
| Where it comes from | Effect |
|---|---|
@ResponseBody on a handler method | That method’s result becomes the body |
@ResponseBody on the advice class | Every handler in the class writes a body |
@RestControllerAdvice / @RestController | Already include @ResponseBody |
Returning ResponseEntity or ProblemDetail | Always written as the body, even without @ResponseBody |
In an application that mixes HTML pages and a REST API, a plain @ControllerAdvice can serve both: handlers without @ResponseBody return error page names, and handlers with it return JSON.
@ControllerAdvice
public class MixedExceptionHandler {
// For the HTML website: shows templates/error/course-not-found.html
@ExceptionHandler
public String handlePageNotFound(CourseNotFoundException ex, Model model) {
model.addAttribute("message", ex.getMessage());
return "error/course-not-found";
}
// For the REST API
@ExceptionHandler
@ResponseBody
public ResponseEntity<ErrorMessage> handleCourseFull(CourseFullException ex) {
return ResponseEntity.status(HttpStatus.CONFLICT)
.body(new ErrorMessage("COURSE_FULL", ex.getMessage()));
}
}
11. @Order for Multiple Advice Classes
With more than one advice class, add @Order so Spring checks them in a predictable order. A lower number means a higher priority:
@RestControllerAdvice(basePackageClasses = EnrollmentController.class)
@Order(1)
public class EnrollmentExceptionHandler {
// specific handlers for the enrollment API
}
@RestControllerAdvice
@Order(Ordered.LOWEST_PRECEDENCE)
public class FallbackExceptionHandler {
@ExceptionHandler
@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
public ErrorMessage handleAnything(Exception ex) {
return new ErrorMessage("INTERNAL_ERROR", "An unexpected error occurred.");
}
}
Spring uses the first advice, in order, that has a handler for the exception. Keep the broadest handlers in the class with the lowest priority.
12. Putting It All Together
This complete example combines every annotation on the page:
package com.javabytechie.school.api.error;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.http.HttpStatus;
import org.springframework.http.ProblemDetail;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.bind.annotation.RestControllerAdvice;
@RestControllerAdvice(annotations = RestController.class) // REST controllers only
@Order(Ordered.HIGHEST_PRECEDENCE) // checked before other advice
public class SchoolApiExceptionHandler {
@ExceptionHandler({CourseNotFoundException.class, StudentNotFoundException.class})
public ProblemDetail handleNotFound(RuntimeException ex) {
return ProblemDetail.forStatusAndDetail(HttpStatus.NOT_FOUND, ex.getMessage());
}
@ExceptionHandler
public ProblemDetail handleCourseFull(CourseFullException ex) {
ProblemDetail problem = ProblemDetail.forStatusAndDetail(HttpStatus.CONFLICT, ex.getMessage());
problem.setTitle("Course full");
problem.setProperty("code", "COURSE_FULL");
return problem;
}
@ExceptionHandler
@ResponseStatus(HttpStatus.UNPROCESSABLE_ENTITY)
public ErrorMessage handlePrerequisite(PrerequisiteMissingException ex) {
return new ErrorMessage("PREREQUISITE_MISSING", ex.getMessage());
}
}
Meanwhile, EnrollmentClosedException still needs no handler at all, because its class carries @ResponseStatus(HttpStatus.FORBIDDEN).
13. Which Annotation Should You Use?
| You want to... | Use |
|---|---|
| Give an exception a fixed status, with no custom body | @ResponseStatus on the exception class |
| Handle an exception that only one controller throws | @ExceptionHandler inside that controller |
| Handle exceptions the same way across a REST API | @RestControllerAdvice with @ExceptionHandler methods |
| Mix HTML error pages and JSON errors | @ControllerAdvice, adding @ResponseBody to the JSON handlers |
| Return a plain object with a non-200 status | @ResponseStatus on the handler method |
| Control which of several advice classes runs first | @Order |
14. Common Mistakes to Avoid
| Mistake | What goes wrong | Fix |
|---|---|---|
Using @ControllerAdvice without @ResponseBody in a REST API | Spring treats the returned object or string as a view name | Use @RestControllerAdvice |
| Handler parameter type does not fit all listed exceptions | The handler itself fails with an error instead of producing a response | Use a common supertype as the parameter |
Mapping NullPointerException to 400 | A server bug looks like the client’s fault | Let unexpected errors become 500 |
Expecting @ResponseStatus(reason = ...) to return your own JSON | Spring sends a standard error response instead | Write an @ExceptionHandler for a custom body |
Two advice classes without @Order | Which handler runs may change between builds | Add @Order |
| Expecting a handler in one controller to catch another controller’s exceptions | It never runs | Move it to an advice class |
15. Summary
@ExceptionHandlerturns a matching exception into a response; inside a controller it is local, inside an advice class it is shared.@ControllerAdviceshares handlers across controllers, and its attributes limit where it applies.@RestControllerAdviceis the same, plus@ResponseBody; it is the usual choice for REST APIs.@ResponseStatussets a status on an exception class or a method.@ResponseBodywrites the return value as the body instead of choosing a view.@Ordermakes the order of several advice classes predictable.
Used together, they keep controllers free of try/catch blocks and give clients clear, consistent error responses. Continue with the Spring Boot Exception Handling guide for error formats, logging and testing.
16. Frequently Asked Questions (FAQ)
Q1. Why does my @ExceptionHandler return status 200 even though it handles an error?
If a handler returns a plain object, such as a Map or a record, Spring has no status to use, so it defaults to 200 OK. The client then believes the request succeeded.
Set the status in one of three ways:
// 1. @ResponseStatus on the method
@ExceptionHandler
@ResponseStatus(HttpStatus.CONFLICT)
public ErrorMessage handle(CourseFullException ex) { ... }
// 2. ResponseEntity
@ExceptionHandler
public ResponseEntity<ErrorMessage> handle(CourseFullException ex) {
return ResponseEntity.status(HttpStatus.CONFLICT).body(...);
}
// 3. ProblemDetail, which carries its own status
@ExceptionHandler
public ProblemDetail handle(CourseFullException ex) {
return ProblemDetail.forStatusAndDetail(HttpStatus.CONFLICT, ex.getMessage());
}
Q2. Why does an @ExceptionHandler in one controller not catch exceptions from another controller?
Because a handler inside a controller is local: it only sees exceptions thrown by that controller’s own methods. This is by design, so one controller can treat an error differently from the rest of the application.
To share a handler, move it into a class annotated with @ControllerAdvice or @RestControllerAdvice.
Q3. If a controller and an advice class both handle the same exception, which one runs?
The controller’s own handler always wins, even if the advice has a more specific match. Spring looks inside the controller first and only consults advice classes when the controller has no matching handler.
This lets you override the global behavior for one controller, for example returning a friendlier message for a public endpoint, without changing the shared advice.
Q4. Why does @ResponseStatus with a reason ignore my exception message?
When reason is set, Spring calls response.sendError(status, reason). The request then goes to Spring Boot’s standard error handling, and your exception message is not used:
@ResponseStatus(value = HttpStatus.NOT_FOUND, reason = "Course not found")
public class CourseNotFoundException extends RuntimeException { ... }
- The
reasontext is fixed, so it cannot include details such as the course code. - Even the reason is hidden from the JSON response unless
server.error.include-message=alwaysis set.
For dynamic messages, leave out reason and handle the exception with an @ExceptionHandler instead.
Q5. What is the difference between value and code in @ResponseStatus?
None. They are aliases for the same setting, so these two lines mean exactly the same:
@ResponseStatus(HttpStatus.NOT_FOUND)
@ResponseStatus(code = HttpStatus.NOT_FOUND)
value lets you write the shorter form when the status is the only attribute. Use code when you also set reason, because it reads more clearly. Setting both to different values causes an error.
Q6. Do subclasses inherit @ResponseStatus from their parent exception?
Yes. Spring searches the class hierarchy for the annotation, so a subclass gets its parent’s status unless it declares its own:
@ResponseStatus(HttpStatus.NOT_FOUND)
public abstract class SchoolResourceNotFoundException extends RuntimeException { ... }
public class CourseNotFoundException extends SchoolResourceNotFoundException { ... } // 404
public class StudentNotFoundException extends SchoolResourceNotFoundException { ... } // 404
This is a tidy way to give a whole family of exceptions the same status code.
Q7. If an exception has @ResponseStatus and also an @ExceptionHandler, which applies?
The @ExceptionHandler wins. Handlers are checked before the resolver that reads @ResponseStatus on exception classes, so the annotation on the class is ignored for that exception.
The response status then comes only from the handler. If the handler returns a plain object without its own @ResponseStatus, the result is 200, not the status written on the exception class.
Q8. Does the order of @ExceptionHandler methods inside a class matter?
No. Spring does not read handlers from top to bottom. It always picks the handler whose exception type is the closest match, wherever the method appears in the class.
What you must avoid is mapping the same exception type in two methods of one class. Spring cannot choose between them and fails with an “Ambiguous @ExceptionHandler method” error:
@ExceptionHandler(CourseFullException.class)
public ProblemDetail first(CourseFullException ex) { ... }
@ExceptionHandler(CourseFullException.class) // error: same type mapped twice
public ProblemDetail second(CourseFullException ex) { ... }
Q9. Can an @ExceptionHandler match an exception that is wrapped inside another one?
Yes. Since Spring Framework 5.3, Spring checks the exception and then each of its causes, however deeply nested:
// Somewhere deep in the code:
throw new EnrollmentFailedException("Could not enroll", new CourseFullException("JAVA101", 30));
// This handler still matches, through the cause:
@ExceptionHandler
public ProblemDetail handleCourseFull(CourseFullException ex) { ... }
A handler for the outer exception type is still preferred if one exists. The cause is only checked when nothing matches the outer exception directly.
Q10. Should I add @Component to my @ControllerAdvice class?
No. @ControllerAdvice and @RestControllerAdvice already include @Component, so component scanning picks the class up automatically. Adding @Component again does no harm, but it adds nothing.
What matters is that the class is inside a scanned package, usually the package of your @SpringBootApplication class or below it.
Q11. Can I put @ExceptionHandler methods in a @Service class?
No. @ExceptionHandler only works in controllers and advice classes, because it is part of the web layer. In a service, the annotation is simply ignored.
Services should throw meaningful exceptions; the web layer decides how they become HTTP responses. This keeps services free of HTTP details, so the same service can be reused by a scheduled job or a message listener.
Q12. Can @ControllerAdvice be used for things other than exceptions?
Yes. It can also hold @InitBinder and @ModelAttribute methods that apply to many controllers:
@ControllerAdvice
public class WebDefaults {
// Trim whitespace from all incoming form fields
@InitBinder
public void trimStrings(WebDataBinder binder) {
binder.registerCustomEditor(String.class, new StringTrimmerEditor(true));
}
// Make the current term available to every HTML view
@ModelAttribute("currentTerm")
public String currentTerm() {
return "Autumn 2026";
}
}
Many teams keep these in a separate class from the exception handlers, so each advice class has one clear job.
Q13. Does @RestControllerAdvice also handle exceptions from @Controller classes that return HTML?
Yes, by default. It applies to every controller, so an HTML page controller would suddenly return JSON errors, which browsers display as raw text.
In applications with both kinds of controllers, limit the REST advice to REST controllers:
@RestControllerAdvice(annotations = RestController.class)
public class ApiExceptionHandler { ... }
Page controllers then fall back to HTML error pages, while API controllers get JSON.
Q14. How can I add headers to an error response?
Return a ResponseEntity, which lets you set the status, headers and body together:
@ExceptionHandler
public ResponseEntity<ProblemDetail> handleCourseFull(CourseFullException ex) {
ProblemDetail problem = ProblemDetail.forStatusAndDetail(HttpStatus.CONFLICT, ex.getMessage());
return ResponseEntity.status(HttpStatus.CONFLICT)
.header("X-Waitlist-Url", "/api/waitlist")
.body(problem);
}
Alternatively, add an HttpServletResponse parameter and call response.setHeader(...), which works with any return type.
Q15. Can an @ExceptionHandler method return void?
Yes. Combine it with @ResponseStatus to send a status with an empty body:
@ExceptionHandler
@ResponseStatus(HttpStatus.NO_CONTENT)
public void handleAlreadyEnrolled(AlreadyEnrolledException ex) {
// nothing to return: the student is already enrolled, so there is no change
}
This fits cases where the status alone says everything. If you need a body, return an object instead.
Q16. When should I use ResponseStatusException instead of @ResponseStatus?
@ResponseStatus on a class | ResponseStatusException | |
|---|---|---|
| New exception class needed | Yes | No |
| Status chosen | Fixed, when you write the class | At the moment you throw it |
| Message | Fixed reason, or hidden | Can include details, such as an ID |
throw new ResponseStatusException(HttpStatus.NOT_FOUND, "Course " + code + " does not exist");
Use ResponseStatusException for quick, one-off errors in a controller. For errors that appear in many places, a named exception class is clearer and easier to search for.
Q17. Can I return JSON or XML from a handler depending on what the client asks for?
Yes. If the handler returns an object or ResponseEntity, Spring picks the format from the client’s Accept header automatically, as long as a converter for that format is available, for example jackson-dataformat-xml for XML.
Since Spring Framework 6.2, you can also write separate handlers per format with the produces attribute:
@ExceptionHandler(produces = "application/json")
public ResponseEntity<ErrorMessage> handleJson(CourseFullException ex) { ... }
@ExceptionHandler(produces = "text/html")
public String handleHtml(CourseFullException ex, Model model) {
model.addAttribute("message", ex.getMessage());
return "error/course-full";
}
Q18. How do I unit test an advice class without starting the whole application?
Use MockMvc in standalone mode. You pass in the controller and the advice yourself, and no Spring context is started, so the test runs in milliseconds:
class EnrollmentExceptionHandlerTest {
private final EnrollmentService service = mock(EnrollmentService.class);
private final MockMvc mockMvc = MockMvcBuilders
.standaloneSetup(new EnrollmentController(service))
.setControllerAdvice(new EnrollmentExceptionHandler())
.build();
@Test
void returns409WhenCourseIsFull() throws Exception {
when(service.enroll(7L, "JAVA101")).thenThrow(new CourseFullException("JAVA101", 30));
mockMvc.perform(post("/api/enrollments")
.contentType(MediaType.APPLICATION_JSON)
.content("{\"studentId\": 7, \"courseCode\": \"JAVA101\"}"))
.andExpect(status().isConflict());
}
}
Q19. Do these annotations work in Spring WebFlux?
Yes. @ExceptionHandler, @ControllerAdvice, @RestControllerAdvice, @ResponseStatus and @ResponseBody are the same annotations, from the same packages, in both Spring MVC and WebFlux.
The differences are in the handler parameters and return types. Use ServerWebExchange instead of servlet types, and you may return Mono<ResponseEntity<T>>:
@ExceptionHandler
public Mono<ResponseEntity<ErrorMessage>> handleCourseFull(CourseFullException ex) {
return Mono.just(ResponseEntity.status(HttpStatus.CONFLICT)
.body(new ErrorMessage("COURSE_FULL", ex.getMessage())));
}
Q20. Is it safe to use @ExceptionHandler(Exception.class) inside a single controller?
It is legal, but usually a poor idea. It catches every error from that controller, including Spring’s own exceptions for bad JSON or missing parameters, and replaces their correct 400 responses with whatever your handler returns.
Prefer handling specific exception types in controllers, and keep one carefully written catch-all in a global advice class with the lowest priority. That way, all controllers share one well-tested fallback instead of each having its own.