Skip to content

Uploading a too-large file should be a 4xx client error, not a 500 Internal Server Error #27170

@mjustin

Description

@mjustin

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);
            }
        }
    }
}

Metadata

Metadata

Assignees

Labels

in: webIssues in web modules (web, webmvc, webflux, websocket)type: enhancementA general enhancement

Type

No type

Projects

No projects

Milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions