- Controller doesn’t handle the exception. It just lets it “fly past.”
- Your
GlobalExceptionHandler
is like a safety net that catches it at the very end.
- Your
The Journey of an Unchecked Exception 🚀
Think of it as a ball being thrown upwards:
- Thrown: Your service (e.g.,
toBinaryContent
) throws your customFileAccessException
. - Travels: The exception travels “up” through the call stack. It leaves the service and goes into your controller.
- Ignored by Controller: Your controller code doesn’t have a
try-catch
block for it. It doesn’t know what to do, so it just lets the exception continue flying upwards, out of the controller. - Caught by the Global Handler: Spring sees an unhandled exception has escaped a controller and says, “Whoa, I need to find a handler for this!” It then looks at your
@RestControllerAdvice
(GlobalExceptionHandler
) class. - Handled: Your
GlobalExceptionHandler
looks at the type of exception. It finds the@ExceptionHandler
method that matches and uses it to create a nice JSON error response. The controller’s job is to handle the happy path. TheGlobalExceptionHandler
’s job is to handle the unhappy paths.
The Big Picture
- You use an unchecked exception so it can fly past the controller without being caught.
@Transactional
uses the unchecked exception as a signal to ROLLBACK the database transaction.- The controller ignores the exception because handling system errors isn’t its job.
- The
GlobalExceptionHandler
acts as a final safety net, catches the exception, and provides a clean error response to the client.
과제
@RestControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(NoSuchElementException.class)
public ResponseEntity<ErrorResponse> handleException(NoSuchElementException e) {
ErrorResponse errorResponse = new ErrorResponse(HttpStatus.NOT_FOUND.value(), e.getMessage());
return ResponseEntity
.status(HttpStatus.NOT_FOUND)
.body(errorResponse);
}
@ExceptionHandler(IllegalArgumentException.class)
public ResponseEntity<ErrorResponse> handleException(IllegalArgumentException e) {
ErrorResponse errorResponse = new ErrorResponse(HttpStatus.BAD_REQUEST.value(), e.getMessage());
return ResponseEntity
.status(HttpStatus.BAD_REQUEST)
.body(errorResponse);
}
@ExceptionHandler(Exception.class)
public ResponseEntity<String> handleException(Exception e) {
return ResponseEntity
.status(HttpStatus.INTERNAL_SERVER_ERROR)
.body(e.getMessage());
}
@ExceptionHandler(FileAccessException.class)
public ResponseEntity<String> handleFileAccessException(FileAccessException e) {
return ResponseEntity
.status(e.getStatus())
.body(e.getMessage());
}
}
- Used to return
ResponseEntity<String>
instead ofResponseEntity<ErrorResponse>
- changed in order to return JSON
@ResponseStatus
vs.ResponseEntity
@ResponseStatus
: Simpler and cleaner for cases where the HTTP status is always the same for a given exception.ResponseEntity
: More powerful and flexible. You should use it when you need to dynamically set the status code or add custom headers.
ErrorResponse
@Getter
public class ErrorResponse {
private int status;
private String message;
private long timestamp;
public ErrorResponse(int status, String message) {
this.status = status;
this.message = message;
this.timestamp = System.currentTimeMillis();
}
}