Spring Integration HTTP support module that provides HTTP inbound and outbound channel adapters and gateways for enterprise integration patterns
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();
}Install with Tessl CLI
npx tessl i tessl/maven-spring-integration-http