Hello I have a bunch of controllers with several endpoints each that perform HTTP operations. I wanna dive into error handling and more precisely global error handling. For instance, 404 HTTP status code. How can I handle 404s globally so that whenever someone tries to access a non-defined resource he/she receives a response with 404 HTTP status code saying that the resource you are trying to access is not attainable?
#Configure global error handling spring MVC rest api
28 messages · Page 1 of 1 (latest)
⌛ This post has been reserved for your question.
Hey @sacred glen! Please use
/closeor theClose Postbutton above when you're finished. Please remember to follow the help guidelines. This post will be automatically closed after 300 minutes of inactivity.
TIP: Narrow down your issue to simple and precise questions to maximize the chance that others will reply in here.
Thank you so much in advance
So there are a couple approaches you can do in Spring, depending on the version.
You'll want to start with a class (I recommend in the controller OR config Package), annotated with either @ControllerAdvice or @RestControllerAdvice, and in our case also tack on extends ResponseEntityExceptionHandler
The next problem you're going to have is that Spring doesn't really do HTTP Status Based Exception Handling, (Excluding the ResponseStatusException) So we'll want to isolate the Exceptions thrown.
Spring's Built in Function Handler for the NoHandlerFoundException is going to be handleNoHandlerFoundException in the ResponseEntityExceptionHandler. So in the the custom exception handler we'll override the function and insert the custom response you want.
Note that it is possible for other functions/exceptions to cause 404 responses, but those are usually going to be thrown by the Application, which you have control over.
Bear in mind as well that Spring's Default 404 response for a bad path is already pretty good.
{
"timestamp": "2023-03-01T15:52:25.382+00:00",
"status": 404,
"error": "Not Found",
"path": "/rest/myapp/badpath"
}
💤 Post marked as dormant
This post has been inactive for over 300 minutes, thus, it has been archived.
If your question was not answered yet, feel free to re-open this post or create a new one.
Thank you so much for your reply, I really appreciate it
Bear with me cause I'm relatively new to Spring and I'm still getting used to it. Let me paste the code that I have so far based on what you said above.
I have the following package with a couple of java classes in it:
//RestApiExceptionHandler.class
import java.time.ZoneId;
import java.time.ZonedDateTime;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.servlet.NoHandlerFoundException;
import org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler;
@ControllerAdvice
public class RestApiExceptionHandler extends ResponseEntityExceptionHandler {
@ExceptionHandler(value = { NoHandlerFoundException.class })
private ResponseEntity<Object> handleNoHandlerFoundException(NoHandlerFoundException e) {
HttpStatus notFoundRequest = HttpStatus.NOT_FOUND;
RestApiException rApiEx = new RestApiException(e.getMessage(), e, notFoundRequest,
ZonedDateTime.now(ZoneId.of("Europe/Madrid")));
return new ResponseEntity<>(rApiEx, notFoundRequest);
}
}
And then RestApiException which is basically a blueprint on what I want to return. I might remove Throwable attribute later on, but I will put it for now (merely informative).
//RestApiException.class
import java.time.ZonedDateTime;
import org.springframework.http.HttpStatus;
public class RestApiException {
private final String message;
private final Throwable throwable;
private final HttpStatus httpStatus;
private final ZonedDateTime timestamp;
public RestApiException(String message, Throwable throwable, HttpStatus httpStatus, ZonedDateTime timestamp) {
this.message = message;
this.throwable = throwable;
this.httpStatus = httpStatus;
this.timestamp = timestamp;
}
public String getMessage() {
return message;
}
public Throwable getThrowable() {
return throwable;
}
public HttpStatus getHttpStatus() {
return httpStatus;
}
public ZonedDateTime getTimestamp() {
return timestamp;
}
}
By doing this, instead of the default Spring response for 404
It should use the one that I just created right?
Let's say that I wanna create a new Exception based on HTTP 403 status code. Imagine that someone send an HTTP request to my REST API and I have a logic to determine if that request comes from a valid source or not. Whenever the source is not trustworthy, I would throw an Exception saying that source is not valid. I would implement it this way, please let me know if I should do this differently.
I would create a new class within exceptions package like so:
public class UnautorizedOriginException extends RuntimeException {
private static final long serialVersionUID = 1L;
public UnautorizedOriginException(String message, Throwable cause) {
super(message, cause);
// TODO Auto-generated constructor stub
}
public UnautorizedOriginException(String message) {
super(message);
// TODO Auto-generated constructor stub
}
}
Then I would set a new function that handles this so RestApiExceptionHandler.class would look like this:
//RestApiExceptionHandler.class
import java.time.ZoneId;
import java.time.ZonedDateTime;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.servlet.NoHandlerFoundException;
import org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler;
@ControllerAdvice
public class RestApiExceptionHandler extends ResponseEntityExceptionHandler {
@ExceptionHandler(value = { NoHandlerFoundException.class })
private ResponseEntity<Object> handleNoHandlerFoundException(NoHandlerFoundException e) {
HttpStatus notFoundRequest = HttpStatus.NOT_FOUND;
RestApiException rApiEx = new RestApiException(e.getMessage(), e, notFoundRequest,
ZonedDateTime.now(ZoneId.of("Europe/Madrid")));
return new ResponseEntity<>(rApiEx, notFoundRequest);
}
@ExceptionHandler(value = { UnautorizedOriginException.class })
private ResponseEntity<Object> handleUnautorizedOriginException(UnautorizedOriginException e) {
HttpStatus forbiddenRequest = HttpStatus.FORBIDDEN;
RestApiException rApiEx = new RestApiException(e.getMessage(), e, forbiddenRequest,
ZonedDateTime.now(ZoneId.of("Europe/Madrid")));
return new ResponseEntity<>(rApiEx, forbiddenRequest);
}
}
Then in my code, I would throw new UnautorizedOriginException("Unauthorized source");.
Pinging cause it's been a day, just to make easier for you to enter, @misty valve
I'd wonder first if this shouldn't be a filter to register to spring security
I just woke up, I'll get to this in an hour~ @sacred glen
@sacred glen This is perfect. My only comment would be it's not worth it to put the Throwable into the HTTP Response. Your API consumer has no idea what a UnauthorizedOriginException means, instead it's better to have a clear error message in the API Documentation that can show what the error was.
You don't want your filters to override the response/request. They can append, maybe even wrap, but not override. Otherwise you're running a risk for annoying bugs, and it's better to avoid a lot of custom filters.
One is not a lot. The question should be more whether that check is needed for just a few chosen endpoints, or for almost everything
Yeah I agree, actually I deleted one hour ago. As you said, better to hide that from the client
Not in all of them but in plenty of them😅
The comment was targetted more at error handling in the Filter layer.
If it's not almost-all, like "all but the user registering endpoing" or something like that, then a filter is unlikely to be the helpful solution