This example uses Java 26, Spring Boot 4.1 and Spring MVC to upload a file from the browser without a full page reload.
The browser places the selected file in a FormData object and sends it with fetch(). Spring MVC exposes the uploaded part as a MultipartFile, and Java's Path and Files APIs save it to a server-controlled directory.
Spring Boot file upload steps
- Create a Spring Boot 4.1 application with Spring Web.
- Configure multipart upload limits.
- Create a REST controller that accepts
MultipartFile. - Validate the upload and generate a safe server-side filename.
- Save the file with Java's NIO
PathAPI. - Send the file asynchronously from the browser with
fetch().
Spring Boot 4 and Java 26 Maven setup
Spring Boot's Web starter supplies Spring MVC and multipart upload support. The Spring Boot parent manages compatible Spring Framework and third-party dependency versions.
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>4.1.0</version>
<relativePath/>
</parent>
<properties>
<java.version>26</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
</dependencies>Configure Spring multipart upload limits
Never accept unbounded uploads. Spring Boot lets you define limits in application.properties:
spring.servlet.multipart.max-file-size=10MB
spring.servlet.multipart.max-request-size=10MBChoose limits appropriate for the application. A profile-image endpoint might allow only a few megabytes, while a document-management application may need more.
Modern Spring MVC file upload controller
The original example saved MultipartFile.getOriginalFilename() directly to C:\upload. That is not a good production pattern because the filename comes from the client and the path is operating-system specific. fileciteturn47file0L133-L160
A modern controller can use Path, create the upload directory automatically and generate a filename on the server:
package com.example.demo;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.UUID;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;
@RestController
public class FileUploadController {
private final Path uploadDirectory = Path.of("uploads");
public FileUploadController() throws IOException {
Files.createDirectories(uploadDirectory);
}
@PostMapping("/api/files")
ResponseEntity<String> upload(
@RequestParam("file") MultipartFile file) {
if (file.isEmpty()) {
return ResponseEntity.badRequest()
.body("Choose a file to upload.");
}
try {
var filename = createFilename(file);
var destination = uploadDirectory.resolve(filename);
file.transferTo(destination);
return ResponseEntity.status(HttpStatus.CREATED)
.body(filename);
} catch (IOException e) {
return ResponseEntity.internalServerError()
.body("The file could not be saved.");
}
}
private String createFilename(MultipartFile file) {
var original = file.getOriginalFilename();
var extension = extensionOf(original);
return UUID.randomUUID() + extension;
}
private String extensionOf(String filename) {
if (filename == null) {
return "";
}
var dot = filename.lastIndexOf('.');
return dot >= 0 ? filename.substring(dot) : "";
}
}The browser-provided filename is used only to preserve the extension. The actual stored filename is generated with UUID.randomUUID(), which prevents one user's upload from casually overwriting another file with the same name.
For applications that do not need the original extension, generating the complete stored filename independently is even safer.
Validate uploaded file types
Filename extensions and browser-supplied content types are not security boundaries. If the application accepts only specific formats, validate the file on the server.
A basic first check can reject unexpected MIME types:
private boolean isAllowed(MultipartFile file) {
return switch (file.getContentType()) {
case "image/jpeg", "image/png", "image/webp" -> true;
case null, default -> false;
};
}Java 26 supports this compact switch syntax, including a null case. For security-sensitive uploads, inspect the file's actual contents as well. A client can lie about both the extension and the MIME type.
Ajax file uploader with modern JavaScript
Put a static page at src/main/resources/static/index.html. Spring Boot automatically serves files from its static resource locations, so a separate MVC controller is unnecessary just to render this page.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Spring Boot File Upload</title>
</head>
<body>
<input id="file" type="file">
<button id="upload" type="button">Upload</button>
<p id="status" aria-live="polite"></p>
<script src="/upload.js"></script>
</body>
</html>Then save the JavaScript as src/main/resources/static/upload.js:
const fileInput = document.querySelector("#file");
const uploadButton = document.querySelector("#upload");
const status = document.querySelector("#status");
uploadButton.addEventListener("click", async () => {
const file = fileInput.files[0];
if (!file) {
status.textContent = "Choose a file first.";
return;
}
const data = new FormData();
data.append("file", file);
uploadButton.disabled = true;
status.textContent = "Uploading...";
try {
const response = await fetch("/api/files", {
method: "POST",
body: data
});
const message = await response.text();
if (!response.ok) {
throw new Error(message);
}
status.textContent = `Uploaded as ${message}`;
} catch (error) {
status.textContent = error.message || "Upload failed.";
} finally {
uploadButton.disabled = false;
}
});Do not manually add a Content-Type: multipart/form-data header when sending FormData. The browser adds the header and its multipart boundary automatically.
Run the Spring Boot uploader
Start the Spring Boot application and open:
https://localhost:8080/Select a file and click Upload. The browser sends the multipart request to /api/files, and the server saves the file under the local uploads directory.
Production file upload security
A working upload endpoint is only the beginning. Production systems should consider all of the following:
- Set explicit request and file-size limits.
- Authenticate and authorize users before accepting uploads.
- Generate server-controlled storage names.
- Restrict accepted file formats.
- Inspect file contents when MIME spoofing would be dangerous.
- Do not store untrusted executable files in a directory served by the application server.
- Apply malware scanning when the application's risk profile requires it.
- Use object storage such as Amazon S3 or another dedicated storage service when uploads need durability and horizontal scalability.
Why Path is better than File here
Modern Java code should generally use the java.nio.file.Path and Files APIs for filesystem work.
var uploadDirectory = Path.of("uploads");
Files.createDirectories(uploadDirectory);
var destination = uploadDirectory.resolve(filename);
file.transferTo(destination);This is portable across Windows, Linux and macOS and avoids embedding a machine-specific path such as C:\upload\ in application source code.
Spring Boot 4 file upload architecture
The complete flow is simple:
Browser file input
↓
FormData
↓
fetch("/api/files")
↓
Spring MultipartFile
↓
validation
↓
server-generated filename
↓
Path / Files
↓
uploads directorySpring Boot 4.1 handles the multipart plumbing, while Java 26 provides concise, modern language and filesystem APIs. The result is a much cleaner implementation than directly concatenating an untrusted original filename onto a Windows filesystem path.