Spring Boot handle SizeLimitExceededException - java

I use Spring boot 1.5.7.
I have not configured CommonsMultipartResolver, because Spring Boot handles file uploads already.
If my upload exceeds the maximum permited size, an ugly Exception is thrown.
This is handled by my controller.
#ControllerAdvice
public abstract class DefaultController implements InitializingBean {
#ExceptionHandler(Exception.class)
public ResponseEntity<ServiceException> handleException(final Exception ex) {
...
} else if (ex instanceof MultipartException) {
MultipartException me = (MultipartException) ex;
Throwable cause = ex.getCause();
if (cause instanceof IllegalStateException) {
Throwable cause2 = cause.getCause();
if (cause2 instanceof SizeLimitExceededException) {
// this is tomcat specific
SizeLimitExceededException slee = (SizeLimitExceededException) cause2;
}
}
}
This kind of handling is not only complex but also sadly Tomcat specific, because the SizeLimitExceededException is in the package org.apache.tomcat.util.http.fileupload.FileUploadBase.
How can I handle the error case, that some one uploads a bigger file then allowed and return a nice message, regardless which Servlet Engine is used?

You could define a exception handler method in your #ControllerAdvice which is specifically for MultipartException and then qualify it with a specific HttpStatus. For example:
#ExceptionHandler(MultipartException.class)
#ResponseStatus(value = HttpStatus.PAYLOAD_TOO_LARGE)
public ResponseEntity<ServiceException> handleMultipartException(MultipartException ex) {
...
}
This should allow you to focus on the 'maximum file size' exception without having to get into servlet container specifics.
Update 1 in response to this comment:
Sounds good, what about getPermittedSize and getActualSize provided by SizeLimitExceededException is there a chance to get this values not only if Tomcat is used?
By intercepting this error on the basis of (a) the exception type and (b) the HTTP status ... you are making the solution generally applicable. But in so doing you might lose the sort of detailed information that one servlet container (but perhaps not another) might give you. You could perhaps enforce your own maximum size by setting spring.http.multipart.max-file-size in which case you would be able to report 'permitted size' but if you want to report 'actual size' then you'll have to consider one of the following:
Have to use something provided by the servlet container
Choose a spring.http.multipart.max-file-size which is less thatn the supported maximum for your servlet container and then apply your own max size check inside your controller and throw your own specific exception type containing the actual and permitted sizes.

This working fine in my #RestControllerAdvice and the exception is more specific than MultipartException :
#ExceptionHandler(MaxUploadSizeExceededException.class)
#ResponseStatus(value = HttpStatus.PAYLOAD_TOO_LARGE)
public ResponseEntity handleMultipartException(MaxUploadSizeExceededException e) {
Map<String, String> result = new HashMap<>();
result.put("message", "Error ==> Large File ");
return ResponseEntity.status(HttpStatus.PAYLOAD_TOO_LARGE)
.body(result);
}
PS : when using #RestControllerAdvice(annotations = RestController.class)
it doens't work .. you have to put #RestControllerAdvice without RestController.class annotation

Related

Spring Boot, AOP and exception handler

Context
What I'm satisfied with
I'm creating a Spring Boot REST service returning a ResponseEntity<StreamingResponseBody>. I also have a #ControllerAdvice for exception handling. It all works fine.
I've been asked to provide logging (endpoints being called, execution time, HTTP status and such). The most solid way I can think of to do it was to use an Aspect. I made one with some #Around pointcuts, like below, and that, too, works fine in the nominal case.
#Around(value = "#annotation(requestMapping)", argNames = "pjp,requestMapping")
public Object trackExecutionTime(ProceedingJoinPoint pjp, RequestMapping requestMapping) throws Throwable {
return trackExecutionTime(pjp, requestMapping.method()[0], requestMapping.value()[0]);
}
What's brittle or broken
My problem now occurs in case of an exception. I couldn't find a better way than injecting my exception handler into my Aspect. It's dirty, brittle, and even completely broken in my case:
// Simplified
private Object trackExecutionTime(ProceedingJoinPoint pjp) throws Throwable {
StopWatch stopWatch = new StopWatch();
stopWatch.start();
Object result = null;
try {
result = pjp.proceed();
} catch (MyException1 e) {
result = exceptionHandler.handleMyException1(e);
} catch (Exception e) {
result = exceptionHandler.handleException(e, webRequest);
} finally {
stopWatch.stop();
doLog(result, stopWatch.getTotalTimeMillis(),
((ResponseEntity) result).getStatus());
}
return result;
}
Brittle: new cases in exception handler means new catches to add here. I don't like it.
Broken: Spring isn't happy, at all. He expects a StreamingResponseBody and I return an ErrorDto. The ExceptionHandling is broken and the endpoint returns Spring's generic error + 500 instead of my DTO + appropriate HTTP status.
org.springframework.web.util.NestedServletException: Request processing failed; nested exception is java.lang.IllegalArgumentException: StreamingResponseBody expected: lu.pgd.dri.bidas.nist.rest.dto.NistRestApiErrorDto
at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:1014) ~[spring-webmvc-5.3.24.jar:5.3.24]
at org.springframework.web.servlet.FrameworkServlet.doPost(FrameworkServlet.java:909) ~[spring-webmvc-5.3.24.jar:5.3.24]
at javax.servlet.http.HttpServlet.service(HttpServlet.java:517) ~[jakarta.servlet-api-4.0.4.jar:4.0.4]
...
Caused by: java.lang.IllegalArgumentException: StreamingResponseBody expected: lu.pgd.dri.bidas.nist.rest.dto.NistRestApiErrorDto
at org.springframework.util.Assert.instanceCheckFailed(Assert.java:702) ~[spring-core-5.3.24.jar:5.3.24]
at org.springframework.util.Assert.isInstanceOf(Assert.java:602) ~[spring-core-5.3.24.jar:5.3.24]
at org.springframework.web.servlet.mvc.method.annotation.StreamingResponseBodyReturnValueHandler.handleReturnValue(StreamingResponseBodyReturnValueHandler.java:90) ~[spring-webmvc-5.3.24.jar:5.3.24]
...
I of course did a PoC before starting this aspect, but I used ResponseEntity<String> in both success and failure cases, so I missed this issue.
My questions
Real, primary question: Is there a way to enhance my aspect so that I can achieve my purpose?
Bonus question: Does that enhancement allow me to handle exceptions in a generic way, by throwing it to some kind of Dispatcher in the Spring context, for instance?
As a potential lead, I considered not catching the exception and making the ExceptionHandler call the log service itself, but then I don't know how to get the StopWatch to have the execution time.
Thanks for any insight!

Handle exceptions in a rest controller in Spring Boot

I create REST web-service with Spring Boot.
I would like to know what is a better way to handle exceptions in a controller. I have seen other questions and didn’t found an answer.
My controller:
#GetMapping
public ResponseEntity<?> saveMyUser(){
MyUser myUser = new MyUser(“Anna”);
//throws SQLException
MyUserDetails userDetails = userService.saveMyUser(myUser);
//if successful
return ResponseBody.ok(userDetails);
}
saveMyUser() method of UserService:
public MyUserDetails saveUser(MyUser) throws SQLException {...}
So at this point I have at least 2 simple options:
Add exception to method signature.
Here I may rely on Spring Boot to pass all information about exception and status code to a client. However do not know if it is a reliable approach.
Surround with try/catch and pass all information about exceptions manually.
What is a better simple way?
You can create an additional class with #ControllerAdivce annotation and later you will be able to write custom response logic for each exception e.g:
#ControllerAdvice
public class GlobalExceptionHandler {
#ExceptionHandler({SQLException.class})
public ResponseEntity<Object> sqlError(Exception ex) {
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body("Some SQL exception occured");
}
}
Also, you can extend ResponseEntityExceptionHandler and override the default behavior for mapping from exceptions to HTTP response.
Also, take a look at this, it holds very usefull information for your case.

How could we use #ExceptionHandler with spring web flux?

In spring web we could use annotation #ExceptionHandler for handling server and client errors for controllers.
I've tried to use this annotation with web-flux controller and it still worked for me, but after some investigation I've found out here
The situation with Spring Web Reactive is more complicated. Because
the reactive streams are evaluted by a different thread than the one
that executes the controllers method, the exceptions won’t be
propagated to the controller thread automatically. This means that the
#ExceptionHandler method will work only for exceptions that are thrown
in the thread that handles the request directly. Exceptions thrown in
the stream will have to be propagated back to the thread if we want to
use the #ExceptionHandler feature. This seems like a bit of a let down
but at the time of writing this Spring 5 is still not released so
error handling might still get better.
So my question is how to propagate back exception to the thread. Is there a good example or article about using #ExceptionHandler and Spring web flux?
Updated:
From spring.io it looks like it's supported, but still lack general understanding
Thanks,
Now it is possible to use the #ExceptionHandler as well as #RestControllerAdvice or even #ControllerAdvice in Spring WebFlux.
Example:
Add the webflux dependency
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webflux</artifactId>
</dependency>
Create your class ExceptionHandler
#RestControllerAdvice
public class ExceptionHandlers {
private static final Logger LOGGER = LoggerFactory.getLogger(ExceptionHandlers.class);
#ExceptionHandler(Exception.class)
#ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
public String serverExceptionHandler(Exception ex) {
LOGGER.error(ex.getMessage(), ex);
return ex.getMessage();
}
}
Create a Controller
#GetMapping(value = "/error", produces = MediaType.APPLICATION_JSON_UTF8_VALUE)
public Mono<String> exceptionReturn() {
return Mono.error(new RuntimeException("test error"));
}
Example extracted here:
https://ddcode.net/2019/06/21/spring-5-webflux-exception-handling/
You can use #ExceptionHandler annotated methods to handle errors that happen within the execution of a WebFlux handler (e.g., your controller method). With MVC you can indeed also handle errors happening during the mapping phase, but this is not the case with WebFlux.
Back to your exception propagation question, the article you're sharing is not accurate.
In reactive applications, the request processing can indeed hop from one thread to another at any time, so you can't rely on the "one thread per request" model anymore (think: ThreadLocal).
You don't have to think about exception propagation or how threads are managed, really. For example, the following samples should be equivalent:
#GetMapping("/test")
public Mono<User> showUser() {
throw new IllegalStateException("error message!");
}
#GetMapping("/test")
public Mono<User> showUser() {
return Mono.error(new IllegalStateException("error message!"));
}
Reactor will send those Exceptions as error signals as expected in the Reactive Streams contract (see the "error handling" documentation section for more on that).
not an exact answer to the original question, but a quick way to map your exceptions to http response status is to throw org.springframework.web.server.ResponseStatusException / or create your own subclasses...
Full control over http response status + spring will add a response body with the option to add a reason.
{
"timestamp": 1529138182607,
"path": "/api/notes/f7b.491bc-5c86-4fe6-9ad7-111",
"status": 400,
"error": "Bad Request",
"message": "For input string: \"f7b.491bc\""
}
The following global error handler did the trick for me:
import org.springframework.web.server.ResponseStatusException;
#Slf4j
#RestControllerAdvice
#ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.REACTIVE)
public class MyCustomReactiveErrorHandling {
#ExceptionHandler(MyCustomNotFoundException.class)
public void handleMyCustomException(MyCustomNotFoundException ex) {
throw new ResponseStatusException(404, "Data not found!", ex);
}
}
Throwing my exceptions returns the correct http status code at the rest service.

Spring multipart file upload size

My application has following properties set
spring.http.multipart.max-file-size = 10MB
and
spring.http.multipart.max-request-size = 10MB
But instead of throwing the Springs default exception below
{ "message": "Could not parse multipart servlet request; nested exception is java.lang.IllegalStateException: org.apache.tomcat.util.http.fileupload.FileUploadBase$SizeLimitExceededException: the request was rejected because its size (12780451) exceeds the configured maximum (10485760)", "type": "MultipartException" }
I would like to throw CustomException with message like FILE_SIZE_TOO_BIG. Is this possible?
Can I use #ControllerAdvice for this ?
There're a lot of ways to handle exceptions in Spring application. See exception handling in Spring MVC topic. I think GlobalControllerExceptionHandler taken from the provided article will fit your requirements. Try this handler:
#ControllerAdvice
class GlobalControllerExceptionHandler {
#ResponseStatus(value = HttpStatus.PAYLOAD_TOO_LARGE)
#ExceptionHandler(MultipartException.class)
public void handleMultipart(MultipartException exception) {
// to do
}
}
If you get this class picked up by component scan, it should handle MultipartExceptions thrown from any controller.

Rest Exceptions: Wrappers vs Error Object

Lets say we have a rest service defined as:
#GET
#Produces("application/json")
public Response getAllCategories(#QueryParam(value="startIndex") int startIndex, #QueryParam(value="size") int size)
{
logger.info("[SampleCategoryController][getAllCategories]");
List<YpCategory> categoryList = sampleCategoryService.getAllCategories(startIndex, size);
return Response.ok(categoryList).build();
}
and the service is defined as:
public class SampleCategoriesServiceImpl {
public List<YpCategory> getAllCategories(int startIndex, int size) {
...
//call some method that throws a runtime exception
...
}
}
And an Application Exception handler:
#Provider
#Component
public class ApplicationExceptionHandler implements ExceptionMapper<Throwable> {
#Override
public Response toResponse(Throwable ex) {
String internalError = "There was a problem processing your request.";
return Response.serverError().entity(new ExceptionResponse(500, internalError)).build();
}
}
}
Exception response object: Let the exception bubble up to the ApplicationExceptionHandler and return the ExceptionResponse Object. This way seems cleaner because the service doesn't have to try to handle an exception that it can't really do anything with and the client will still receive a json response.
Response wrapper: The category object would extend some type of generic response wrapper object with information about error codes then I would always have to wrap the method that can throw a runtime exception in a try/catch block and set the error codes and message info in the catch block.
Is one of these ways preferred? Are there cons to using either one of these methods to handle errors?
I think you should use the ExceptionMapper in this case. It is cleaner to let exceptions be handled outside of your implementation.
Also your implementation should be as less possible aware of HTTP. The less your implementation knows about the other parts of your framework the more flexible it will become.
To give an example. Lets say that in the future there is support for a non-HTTP protocol and error messaging will go different then using HTTP status code. You can do the implementation at the level of ExceptionMapper without changing your implementation. Otherwise you have to refactor your application to be aware of the non-HTTP protocol.
Just to be clear, I don't say there is an other implementation available now. It is just a theory.

Categories

Resources