In this tutorial, you’ll learn how to build a file upload and download REST API using Spring Boot 3.5.6 and Java 21. You’ll implement endpoints to upload files to the local filesystem and download them later through HTTP requests.
By the end of this guide, you’ll have a working API that supports:
-
Uploading single or multiple files
-
Serving stored files for download
-
Organizing files safely in a configurable directory
This tutorial is ideal for backend developers who want to handle file uploads and downloads efficiently without external storage like AWS S3.
What You’ll Need
-
Java 21 or later
-
Maven 3.9+
-
An IDE like IntelliJ IDEA or VS Code
-
Postman or curl for testing
Next, we’ll set up a new Spring Boot project and add all required dependencies.
Project Setup
In this section, you’ll create a new Spring Boot 3.5.6 project using Spring Initializr and configure it to support file upload and download through REST APIs.
Step 1: Create a New Spring Boot Project
You can generate a Spring Boot project using Spring Initializr or your IDE’s built-in generator.
Project Metadata:
Field | Value |
---|---|
Project | Maven |
Language | Java |
Spring Boot | 3.5.6 |
Group | com.djamware |
Artifact | file-upload-download |
Name | File Upload Download |
Package name | com.djamware.fileuploaddownload |
Packaging | Jar |
Java | 21 |
Dependencies:
-
Spring Web – to build REST APIs
-
Spring Boot DevTools – for live reload and fast development
-
Lombok – to reduce boilerplate code (getters/setters, constructors)
Click “Generate”, and extract the downloaded ZIP file into your development folder.
Step 2: Project Structure Overview
Once you open the project in your IDE, the structure should look like this:
file-upload-download/
├── src/
│ ├── main/
│ │ ├── java/
│ │ │ └── com/
│ │ │ └── djamware/
│ │ │ └── fileuploaddownload/
│ │ │ └── FileUploadDownloadApplication.java
│ │ └── resources/
│ │ ├── application.properties
│ │ └── static/
│ └── test/
│ └── java/
│ └── com/
│ └── djamware/
│ └── fileuploaddownload/
│ └── FileUploadDownloadApplicationTests.java
├── mvnw
├── mvnw.cmd
├── pom.xml
└── README.md
Step 3: Add Dependencies to pom.xml
If you didn’t include them during project creation, make sure your pom.xml
contains the following dependencies:
<dependencies>
<!-- Spring Web -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- Lombok -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<!-- DevTools -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
</dependency>
<!-- Test (optional) -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
Run the following command to make sure everything compiles correctly:
mvn clean install
If the build succeeds, you’re ready to configure the upload directory.
Create the File Upload Directory
Before we can upload any files, we need to define where they’ll be stored and make sure that the directory exists when the Spring Boot application starts.
Step 1: Define the Upload Directory
Open the src/main/resources/application.properties
file and add the following configuration:
# File upload directory
file.upload-dir=uploads
This property defines a relative folder named uploads
inside your project root. You can also set an absolute path (e.g., /home/user/uploads
or C:/uploads
) if you want to store files elsewhere.
Step 2: Create a File Storage Service
Now, create a service class that handles initialization of the upload directory and provides access to the configured path.
File: src/main/java/com/djamware/file_upload_download/service/FileStorageService.java
package com.djamware.file_upload_download.service;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import jakarta.annotation.PostConstruct;
@Service
public class FileStorageService {
@Value("${file.upload-dir}")
private String uploadDir;
private Path fileStoragePath;
@PostConstruct
public void init() {
try {
fileStoragePath = Paths.get(uploadDir).toAbsolutePath().normalize();
Files.createDirectories(fileStoragePath);
System.out.println("Upload directory initialized at: " + fileStoragePath);
} catch (IOException e) {
throw new RuntimeException("Could not create upload directory!", e);
}
}
public Path getFileStoragePath() {
return fileStoragePath;
}
}
How It Works
-
The
@Value
annotation injects the directory path fromapplication.properties
. -
The
@PostConstruct
method runs automatically after bean initialization to create the directory if it doesn’t exist. -
The
getFileStoragePath()
method provides access to the absolute path for other services or controllers.
Step 3: Verify the Directory Creation
Run the Spring Boot application using:
mvn spring-boot:run
You should see a log similar to this in your console:
Upload directory initialized at: /path/to/project/uploads
And an uploads folder will appear in your project directory — ready to store incoming files!
Implement the File Upload REST API
In this section, we’ll create a REST controller that allows users to upload single or multiple files and saves them to the configured uploads
directory.
Step 1: Create the File Upload Controller
Create a new controller class named FileUploadController.java
inside com.djamware.file_upload_download
.controller
.
File: src/main/java/com/djamware/file_upload_download/controller/FileUploadController.java
package com.djamware.file_upload_download.controller;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardCopyOption;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;
import com.djamware.file_upload_download.service.FileStorageService;
@RestController
@RequestMapping("/api/files")
public class FileUploadController {
@Autowired
private FileStorageService fileStorageService;
@PostMapping("/upload")
public ResponseEntity<Map<String, Object>> uploadFile(@RequestParam("file") MultipartFile file) {
Map<String, Object> response = new HashMap<>();
try {
String fileName = StringUtils.cleanPath(Objects.requireNonNull(file.getOriginalFilename()));
Path targetLocation = fileStorageService.getFileStoragePath().resolve(fileName);
Files.copy(file.getInputStream(), targetLocation, StandardCopyOption.REPLACE_EXISTING);
response.put("fileName", fileName);
response.put("fileSize", file.getSize());
response.put("message", "File uploaded successfully!");
return ResponseEntity.ok(response);
} catch (IOException e) {
response.put("error", "Could not upload the file: " + e.getMessage());
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(response);
}
}
@PostMapping("/upload-multiple")
public ResponseEntity<List<Map<String, Object>>> uploadMultipleFiles(@RequestParam("files") MultipartFile[] files) {
List<Map<String, Object>> results = new ArrayList<>();
for (MultipartFile file : files) {
Map<String, Object> fileResponse = new HashMap<>();
try {
String fileName = StringUtils.cleanPath(Objects.requireNonNull(file.getOriginalFilename()));
Path targetLocation = fileStorageService.getFileStoragePath().resolve(fileName);
Files.copy(file.getInputStream(), targetLocation, StandardCopyOption.REPLACE_EXISTING);
fileResponse.put("fileName", fileName);
fileResponse.put("fileSize", file.getSize());
fileResponse.put("message", "Uploaded successfully");
} catch (IOException e) {
fileResponse.put("error", "Failed to upload: " + e.getMessage());
}
results.add(fileResponse);
}
return ResponseEntity.ok(results);
}
}
Step 2: How the Upload API Works
-
The
/api/files/upload
endpoint accepts a single file under the keyfile
. -
The
/api/files/upload-multiple
endpoint accepts multiple files under the keyfiles
. -
Each file is saved to the directory defined in
application.properties
. -
The response includes the file name, size, and a success or error message.
-
StringUtils.cleanPath()
ensures filenames are sanitized to prevent path traversal attacks.
Step 3: Test the Upload API
Start the Spring Boot app again:
mvn spring-boot:run
Then, open Postman and send a POST request to:
Single file upload:
POST http://localhost:8080/api/files/upload
-
Under the Body tab, choose form-data.
-
Add a key named
file
, select type File, and pick any file from your system. -
Click Send.
✅ You should see a response similar to:
{
"fileName": "example.txt",
"fileSize": 1024,
"message": "File uploaded successfully!"
}
You’ll also see the uploaded file inside your uploads/
directory.
Multiple file upload:
POST http://localhost:8080/api/files/upload-multiple
-
Add a
files
key with multiple files selected. -
The response will list each file with its status.
Implement the File Download REST API
In this section, you’ll create a REST endpoint that lets users download files previously uploaded to the local filesystem.
Step 1: Add the Download Endpoint
Let’s extend the existing FileUploadController
by adding a new method for downloading files.
File: src/main/java/com/djamware/file_upload_download
/controller/FileUploadController.java
Add the following imports and method:
import org.springframework.core.io.Resource;
import org.springframework.core.io.UrlResource;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import java.net.MalformedURLException;
Then add the download method below the upload endpoints:
@GetMapping("/download/{fileName:.+}")
public ResponseEntity<Resource> downloadFile(@PathVariable String fileName) {
try {
Path filePath = fileStorageService.getFileStoragePath().resolve(fileName).normalize();
Resource resource = new UrlResource(filePath.toUri());
if (!resource.exists()) {
return ResponseEntity.notFound().build();
}
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 (MalformedURLException e) {
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build();
} catch (IOException e) {
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build();
}
}
Step 2: How the Download API Works
-
The endpoint
/api/files/download/{fileName}
retrieves a file by name. -
It uses
UrlResource
to safely read the file from the filesystem. -
Files.probeContentType()
automatically sets the correct MIME type (e.g., image/png, text/plain). -
The response header includes a
Content-Disposition
field, prompting the browser to download the file rather than display it.
Step 3: Test the Download API
Start your Spring Boot app if it’s not running:
mvn spring-boot:run
Open Postman or your browser and make a GET request to:
GET http://localhost:8080/api/files/download/example.txt
If the file exists in the uploads
folder, it should start downloading automatically or show the content depending on the MIME type.
If the file does not exist, you’ll get a 404 Not Found response.
✅ Example success response headers:
Content-Type: text/plain
Content-Disposition: attachment; filename="example.txt"
Optional Enhancement (File Validation)
To make it more secure, you can validate filenames to prevent malicious access:
if (fileName.contains("..")) {
return ResponseEntity.badRequest().build(); // Prevent path traversal
}
Testing the APIs
In this section, you’ll test the file upload and download endpoints using Postman and curl to verify that your Spring Boot REST API works correctly.
Step 1: Run the Application
Start your Spring Boot application if it’s not already running:
mvn spring-boot:run
You should see a log confirming the upload directory initialization, such as:
Upload directory initialized at: /path/to/project/uploads
Your REST API will be available at:
http://localhost:8080/api/files
Step 2: Test File Upload with Postman
✅ Single File Upload
-
Open Postman.
-
Create a POST request to:
http://localhost:8080/api/files/upload
-
Under the Body tab, select form-data.
-
Add a key named
file
, change its type to File, and choose any file to upload. -
Click Send.
Expected JSON response:
{
"fileName": "example.txt",
"fileSize": 1024,
"message": "File uploaded successfully!"
}
Check your project’s uploads/
directory — the file should appear there.
✅ Multiple Files Upload
-
Create a POST request to:
http://localhost:8080/api/files/upload-multiple
-
Under Body → form-data, add multiple keys named
files
(same key name), each with a file. -
Click Send.
Expected JSON response:
[
{
"fileName": "photo1.jpg",
"fileSize": 45678,
"message": "Uploaded successfully"
},
{
"fileName": "photo2.jpg",
"fileSize": 56789,
"message": "Uploaded successfully"
}
]
Step 3: Test File Download with Postman
-
Create a GET request to:
http://localhost:8080/api/files/download/example.txt
-
Click Send.
If the file exists, Postman will prompt to download it or show raw data (depending on file type).
If it doesn’t exist, you’ll get a 404 Not Found response.
Step 4: Test Using curl (Optional)
If you prefer the command line, here are equivalent curl commands:
Upload a file:
curl -X POST -F "[email protected]" http://localhost:8080/api/files/upload
Upload multiple files:
curl -X POST -F "[email protected]" -F "[email protected]" http://localhost:8080/api/files/upload-multiple
Download a file:
curl -O http://localhost:8080/api/files/download/example.txt
The -O
flag tells curl to save the file with the same name.
✅ At this point, both upload and download functionalities should work smoothly. You now have a fully functional file management REST API using Spring Boot.
Conclusion
In this tutorial, you’ve learned how to build a Java file upload and download REST API using Spring Boot 3.5.6. You created endpoints to handle single and multiple file uploads, saved them to the local filesystem, and implemented a secure download mechanism using Spring’s Resource
abstraction.
Here’s a recap of what you built:
-
✅ Configured file storage using
application.properties
-
✅ Initialized the upload directory automatically on startup
-
✅ Implemented upload endpoints for single and multiple files
-
✅ Created a download endpoint to serve stored files
-
✅ Tested everything using Postman and curl
This setup provides a simple yet powerful base for managing files in a RESTful API.
Best Practices for Production
Before deploying your app, consider improving it with these best practices:
-
🔒 Validate file types and size limits
Reject unsupported file formats or restrict uploads by MIME type and file size to prevent abuse. -
🧼 Sanitize filenames
Always clean and validate filenames to avoid path traversal attacks (e.g.,../../etc/passwd
). -
🧾 Unique file naming
Prevent overwriting by appending timestamps or UUIDs to filenames:String fileName = UUID.randomUUID() + "_" + originalName;
-
🗂 Separate storage configuration
Store uploaded files in a dedicated directory outside your project structure for better security. -
☁️ Optional cloud integration
In a real-world app, consider uploading files to Amazon S3, Google Cloud Storage, or Azure Blob for scalability and reliability. -
🚫 Error handling and logging
Use a global exception handler (@ControllerAdvice
) to handle errors consistently and log them for debugging.
Final Thoughts
This simple Spring Boot file API is a great starting point for projects like:
-
Profile picture uploads
-
Document management systems
-
Image/file sharing platforms
From here, you can easily expand it by adding file metadata storage (using JPA + database) or enabling cloud-based uploads.
You can find the full source code on our GitHub.
That's just the basics. If you need more deep learning about Spring Boot, you can take the following cheap course:
- [NEW] Spring Boot 3, Spring 6 & Hibernate for Beginners
- Java Spring Framework 6, Spring Boot 3, Spring AI Telusko
- [NEW] Master Microservices with SpringBoot,Docker,Kubernetes
- Spring Boot Microservices Professional eCommerce Masterclass
- Java Spring Boot: Professional eCommerce Project Masterclass
- Master Backend Development using Java & Spring Boot
- Spring Boot REST APIs: Building Modern APIs with Spring Boot
- [NEW] Spring Boot 3, Spring Framework 6: Beginner to Guru
- Spring 6 & Spring Boot 3: AI, Security, Docker, Cloud
- From Java Dev to AI Engineer: Spring AI Fast Track
Thanks!