Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to catch FileSizeLimitExceededException in a spring boot multipart controller action in a tomcat container?

When I upload a file of size that exceeds the configured max file size, the response returned is not very pretty or useful to my JS UI. So, I want to catch it and handle it. But, the issue is error is thrown before my controller is entered. Thus, I'm at a loss as to where to place my error handling code. One idea I'm toying with is defining a filter and catching it there. Is that the normal place to do it? The stack trace I'm seeing is:

at org.apache.tomcat.util.http.fileupload.FileUploadBase$FileItemIteratorImpl$FileItemStreamImpl$1.raiseError(FileUploadBase.java:628) ~[tomcat-embed-core-8.5.34.jar!/:8.5.34]
at org.apache.tomcat.util.http.fileupload.util.LimitedInputStream.checkLimit(LimitedInputStream.java:76) ~[tomcat-embed-core-8.5.34.jar!/:8.5.34]
at org.apache.tomcat.util.http.fileupload.util.LimitedInputStream.read(LimitedInputStream.java:135) ~[tomcat-embed-core-8.5.34.jar!/:8.5.34]
at java.io.FilterInputStream.read(FilterInputStream.java:107) ~[na:1.8.0_121]
at org.apache.tomcat.util.http.fileupload.util.Streams.copy(Streams.java:98) ~[tomcat-embed-core-8.5.34.jar!/:8.5.34]
at org.apache.tomcat.util.http.fileupload.util.Streams.copy(Streams.java:68) ~[tomcat-embed-core-8.5.34.jar!/:8.5.34]
at org.apache.tomcat.util.http.fileupload.FileUploadBase.parseRequest(FileUploadBase.java:293) ~[tomcat-embed-core-8.5.34.jar!/:8.5.34]
at org.apache.catalina.connector.Request.parseParts(Request.java:2902) ~[tomcat-embed-core-8.5.34.jar!/:8.5.34]
at org.apache.catalina.connector.Request.parseParameters(Request.java:3242) ~[tomcat-embed-core-8.5.34.jar!/:8.5.34]
at org.apache.catalina.connector.Request.getParameter(Request.java:1136) ~[tomcat-embed-core-8.5.34.jar!/:8.5.34]
at org.apache.catalina.connector.RequestFacade.getParameter(RequestFacade.java:381) ~[tomcat-embed-core-8.5.34.jar!/:8.5.34]
at org.springframework.web.filter.HiddenHttpMethodFilter.doFilterInternal(HiddenHttpMethodFilter.java:84) ~[spring-web-5.0.9.RELEASE.jar!/:5.0.9.RELEASE]
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107) ~[spring-web-5.0.9.RELEASE.jar!/:5.0.9.RELEASE]
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193) ~[tomcat-embed-core-8.5.34.jar!/:8.5.34]
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) ~[tomcat-embed-core-8.5.34.jar!/:8.5.34]
at org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:200) ~[spring-web-5.0.9.RELEASE.jar!/:5.0.9.RELEASE]
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107) ~[spring-web-5.0.9.RELEASE.jar!/:5.0.9.RELEASE]
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193) ~[tomcat-embed-core-8.5.34.jar!/:8.5.34]
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) ~[tomcat-embed-core-8.5.34.jar!/:8.5.34]
at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:198) ~[tomcat-embed-core-8.5.34.jar!/:8.5.34]
at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:96) [tomcat-embed-core-8.5.34.jar!/:8.5.34]
at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:493) [tomcat-embed-core-8.5.34.jar!/:8.5.34]
at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:140) [tomcat-embed-core-8.5.34.jar!/:8.5.34]
at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:81) [tomcat-embed-core-8.5.34.jar!/:8.5.34]
at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:87) [tomcat-embed-core-8.5.34.jar!/:8.5.34]
at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:342) [tomcat-embed-core-8.5.34.jar!/:8.5.34]
at org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:800) [tomcat-embed-core-8.5.34.jar!/:8.5.34]
at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:66) [tomcat-embed-core-8.5.34.jar!/:8.5.34]
at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:806) [tomcat-embed-core-8.5.34.jar!/:8.5.34]
at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1498) [tomcat-embed-core-8.5.34.jar!/:8.5.34]
at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:49) [tomcat-embed-core-8.5.34.jar!/:8.5.34]
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142) [na:1.8.0_121]
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617) [na:1.8.0_121]
at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61) [tomcat-embed-core-8.5.34.jar!/:8.5.34]
at java.lang.Thread.run(Thread.java:745) [na:1.8.0_121]

My controller action looks like this:

@PostMapping("/upload")
@ResponseBody
public String handleFileUpload(@RequestParam("file") MultipartFile file) {
    String fileName = storageService.store(file);
    String fileUrl = "/api/file/" + fileName;

    return "{\"fileUrl\":\"" + fileUrl + "\"}";
}

Though that's not very important because that code is not entered because the exception is thrown int he thread first.

As expected, adding an exception handler to the controller did not catch the exception for the same reason.

@ExceptionHandler(FileUploadBase.FileSizeLimitExceededException.class)
public String handlefileSizeLimitExceeded(FileUploadBase.FileSizeLimitExceededException exc) {
    return "{\"error\":\"file too big\"}";
}

Note: I am not asking how to change the file size max. I already know how to do that. My objective is to report when the user attempts to upload a file of size greater than the maximum.

like image 943
Calicoder Avatar asked Jan 21 '19 08:01

Calicoder


2 Answers

You need to catch MaxUploadSizeExceededException.class

@ExceptionHandler(MaxUploadSizeExceededException.class)
public String handleFileSizeLimitExceeded(MaxUploadSizeExceededException exc) {
    return "{\"error\":\"file too big\"}";
}
like image 89
HoucineKrichen Avatar answered Oct 20 '22 00:10

HoucineKrichen


I had the same problem, the other answer did not work for me so kept looking and found this: https://www.baeldung.com/spring-maxuploadsizeexceeded (as always, excellent guides at baeldung.com, thanks!). Check the Tomcat configuration part, it explains why we keep running into the problem even when using an ExceptionHandler

I will share what worked for me, using Spring Boot v2.3.4 with the embedded Tomcat. I wanted to allow files of 10 MB and show an error message to the user

Added this to application.yml

#Configuration for size of files that can be uploaded
#Set Tomcat to accept any file size for failed upload
server.tomcat.max-swallow-size: -1
#set the Spring max file size for upload
spring.servlet.multipart:
    max-file-size: 10MB
    max-request-size: 10MB

Then I added a @ControllerAdvice like the guide said. Note that just adding an @ExceptionHandler to the controller where the upload is handled did not work.

@Slf4j
@ControllerAdvice
public class FileUploadExceptionAdvice {


    @ExceptionHandler(MaxUploadSizeExceededException.class)
    public ResponseEntity<String> handleMaxUploadSizeExceededException(Exception ex){
        log.warn("Handling MaxUploadSizeExceededException: {}",ex.getMessage());

       
        return ResponseEntity.status(HttpStatus.PAYLOAD_TOO_LARGE)
                .contentType(MediaType.TEXT_PLAIN)
                .body("Message for user");
    }
}

In my case I wanted to return some plain text to be used in the JS library that handles the upload and shows the error (this one is excellent:https://www.dropzonejs.com/).

You can change your handling depending on what you need : some JSON error message, or show a page with an error message like the guide.

Note that you can retrieve the Locale in the exception handler if you need i18n for your error message, just add it as a parameter of the method. Looks something like this:

@Slf4j
@ControllerAdvice
public class FileUploadExceptionAdvice {
    @Value("${spring.servlet.multipart.max-file-size}")
    private String maxFileSize;

    private final MessageSource messageSource;

    public FileUploadExceptionAdvice(MessageSource messageSource){
        this.messageSource=messageSource;
    }

 @ExceptionHandler(MaxUploadSizeExceededException.class)
    public ResponseEntity<String> handleMaxUploadSizeExceededException(Locale locale, Exception ex){

        String errorMessage = messageSource.getMessage("max.upload.size.exceeded", new Object[]{maxFileSize}, locale);
//use errorMessage in return handling
   }
}
like image 27
loïc Avatar answered Oct 20 '22 00:10

loïc