When uploading a file that is too large (exceeding either spring.servlet.multipart.max-file-size or spring.servlet.multipart.max-request-size), a 500 Internal Server Error is thrown. This seems odd to me, as this is an error due to an unsupported value sent by the client, not an unexpected server issue.

It feels like a 4xx client error would be more appropriate for this situation, such as 413 Payload Too Large.

Workaround

This can be manually implemented in a Spring Boot application today by creating a custom @ExceptionHandler for MaxUploadSizeExceededException. If the handler is set in the controller class, the spring.servlet.multipart.resolve-lazily property must also be set to true:

@ExceptionHandler
public void maxUploadSizeExceeded(MaxUploadSizeExceededException e, HttpServletResponse response) throws IOException {
    response.sendError(HttpServletResponse.SC_REQUEST_ENTITY_TOO_LARGE);
}

Example of current behavior

import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;

@RestController
@RequestMapping("/fileUpload")
public class FileUploadController {
    @PostMapping
    public String handleFileUpload(@RequestParam("file") MultipartFile file) {
        return String.format("Uploaded %s (%s bytes)", file.getName(), file.getSize());
    }
}
import com.fasterxml.jackson.databind.JsonNode;
import org.junit.jupiter.api.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.web.client.TestRestTemplate;
import org.springframework.core.io.FileSystemResource;
import org.springframework.http.*;
import org.springframework.test.context.TestPropertySource;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.web.multipart.MaxUploadSizeExceededException;

import java.io.*;
import java.nio.file.*;

import static org.junit.jupiter.api.Assertions.assertEquals;

class UploadTest {
    @Nested
    @TestPropertySource(properties = "spring.servlet.multipart.max-file-size:1000B")
    class MaxFileSize extends AbstractUploadTest {
        @Test
        void uploadFileLargerThanMaxFileSize() {
            ResponseEntity<JsonNode> response = uploadFileWithError(" ".repeat(2000).getBytes());
            assertEquals(MaxUploadSizeExceededException.class.getName(), response.getBody().get("exception").textValue());
            assertEquals(HttpStatus.INTERNAL_SERVER_ERROR, response.getStatusCode());
        }
    }

    @Nested
    @TestPropertySource(properties = "spring.servlet.multipart.max-request-size:1000B")
    class MaxRequestSize extends AbstractUploadTest  {
        @Test
        void uploadFileLargerThanMaxRequestSize() {
            ResponseEntity<JsonNode> response = uploadFileWithError(" ".repeat(2000).getBytes());
            assertEquals(MaxUploadSizeExceededException.class.getName(), response.getBody().get("exception").textValue());
            assertEquals(HttpStatus.INTERNAL_SERVER_ERROR, response.getStatusCode());
        }
    }

    @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
    @TestPropertySource(properties = "server.error.include-exception=true")
    private static abstract class AbstractUploadTest {
        @Autowired
        private TestRestTemplate testRestTemplate;

        protected ResponseEntity<JsonNode> uploadFileWithError(byte[] bytes) {
            return testRestTemplate.postForEntity("/fileUpload",
                    getRequestEntity(bytes), JsonNode.class);
        }

        private HttpEntity<LinkedMultiValueMap<String, Object>> getRequestEntity(byte[] data) {
            LinkedMultiValueMap<String, Object> parameters = new LinkedMultiValueMap<>();
            parameters.add("file", createTempFile(data));

            HttpHeaders headers = new HttpHeaders();
            headers.setContentType(MediaType.MULTIPART_FORM_DATA);

            return new HttpEntity<>(parameters, headers);
        }

        private FileSystemResource createTempFile(byte[] data) {
            try {
                Path file = Files.createTempFile("test", ".txt");
                Files.write(file, data);
                return new FileSystemResource(file);
            } catch (IOException e) {
                throw new UncheckedIOException(e);
            }
        }
    }
}

Comment From: wilkinsona

Thanks for the suggestion.

If there is to be a default mapping to a 413 response, I think it should be done in Spring Framework, possibly in the existing org.springframework.web.servlet.mvc.support.DefaultHandlerExceptionResolver class. We'll transfer this issue to the Framework team for their consideration.