Spring Boot: File Upload with MultipartFile

Spring Boot handles multipart file uploads through the MultipartFile interface. The embedded Tomcat servlet handles the multipart parsing; your controller receives ready-to-use file objects with metadata (original name, size, content type) and the raw byte content.

Contents

Multipart Config

Add the following entries to your application.properties file.

Properties
# application.properties
spring.servlet.multipart.enabled=true
spring.servlet.multipart.max-file-size=10MB     # max size per file
spring.servlet.multipart.max-request-size=50MB  # max total request size
spring.servlet.multipart.location=/tmp          # temp dir for parts
The defaults are max-file-size=1MB and max-request-size=10MB. Increase them only as needed. A MaxUploadSizeExceededException is thrown when limits are exceeded: catch it with @ControllerAdvice.

@PostMapping with MultipartFile

Accept an upload with a MultipartFile parameter in a @PostMapping method.

Java
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;

@RestController
@RequestMapping("/files")
public class FileController {

    private final FileStorageService storageService;

    public FileController(FileStorageService storageService) {
        this.storageService = storageService;
    }

    @PostMapping("/upload")
    public ResponseEntity<Map<String, String>> uploadFile(
            @RequestParam("file") MultipartFile file) {

        if (file.isEmpty()) {
            return ResponseEntity.badRequest()
                .body(Map.of("error", "File must not be empty"));
        }

        String savedName = storageService.store(file);

        return ResponseEntity.ok(Map.of(
            "filename",     savedName,
            "originalName", file.getOriginalFilename(),
            "size",         String.valueOf(file.getSize()),
            "contentType",  file.getContentType()
        ));
    }
}

Saving to Disk

This service writes the uploaded file to a directory on disk.

Java
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;
import java.io.IOException;
import java.nio.file.*;
import java.util.UUID;

@Service
public class FileStorageService {

    private final Path uploadDir = Paths.get("uploads");

    public FileStorageService() throws IOException {
        Files.createDirectories(uploadDir);
    }

    public String store(MultipartFile file) {
        try {
            // Generate a unique name to avoid collisions
            String extension = getExtension(file.getOriginalFilename());
            String uniqueName = UUID.randomUUID() + "." + extension;

            Path destination = uploadDir.resolve(uniqueName).normalize().toAbsolutePath();

            // Security check — prevent path traversal
            if (!destination.startsWith(uploadDir.toAbsolutePath())) {
                throw new StorageException("Cannot store file outside upload directory");
            }

            file.transferTo(destination);
            return uniqueName;

        } catch (IOException e) {
            throw new StorageException("Failed to store file", e);
        }
    }

    private String getExtension(String filename) {
        if (filename == null || !filename.contains(".")) return "bin";
        return filename.substring(filename.lastIndexOf('.') + 1);
    }
}
Never use the original filename directly as a path. Always sanitise or replace it with a UUID to prevent path traversal attacks. Validate extensions before saving.

Validating File Type and Size

Check the size and content type yourself before saving; the multipart size limits do not check the type.

Java
public void validate(MultipartFile file) {
    // Size check (in addition to multipart limits)
    long maxSizeBytes = 5 * 1024 * 1024; // 5 MB
    if (file.getSize() > maxSizeBytes) {
        throw new InvalidFileException("File size exceeds 5 MB limit");
    }

    // Content type whitelist
    List<String> allowedTypes = List.of("image/jpeg", "image/png", "application/pdf");
    String contentType = file.getContentType();
    if (contentType == null || !allowedTypes.contains(contentType)) {
        throw new InvalidFileException("File type not allowed: " + contentType);
    }

    // Extension check (defence in depth — content type can be spoofed)
    String name = file.getOriginalFilename();
    if (name == null) throw new InvalidFileException("Filename is missing");
    String ext = name.toLowerCase();
    if (!ext.endsWith(".jpg") && !ext.endsWith(".jpeg")
            && !ext.endsWith(".png") && !ext.endsWith(".pdf")) {
        throw new InvalidFileException("File extension not allowed");
    }
}

Multiple File Upload

To accept several files, bind the request part to a list of MultipartFile.

Java
@PostMapping("/upload-multiple")
public ResponseEntity<List<String>> uploadMultiple(
        @RequestParam("files") List<MultipartFile> files) {

    List<String> savedNames = files.stream()
        .filter(f -> !f.isEmpty())
        .map(storageService::store)
        .toList();

    return ResponseEntity.ok(savedNames);
}

HTML form for multiple file upload:

HTML
<form action="/files/upload-multiple" method="post" enctype="multipart/form-data">
  <input type="file" name="files" multiple>
  <button type="submit">Upload</button>
</form>

Downloading a File with ResponseEntity

Return the file as a Resource with a Content-Disposition header, so the browser downloads it.

Java
import org.springframework.core.io.Resource;
import org.springframework.core.io.UrlResource;
import org.springframework.http.*;

@GetMapping("/download/{filename}")
public ResponseEntity<Resource> downloadFile(@PathVariable String filename) {
    try {
        Path filePath = uploadDir.resolve(filename).normalize();
        Resource resource = new UrlResource(filePath.toUri());

        if (!resource.exists() || !resource.isReadable()) {
            return ResponseEntity.notFound().build();
        }

        // Determine content type
        String contentType = Files.probeContentType(filePath);
        if (contentType == null) contentType = "application/octet-stream";

        return ResponseEntity.ok()
            .contentType(MediaType.parseMediaType(contentType))
            .header(HttpHeaders.CONTENT_DISPOSITION,
                    "attachment; filename=\"" + resource.getFilename() + "\"")
            .body(resource);

    } catch (IOException e) {
        return ResponseEntity.internalServerError().build();
    }
}

// To display inline (e.g., images in browser) instead of downloading:
// .header(HttpHeaders.CONTENT_DISPOSITION, "inline; filename=\"" + name + "\"")