Support for multipart file uploads with various strategies for reading and storing file content. The module provides flexible options for handling multipart requests, including in-memory storage, temporary file creation, and simplified content reading.
Required Configuration:
MultipartAwareFormHttpMessageConverter must be configured on inbound endpointMultipartFileReader must be set on convertermultipart/form-data content typeMultipartResolver can be configured (optional, uses default if not set)Storage Strategies:
DefaultMultipartFileReader: Stores files in memory as byte arraysFileCopyingMultipartFileReader: Stores files on disk as temporary filesSimpleMultipartFileReader: Returns content as String or byte[] (no file metadata)File Availability:
MultipartFile only available during request scopeUploadedMultipartFile available after request scope endsMemory vs. Disk:
MultipartFileReader interfaceFile Metadata:
getOriginalFilename()getContentType()getSize()getName()UploadedMultipartFileTemporary Files:
"si_", default suffix: ".tmp"Edge Cases:
isEmpty() returns true, size is 0MultiValueMap to access allMultiValueMapOutOfMemoryError (memory strategy) or disk full (disk strategy)Performance Considerations:
Security Considerations:
Strategy interface for reading MultipartFile content in different ways. Implementations can store files in memory, write to filesystem, or extract content as strings or byte arrays.
public interface MultipartFileReader<T> {
/**
* Reads MultipartFile content and returns result of type T.
* Type T depends on implementation strategy (File, MultipartFile, String, byte[], etc.).
*
* @param multipartFile the multipart file to read
* @return the processed file content
* @throws IOException if read error occurs
*/
T readMultipartFile(MultipartFile multipartFile) throws IOException;
}This interface allows custom implementations for different file handling strategies based on application requirements.
MultipartFileReader implementation that reads MultipartFile content into a new MultipartFile instance not restricted to HTTP request scope. Files are stored in memory, making them available after request completion.
public class DefaultMultipartFileReader
implements MultipartFileReader<MultipartFile> {
/**
* Creates default multipart file reader.
* Files are read into memory as UploadedMultipartFile instances.
*/
public DefaultMultipartFileReader();
/**
* Reads multipart file and returns UploadedMultipartFile.
* Content is stored in memory (byte array).
*
* @param multipartFile the source multipart file
* @return UploadedMultipartFile with content in memory
* @throws IOException if read error occurs
*/
public MultipartFile readMultipartFile(MultipartFile multipartFile)
throws IOException;
}Usage Example:
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.integration.http.multipart.DefaultMultipartFileReader;
import org.springframework.integration.http.converter.MultipartAwareFormHttpMessageConverter;
import org.springframework.integration.http.inbound.HttpRequestHandlingMessagingGateway;
import org.springframework.integration.http.inbound.RequestMapping;
import org.springframework.http.HttpMethod;
@Configuration
public class DefaultMultipartConfig {
@Bean
public DefaultMultipartFileReader defaultFileReader() {
return new DefaultMultipartFileReader();
}
@Bean
public MultipartAwareFormHttpMessageConverter multipartConverter() {
MultipartAwareFormHttpMessageConverter converter =
new MultipartAwareFormHttpMessageConverter();
converter.setMultipartFileReader(defaultFileReader());
return converter;
}
@Bean
public HttpRequestHandlingMessagingGateway uploadGateway() {
HttpRequestHandlingMessagingGateway gateway =
new HttpRequestHandlingMessagingGateway();
RequestMapping mapping = new RequestMapping();
mapping.setPathPatterns("/upload");
mapping.setMethods(HttpMethod.POST);
mapping.setConsumes("multipart/form-data");
gateway.setRequestMapping(mapping);
gateway.setMessageConverters(List.of(multipartConverter()));
gateway.setRequestChannel(uploadChannel());
return gateway;
}
}MultipartFileReader implementation that copies MultipartFile content to a new temporary File on the filesystem. Useful for large files or when file persistence is required beyond request scope.
public class FileCopyingMultipartFileReader
implements MultipartFileReader<MultipartFile> {
/**
* Creates reader using default temporary directory.
* Files are created in system temp directory.
*/
public FileCopyingMultipartFileReader();
/**
* Creates reader using specified directory.
* Files are created in the provided directory.
*
* @param directory the directory for temporary files
*/
public FileCopyingMultipartFileReader(File directory);
/**
* Sets prefix for temporary files.
* Default: "si_".
*
* @param prefix the file prefix
*/
public void setPrefix(String prefix);
/**
* Sets suffix for temporary files.
* Default: ".tmp".
*
* @param suffix the file suffix
*/
public void setSuffix(String suffix);
/**
* Reads and copies multipart file to temporary File.
* File is created with configured prefix and suffix.
* Returns UploadedMultipartFile wrapping the temporary file.
*
* @param multipartFile the source multipart file
* @return MultipartFile (UploadedMultipartFile) containing copied content
* @throws IOException if copy error occurs
*/
public MultipartFile readMultipartFile(MultipartFile multipartFile)
throws IOException;
}Usage Example - Default Temp Directory:
import org.springframework.context.annotation.Bean;
import org.springframework.integration.http.multipart.FileCopyingMultipartFileReader;
@Configuration
public class FileCopyingConfig {
@Bean
public FileCopyingMultipartFileReader fileCopyingReader() {
FileCopyingMultipartFileReader reader =
new FileCopyingMultipartFileReader();
reader.setPrefix("upload_");
reader.setSuffix(".dat");
return reader;
}
@Bean
public MultipartAwareFormHttpMessageConverter fileConverter() {
MultipartAwareFormHttpMessageConverter converter =
new MultipartAwareFormHttpMessageConverter();
converter.setMultipartFileReader(fileCopyingReader());
return converter;
}
}Usage Example - Custom Directory:
@Bean
public FileCopyingMultipartFileReader customDirectoryReader() {
File uploadDir = new File("/var/app/uploads");
if (!uploadDir.exists()) {
uploadDir.mkdirs();
}
FileCopyingMultipartFileReader reader =
new FileCopyingMultipartFileReader(uploadDir);
reader.setPrefix("user_upload_");
reader.setSuffix(".bin");
return reader;
}Usage Example - Java DSL:
import org.springframework.integration.dsl.IntegrationFlow;
import org.springframework.integration.http.dsl.Http;
@Bean
public IntegrationFlow fileUploadFlow() {
FileCopyingMultipartFileReader reader =
new FileCopyingMultipartFileReader();
reader.setPrefix("doc_");
reader.setSuffix(".pdf");
MultipartAwareFormHttpMessageConverter converter =
new MultipartAwareFormHttpMessageConverter();
converter.setMultipartFileReader(reader);
return IntegrationFlow
.from(Http.inboundGateway("/documents/upload")
.requestMapping(m -> m
.methods(HttpMethod.POST)
.consumes("multipart/form-data"))
.messageConverters(converter))
.<MultiValueMap<String, Object>>handle((payload, headers) -> {
MultipartFile uploadedFile = (MultipartFile) payload.getFirst("file");
return documentService.processDocument(uploadedFile);
})
.get();
}MultipartFileReader implementation that reads file content as String or byte array depending on Content-Type, without maintaining file metadata. Simplifies file handling when only content is needed.
public class SimpleMultipartFileReader
implements MultipartFileReader<Object> {
/**
* Creates simple multipart file reader.
* Returns String for text content, byte[] for binary content.
*/
public SimpleMultipartFileReader();
/**
* Sets default charset for text content.
* Used when Content-Type doesn't specify charset.
* Default: ISO-8859-1.
*
* @param defaultCharset the default charset
*/
public void setDefaultMultipartCharset(String defaultCharset);
/**
* Reads multipart file and returns String or byte[].
* Returns String for text/* content types, byte[] otherwise.
*
* @param multipartFile the source multipart file
* @return String for text content, byte[] for binary content
* @throws IOException if read error occurs
*/
public Object readMultipartFile(MultipartFile multipartFile)
throws IOException;
}Usage Example:
import org.springframework.context.annotation.Bean;
import org.springframework.integration.http.multipart.SimpleMultipartFileReader;
@Configuration
public class SimpleMultipartConfig {
@Bean
public SimpleMultipartFileReader simpleFileReader() {
SimpleMultipartFileReader reader = new SimpleMultipartFileReader();
reader.setDefaultMultipartCharset("UTF-8");
return reader;
}
@Bean
public MultipartAwareFormHttpMessageConverter simpleConverter() {
MultipartAwareFormHttpMessageConverter converter =
new MultipartAwareFormHttpMessageConverter();
converter.setMultipartFileReader(simpleFileReader());
return converter;
}
@Bean
public IntegrationFlow simpleUploadFlow() {
return IntegrationFlow
.from(Http.inboundGateway("/upload/simple")
.messageConverters(simpleConverter()))
.<MultiValueMap<String, Object>>handle((payload, headers) -> {
Object fileContent = payload.getFirst("file");
if (fileContent instanceof String) {
// Text file content
String text = (String) fileContent;
return processTextFile(text);
} else {
// Binary file content
byte[] data = (byte[]) fileContent;
return processBinaryFile(data);
}
})
.get();
}
}MultipartFile implementation representing an uploaded file with content in memory (byte array) or in a File. Provides access to file content after the HTTP request scope ends.
public class UploadedMultipartFile implements MultipartFile {
/**
* Creates instance with File-based content.
* Content is stored in the provided file.
*
* @param file the file containing content
* @param size the file size
* @param contentType the content type
* @param formParameterName the form parameter name
* @param originalFilename the original filename
*/
public UploadedMultipartFile(
File file,
long size,
String contentType,
String formParameterName,
String originalFilename);
/**
* Creates instance with byte array content.
* Content is stored in memory.
*
* @param bytes the file content
* @param contentType the content type
* @param formParameterName the form parameter name
* @param originalFilename the original filename
*/
public UploadedMultipartFile(
byte[] bytes,
String contentType,
String formParameterName,
String originalFilename);
/**
* Returns form parameter name.
*
* @return the parameter name
*/
public String getName();
/**
* Returns file content as byte array.
* Reads from file or returns in-memory bytes.
*
* @return the file content
* @throws IOException if read error occurs
*/
public byte[] getBytes() throws IOException;
/**
* Returns content type.
*
* @return the content type
*/
public String getContentType();
/**
* Returns InputStream for file content.
*
* @return the input stream
* @throws IOException if error occurs
*/
public InputStream getInputStream() throws IOException;
/**
* Returns original filename from upload.
*
* @return the original filename
*/
public String getOriginalFilename();
/**
* Returns file size.
*
* @return the size in bytes
*/
public long getSize();
/**
* Checks if file is empty.
*
* @return true if size is 0
*/
public boolean isEmpty();
/**
* Transfers file content to destination File.
* Copies content to the specified file.
*
* @param dest the destination file
* @throws IOException if transfer error occurs
* @throws IllegalStateException if transfer not supported
*/
public void transferTo(File dest) throws IOException, IllegalStateException;
}Usage Example:
import org.springframework.integration.http.multipart.UploadedMultipartFile;
import org.springframework.web.multipart.MultipartFile;
public class FileProcessor {
public void processUploadedFile(MultipartFile multipartFile)
throws IOException {
if (multipartFile instanceof UploadedMultipartFile) {
UploadedMultipartFile uploaded = (UploadedMultipartFile) multipartFile;
// Access file properties
String originalName = uploaded.getOriginalFilename();
String contentType = uploaded.getContentType();
long size = uploaded.getSize();
System.out.println("Processing: " + originalName);
System.out.println("Type: " + contentType);
System.out.println("Size: " + size + " bytes");
// Read content
byte[] content = uploaded.getBytes();
// Or get input stream
try (InputStream input = uploaded.getInputStream()) {
// Process stream
processStream(input);
}
// Transfer to permanent location
File destination = new File("/var/app/files/" + originalName);
uploaded.transferTo(destination);
}
}
}Implementation of ServletServerHttpRequest that wraps a MultipartHttpServletRequest. Provides access to multipart content through Spring's MultipartRequest interface.
public class MultipartHttpInputMessage
extends ServletServerHttpRequest
implements MultipartRequest {
/**
* Creates instance wrapping multipart request.
*
* @param multipartServletRequest the multipart HTTP servlet request
*/
public MultipartHttpInputMessage(
MultipartHttpServletRequest multipartServletRequest);
/**
* Returns MultipartFile for given name.
*
* @param name the parameter name
* @return the multipart file or null
*/
public MultipartFile getFile(String name);
/**
* Returns Map of parameter name to MultipartFile.
*
* @return map of files
*/
public Map<String, MultipartFile> getFileMap();
/**
* Returns MultiValueMap of all MultipartFiles.
* Single parameter may have multiple files.
*
* @return multi-value map of files
*/
public MultiValueMap<String, MultipartFile> getMultiFileMap();
/**
* Returns Iterator of file parameter names.
*
* @return iterator of file names
*/
public Iterator<String> getFileNames();
/**
* Returns List of MultipartFiles for given name.
* Supports multiple files with same parameter name.
*
* @param name the parameter name
* @return list of multipart files
*/
public List<MultipartFile> getFiles(String name);
/**
* Returns MultiValueMap of request parameters.
*
* @return parameter map
*/
public MultiValueMap<String, String> getParameterMap();
/**
* Returns content type for given parameter or file name.
*
* @param paramOrFileName the parameter or file name
* @return the content type
*/
public String getMultipartContentType(String paramOrFileName);
}This class is typically used internally by the framework but can be used in custom converters or handlers.
Handle multiple file uploads in a single request:
@Bean
public IntegrationFlow multipleFileUploadFlow() {
FileCopyingMultipartFileReader reader =
new FileCopyingMultipartFileReader();
MultipartAwareFormHttpMessageConverter converter =
new MultipartAwareFormHttpMessageConverter();
converter.setMultipartFileReader(reader);
return IntegrationFlow
.from(Http.inboundGateway("/upload/multiple")
.requestMapping(m -> m
.methods(HttpMethod.POST)
.consumes("multipart/form-data"))
.messageConverters(converter)
.requestPayloadType(MultiValueMap.class))
.<MultiValueMap<String, Object>>handle((payload, headers) -> {
// Get all files
List<MultipartFile> files = new ArrayList<>();
// Extract multiple files from payload
for (String key : payload.keySet()) {
List<?> values = payload.get(key);
for (Object value : values) {
if (value instanceof MultipartFile) {
files.add((MultipartFile) value);
}
}
}
// Process all files
return fileService.processMultipleFiles(files);
})
.get();
}Handle file uploads with additional form fields:
@Bean
public IntegrationFlow fileWithMetadataFlow() {
DefaultMultipartFileReader reader = new DefaultMultipartFileReader();
MultipartAwareFormHttpMessageConverter converter =
new MultipartAwareFormHttpMessageConverter();
converter.setMultipartFileReader(reader);
return IntegrationFlow
.from(Http.inboundGateway("/upload/with-metadata")
.messageConverters(converter)
.requestPayloadType(MultiValueMap.class))
.<MultiValueMap<String, Object>>handle((payload, headers) -> {
// Extract file
MultipartFile file =
(MultipartFile) payload.getFirst("file");
// Extract metadata from form fields
String title = (String) payload.getFirst("title");
String description = (String) payload.getFirst("description");
String category = (String) payload.getFirst("category");
// Create metadata object
FileMetadata metadata = new FileMetadata();
metadata.setTitle(title);
metadata.setDescription(description);
metadata.setCategory(category);
// Process file with metadata
return fileService.processFileWithMetadata(file, metadata);
})
.get();
}Choose storage strategy based on file size or type:
@Configuration
public class ConditionalStorageConfig {
@Bean
public MultipartFileReader<?> conditionalFileReader() {
return new MultipartFileReader<Object>() {
private final DefaultMultipartFileReader memoryReader =
new DefaultMultipartFileReader();
private final FileCopyingMultipartFileReader fileReader =
new FileCopyingMultipartFileReader();
@Override
public Object readMultipartFile(MultipartFile multipartFile)
throws IOException {
// Store large files on disk (returns MultipartFile/UploadedMultipartFile)
if (multipartFile.getSize() > 5 * 1024 * 1024) { // 5MB
return fileReader.readMultipartFile(multipartFile);
}
// Store small files in memory (returns MultipartFile/UploadedMultipartFile)
else {
return memoryReader.readMultipartFile(multipartFile);
}
}
};
}
@Bean
public IntegrationFlow conditionalStorageFlow() {
MultipartAwareFormHttpMessageConverter converter =
new MultipartAwareFormHttpMessageConverter();
converter.setMultipartFileReader(conditionalFileReader());
return IntegrationFlow
.from(Http.inboundGateway("/upload/smart")
.messageConverters(converter))
.handle((payload, headers) -> {
// All files returned as MultipartFile (UploadedMultipartFile)
MultipartFile fileContent =
(MultipartFile) ((MultiValueMap<?, ?>) payload).getFirst("file");
return fileService.processFile(fileContent);
})
.get();
}
}Validate file types before processing:
@Bean
public IntegrationFlow fileValidationFlow() {
DefaultMultipartFileReader reader = new DefaultMultipartFileReader();
MultipartAwareFormHttpMessageConverter converter =
new MultipartAwareFormHttpMessageConverter();
converter.setMultipartFileReader(reader);
return IntegrationFlow
.from(Http.inboundGateway("/upload/validated")
.messageConverters(converter)
.requestPayloadType(MultiValueMap.class))
.<MultiValueMap<String, Object>>handle((payload, headers) -> {
MultipartFile file =
(MultipartFile) payload.getFirst("file");
// Validate file type
String contentType = file.getContentType();
if (!isAllowedContentType(contentType)) {
throw new IllegalArgumentException(
"File type not allowed: " + contentType);
}
// Validate file size
if (file.getSize() > 10 * 1024 * 1024) { // 10MB
throw new IllegalArgumentException(
"File too large: " + file.getSize());
}
// Validate file extension
String filename = file.getOriginalFilename();
if (filename != null && !hasAllowedExtension(filename)) {
throw new IllegalArgumentException(
"File extension not allowed: " + filename);
}
return fileService.processValidatedFile(file);
})
.get();
}
private boolean isAllowedContentType(String contentType) {
return contentType != null && (
contentType.startsWith("image/") ||
contentType.equals("application/pdf") ||
contentType.equals("text/plain")
);
}
private boolean hasAllowedExtension(String filename) {
String[] allowedExtensions = {".jpg", ".png", ".pdf", ".txt"};
for (String ext : allowedExtensions) {
if (filename.toLowerCase().endsWith(ext)) {
return true;
}
}
return false;
}Process uploaded files asynchronously:
@Configuration
public class AsyncFileProcessingConfig {
@Bean
public IntegrationFlow asyncFileUploadFlow() {
FileCopyingMultipartFileReader reader =
new FileCopyingMultipartFileReader();
MultipartAwareFormHttpMessageConverter converter =
new MultipartAwareFormHttpMessageConverter();
converter.setMultipartFileReader(reader);
return IntegrationFlow
.from(Http.inboundGateway("/upload/async")
.messageConverters(converter)
.requestPayloadType(MultiValueMap.class))
.<MultiValueMap<String, Object>>handle((payload, headers) -> {
MultipartFile uploadedFile = (MultipartFile) payload.getFirst("file");
// Generate tracking ID
String trackingId = UUID.randomUUID().toString();
// Store tracking info
uploadTracking.put(trackingId, "PROCESSING");
// Return tracking ID immediately
Map<String, Object> response = new HashMap<>();
response.put("trackingId", trackingId);
response.put("status", "ACCEPTED");
return response;
})
// Send to async processing channel
.channel("fileProcessingChannel")
.get();
}
@Bean
public IntegrationFlow fileProcessingFlow() {
return IntegrationFlow
.from("fileProcessingChannel")
.handle(Message.class, (message, headers) -> {
String trackingId = (String) headers.get("trackingId");
MultipartFile file = (MultipartFile) headers.get("uploadedFile");
try {
// Process file asynchronously
fileService.processFile(file);
uploadTracking.put(trackingId, "COMPLETED");
} catch (Exception e) {
uploadTracking.put(trackingId, "FAILED");
}
return null;
})
.get();
}
}Handle image uploads with transformation:
@Bean
public IntegrationFlow imageUploadFlow() {
FileCopyingMultipartFileReader reader =
new FileCopyingMultipartFileReader();
reader.setPrefix("img_");
reader.setSuffix(".tmp");
MultipartAwareFormHttpMessageConverter converter =
new MultipartAwareFormHttpMessageConverter();
converter.setMultipartFileReader(reader);
return IntegrationFlow
.from(Http.inboundGateway("/upload/image")
.requestMapping(m -> m
.methods(HttpMethod.POST)
.consumes("multipart/form-data"))
.messageConverters(converter)
.requestPayloadType(MultiValueMap.class))
.<MultiValueMap<String, Object>>handle((payload, headers) -> {
MultipartFile imageFile = (MultipartFile) payload.getFirst("image");
// Validate it's an image
String contentType = imageFile.getContentType();
if (contentType == null || !contentType.startsWith("image/")) {
throw new IllegalArgumentException("Not an image file");
}
// Transform image (resize, optimize, etc.)
// Convert MultipartFile to File if needed for processing
File processedImage = imageService.processImage(imageFile);
// Generate thumbnail
File thumbnail = imageService.generateThumbnail(processedImage);
// Return both files
Map<String, Object> response = new HashMap<>();
response.put("original", processedImage.getAbsolutePath());
response.put("thumbnail", thumbnail.getAbsolutePath());
return response;
})
.get();
}Handle batch file uploads with progress tracking:
@Bean
public IntegrationFlow batchFileUploadFlow() {
DefaultMultipartFileReader reader = new DefaultMultipartFileReader();
MultipartAwareFormHttpMessageConverter converter =
new MultipartAwareFormHttpMessageConverter();
converter.setMultipartFileReader(reader);
return IntegrationFlow
.from(Http.inboundGateway("/upload/batch")
.messageConverters(converter)
.requestPayloadType(MultiValueMap.class))
.<MultiValueMap<String, Object>>handle((payload, headers) -> {
List<MultipartFile> files = new ArrayList<>();
// Collect all uploaded files
payload.values().forEach(values ->
values.forEach(value -> {
if (value instanceof MultipartFile) {
files.add((MultipartFile) value);
}
}));
// Generate batch ID
String batchId = UUID.randomUUID().toString();
// Process files with progress tracking
List<String> processedFiles = new ArrayList<>();
int total = files.size();
int current = 0;
for (MultipartFile file : files) {
current++;
updateProgress(batchId, current, total);
String processedPath = fileService.processFile(file);
processedFiles.add(processedPath);
}
// Return results
Map<String, Object> response = new HashMap<>();
response.put("batchId", batchId);
response.put("totalFiles", total);
response.put("processedFiles", processedFiles);
return response;
})
.get();
}