- Uploading Files
- What You Will Build
- What You Need
- How to complete this guide
- Starting with Spring Initializr
- Create an Application Class
- Create a File Upload Controller
- Creating an HTML Template
- Interface MultipartFile
- Method Summary
- Method Details
- getName
- getOriginalFilename
- getContentType
- isEmpty
- getSize
- getBytes
- getInputStream
- getResource
- transferTo
- transferTo
Uploading Files
This guide walks you through the process of creating a server application that can receive HTTP multi-part file uploads.
What You Will Build
You will create a Spring Boot web application that accepts file uploads. You will also build a simple HTML interface to upload a test file.
What You Need
- About 15 minutes
- A favorite text editor or IDE
- Java 17 or later
- Gradle 7.5+ or Maven 3.5+
- You can also import the code straight into your IDE:
How to complete this guide
Like most Spring Getting Started guides, you can start from scratch and complete each step or you can bypass basic setup steps that are already familiar to you. Either way, you end up with working code.
To start from scratch, move on to Starting with Spring Initializr.
To skip the basics, do the following:
- Download and unzip the source repository for this guide, or clone it using Git: git clone https://github.com/spring-guides/gs-uploading-files.git
- cd into gs-uploading-files/initial
- Jump ahead to Create an Application Class.
When you finish, you can check your results against the code in gs-uploading-files/complete .
Starting with Spring Initializr
You can use this pre-initialized project and click Generate to download a ZIP file. This project is configured to fit the examples in this tutorial.
To manually initialize the project:
- Navigate to https://start.spring.io. This service pulls in all the dependencies you need for an application and does most of the setup for you.
- Choose either Gradle or Maven and the language you want to use. This guide assumes that you chose Java.
- Click Dependencies and select Spring Web and Thymeleaf.
- Click Generate.
- Download the resulting ZIP file, which is an archive of a web application that is configured with your choices.
Create an Application Class
To start a Spring Boot MVC application, you first need a starter. In this sample, spring-boot-starter-thymeleaf and spring-boot-starter-web are already added as dependencies. To upload files with Servlet containers, you need to register a MultipartConfigElement class (which would be in web.xml). Thanks to Spring Boot, everything is auto-configured for you!
All you need to get started with this application is the following UploadingFilesApplication class (from src/main/java/com/example/uploadingfiles/UploadingFilesApplication.java ):
package com.example.uploadingfiles; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; @SpringBootApplication public class UploadingFilesApplication < public static void main(String[] args) < SpringApplication.run(UploadingFilesApplication.class, args); >>
As part of auto-configuring Spring MVC, Spring Boot will create a MultipartConfigElement bean and make itself ready for file uploads.
Create a File Upload Controller
The initial application already contains a few classes to deal with storing and loading the uploaded files on disk. They are all located in the com.example.uploadingfiles.storage package. You will use those in your new FileUploadController . The following listing (from src/main/java/com/example/uploadingfiles/FileUploadController.java ) shows the file upload controller:
package com.example.uploadingfiles; import java.io.IOException; import java.util.stream.Collectors; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.core.io.Resource; import org.springframework.http.HttpHeaders; import org.springframework.http.ResponseEntity; import org.springframework.stereotype.Controller; import org.springframework.ui.Model; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.ResponseBody; import org.springframework.web.multipart.MultipartFile; import org.springframework.web.servlet.mvc.method.annotation.MvcUriComponentsBuilder; import org.springframework.web.servlet.mvc.support.RedirectAttributes; import com.example.uploadingfiles.storage.StorageFileNotFoundException; import com.example.uploadingfiles.storage.StorageService; @Controller public class FileUploadController < private final StorageService storageService; @Autowired public FileUploadController(StorageService storageService) < this.storageService = storageService; >@GetMapping("/") public String listUploadedFiles(Model model) throws IOException < model.addAttribute("files", storageService.loadAll().map( path ->MvcUriComponentsBuilder.fromMethodName(FileUploadController.class, "serveFile", path.getFileName().toString()).build().toUri().toString()) .collect(Collectors.toList())); return "uploadForm"; > @GetMapping("/files/") @ResponseBody public ResponseEntity serveFile(@PathVariable String filename) < Resource file = storageService.loadAsResource(filename); return ResponseEntity.ok().header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=\"" + file.getFilename() + "\"").body(file); >@PostMapping("/") public String handleFileUpload(@RequestParam("file") MultipartFile file, RedirectAttributes redirectAttributes) < storageService.store(file); redirectAttributes.addFlashAttribute("message", "You successfully uploaded " + file.getOriginalFilename() + "!"); return "redirect:/"; >@ExceptionHandler(StorageFileNotFoundException.class) public ResponseEntity handleStorageFileNotFound(StorageFileNotFoundException exc) < return ResponseEntity.notFound().build(); >>
The FileUploadController class is annotated with @Controller so that Spring MVC can pick it up and look for routes. Each method is tagged with @GetMapping or @PostMapping to tie the path and the HTTP action to a particular controller action.
- GET / : Looks up the current list of uploaded files from the StorageService and loads it into a Thymeleaf template. It calculates a link to the actual resource by using MvcUriComponentsBuilder .
- GET /files/ : Loads the resource (if it exists) and sends it to the browser to download by using a Content-Disposition response header.
- POST / : Handles a multi-part message file and gives it to the StorageService for saving.
In a production scenario, you more likely would store the files in a temporary location, a database, or perhaps a NoSQL store (such as Mongo’s GridFS). It is best to NOT load up the file system of your application with content. |
You will need to provide a StorageService so that the controller can interact with a storage layer (such as a file system). The following listing (from src/main/java/com/example/uploadingfiles/storage/StorageService.java ) shows that interface:
package com.example.uploadingfiles.storage; import org.springframework.core.io.Resource; import org.springframework.web.multipart.MultipartFile; import java.nio.file.Path; import java.util.stream.Stream; public interface StorageService < void init(); void store(MultipartFile file); StreamloadAll(); Path load(String filename); Resource loadAsResource(String filename); void deleteAll(); >
You also need four classes to support the storage service:
package com.example.uploadingfiles.storage; import org.springframework.boot.context.properties.ConfigurationProperties; @ConfigurationProperties("storage") public class StorageProperties < /** * Folder location for storing files */ private String location = "upload-dir"; public String getLocation() < return location; >public void setLocation(String location) < this.location = location; >>
package com.example.uploadingfiles.storage; public class StorageException extends RuntimeException < public StorageException(String message) < super(message); >public StorageException(String message, Throwable cause) < super(message, cause); >>
package com.example.uploadingfiles.storage; public class StorageFileNotFoundException extends StorageException < public StorageFileNotFoundException(String message) < super(message); >public StorageFileNotFoundException(String message, Throwable cause) < super(message, cause); >>
package com.example.uploadingfiles.storage; import java.io.IOException; import java.io.InputStream; import java.net.MalformedURLException; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import java.nio.file.StandardCopyOption; import java.util.stream.Stream; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.core.io.Resource; import org.springframework.core.io.UrlResource; import org.springframework.stereotype.Service; import org.springframework.util.FileSystemUtils; import org.springframework.util.StringUtils; import org.springframework.web.multipart.MultipartFile; @Service public class FileSystemStorageService implements StorageService < private final Path rootLocation; @Autowired public FileSystemStorageService(StorageProperties properties) < this.rootLocation = Paths.get(properties.getLocation()); >@Override public void store(MultipartFile file) < try < if (file.isEmpty()) < throw new StorageException("Failed to store empty file."); >Path destinationFile = this.rootLocation.resolve( Paths.get(file.getOriginalFilename())) .normalize().toAbsolutePath(); if (!destinationFile.getParent().equals(this.rootLocation.toAbsolutePath())) < // This is a security check throw new StorageException( "Cannot store file outside current directory."); >try (InputStream inputStream = file.getInputStream()) < Files.copy(inputStream, destinationFile, StandardCopyOption.REPLACE_EXISTING); >> catch (IOException e) < throw new StorageException("Failed to store file.", e); >> @Override public Stream loadAll() < try < return Files.walk(this.rootLocation, 1) .filter(path ->!path.equals(this.rootLocation)) .map(this.rootLocation::relativize); > catch (IOException e) < throw new StorageException("Failed to read stored files", e); >> @Override public Path load(String filename) < return rootLocation.resolve(filename); >@Override public Resource loadAsResource(String filename) < try < Path file = load(filename); Resource resource = new UrlResource(file.toUri()); if (resource.exists() || resource.isReadable()) < return resource; >else < throw new StorageFileNotFoundException( "Could not read file: " + filename); >> catch (MalformedURLException e) < throw new StorageFileNotFoundException("Could not read file: " + filename, e); >> @Override public void deleteAll() < FileSystemUtils.deleteRecursively(rootLocation.toFile()); >@Override public void init() < try < Files.createDirectories(rootLocation); >catch (IOException e) < throw new StorageException("Could not initialize storage", e); >> >
Creating an HTML Template
The following Thymeleaf template (from src/main/resources/templates/uploadForm.html ) shows an example of how to upload files and show what has been uploaded:
Interface MultipartFile
The file contents are either stored in memory or temporarily on disk. In either case, the user is responsible for copying file contents to a session-level or persistent store as and if desired. The temporary storage will be cleared at the end of request processing.
Method Summary
Return whether the uploaded file is empty, that is, either no file has been chosen in the multipart form or the chosen file has no content.
Method Details
getName
getOriginalFilename
Return the original filename in the client’s filesystem. This may contain path information depending on the browser used, but it typically will not with any other than Opera. Note: Please keep in mind this filename is supplied by the client and should not be used blindly. In addition to not using the directory portion, the file name could also contain characters such as «..» and others that can be used maliciously. It is recommended to not use this filename directly. Preferably generate a unique one and save this one somewhere for reference, if necessary.
getContentType
isEmpty
Return whether the uploaded file is empty, that is, either no file has been chosen in the multipart form or the chosen file has no content.
getSize
getBytes
getInputStream
Return an InputStream to read the contents of the file from. The user is responsible for closing the returned stream.
getResource
Return a Resource representation of this MultipartFile. This can be used as input to the RestTemplate or the WebClient to expose content length and the filename along with the InputStream.
transferTo
Transfer the received file to the given destination file. This may either move the file in the filesystem, copy the file in the filesystem, or save memory-held contents to the destination file. If the destination file already exists, it will be deleted first. If the target file has been moved in the filesystem, this operation cannot be invoked again afterwards. Therefore, call this method just once in order to work with any storage mechanism. NOTE: Depending on the underlying provider, temporary storage may be container-dependent, including the base directory for relative destinations specified here (e.g. with Servlet multipart handling). For absolute destinations, the target file may get renamed/moved from its temporary location or newly copied, even if a temporary copy already exists.
transferTo
Transfer the received file to the given destination file. The default implementation simply copies the file input stream.