docs
reference
tessl install tessl/maven-io-quarkus--quarkus-resteasy-reactive@3.15.0A Jakarta REST implementation utilizing build time processing and Vert.x for high-performance REST endpoints with reactive capabilities in cloud-native environments.
Quarkus REST provides comprehensive support for multipart form data, enabling file uploads and downloads with easy access to file metadata and content.
import org.jboss.resteasy.reactive.multipart.FileUpload;
import org.jboss.resteasy.reactive.multipart.FileDownload;
import org.jboss.resteasy.reactive.multipart.FilePart;
import org.jboss.resteasy.reactive.PartType;
import org.jboss.resteasy.reactive.PartFilename;
import org.jboss.resteasy.reactive.MultipartForm;
import org.jboss.resteasy.reactive.RestForm;
import jakarta.ws.rs.core.MediaType;
import io.smallrye.common.annotation.Blocking;Base interface for file parts in multipart requests and responses.
public interface FilePart {
String name(); // Form field name
Path filePath(); // File path (deprecated, use specific methods)
String fileName(); // Original filename
long size(); // File size in bytes
String contentType(); // Content type
}Represents an uploaded file in a multipart request (server-side).
public interface FileUpload extends FilePart {
String ALL = "*"; // Constant to request all uploaded files
Path uploadedFile(); // Path to the uploaded file
// Inherited from FilePart
String name();
String fileName();
long size();
String contentType();
String charSet();
}Represents a file to be downloaded in a multipart response (server-side).
public interface FileDownload extends FilePart {
Path filePath(); // Path to the file to download
// Inherited from FilePart
String name();
String fileName();
long size();
String contentType();
String charSet();
}Specifies the media type for a multipart form field.
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface PartType {
String value(); // Media type
}Sets the filename for a multipart file upload (client-side).
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface PartFilename {
String value(); // Filename
}Marks a parameter as a multipart form. This annotation is deprecated and no longer needed.
@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
@Deprecated
public @interface MultipartForm {
}@POST
@Path("/upload")
@Consumes(MediaType.MULTIPART_FORM_DATA)
@Blocking
public Response uploadFile(@RestForm FileUpload file) throws IOException {
// Access file metadata
String filename = file.fileName();
String contentType = file.contentType();
long size = file.size();
// Read file content
byte[] content = Files.readAllBytes(file.uploadedFile());
// Process file
fileService.save(filename, content);
return Response.ok().build();
}@POST
@Path("/upload/multiple")
@Consumes(MediaType.MULTIPART_FORM_DATA)
@Blocking
public Response uploadMultipleFiles(@RestForm List<FileUpload> files) throws IOException {
for (FileUpload file : files) {
byte[] content = Files.readAllBytes(file.uploadedFile());
fileService.save(file.fileName(), content);
}
return Response.ok().build();
}
// Or request all files
@POST
@Path("/upload/all")
@Consumes(MediaType.MULTIPART_FORM_DATA)
@Blocking
public Response uploadAllFiles(@RestForm(FileUpload.ALL) List<FileUpload> files) throws IOException {
for (FileUpload file : files) {
processFile(file);
}
return Response.ok().build();
}public class FileUploadForm {
@RestForm("file")
public FileUpload file;
@RestForm
public String description;
@RestForm
@PartType(MediaType.APPLICATION_JSON)
public Metadata metadata;
}
@POST
@Path("/upload")
@Consumes(MediaType.MULTIPART_FORM_DATA)
@Blocking
public Response uploadWithMetadata(FileUploadForm form) throws IOException {
// Access file
byte[] content = Files.readAllBytes(form.file.uploadedFile());
// Access metadata
String description = form.description;
Metadata metadata = form.metadata;
fileService.save(form.file.fileName(), content, description, metadata);
return Response.ok().build();
}public class UploadRequest {
@RestForm
@PartType(MediaType.TEXT_PLAIN)
public String title;
@RestForm
@PartType(MediaType.TEXT_PLAIN)
public String description;
@RestForm("document")
public FileUpload document;
@RestForm("thumbnail")
public FileUpload thumbnail;
@RestForm
@PartType(MediaType.APPLICATION_JSON)
public List<String> tags;
}
@POST
@Path("/documents")
@Consumes(MediaType.MULTIPART_FORM_DATA)
@Blocking
public Response uploadDocument(UploadRequest request) throws IOException {
// Validate files
if (request.document == null) {
return Response.status(400).entity("Document required").build();
}
// Process main document
byte[] docContent = Files.readAllBytes(request.document.uploadedFile());
// Process optional thumbnail
byte[] thumbContent = null;
if (request.thumbnail != null) {
thumbContent = Files.readAllBytes(request.thumbnail.uploadedFile());
}
// Save with metadata
Document doc = documentService.create(
request.title,
request.description,
docContent,
thumbContent,
request.tags
);
return Response.status(201)
.entity(doc)
.build();
}import org.jboss.resteasy.reactive.multipart.FileDownload;
@GET
@Path("/download/{id}")
@Produces(MediaType.APPLICATION_OCTET_STREAM)
public Response downloadFile(@RestPath String id) {
FileData file = fileService.getFile(id);
return Response.ok(file.getContent())
.header("Content-Disposition", "attachment; filename=\"" + file.getName() + "\"")
.header("Content-Type", file.getContentType())
.build();
}
// Or using FileDownload
@GET
@Path("/download/{id}/part")
@Produces(MediaType.MULTIPART_FORM_DATA)
public FileDownload downloadFilePart(@RestPath String id) {
Path filePath = fileService.getFilePath(id);
return FileDownload.of(filePath);
}@GET
@Path("/download/{id}/stream")
@Produces(MediaType.APPLICATION_OCTET_STREAM)
public File downloadStream(@RestPath String id) {
// Return File directly for streaming
return fileService.getFile(id);
}
// Or with Path
@GET
@Path("/download/{id}/path")
@Produces(MediaType.APPLICATION_OCTET_STREAM)
public Path downloadPath(@RestPath String id) {
return fileService.getFilePath(id);
}@POST
@Path("/upload/async")
@Consumes(MediaType.MULTIPART_FORM_DATA)
public Uni<Response> uploadAsync(@RestForm FileUpload file) {
return Uni.createFrom().item(() -> {
try {
byte[] content = Files.readAllBytes(file.uploadedFile());
return content;
} catch (IOException e) {
throw new RuntimeException(e);
}
})
.chain(content -> fileService.saveAsync(file.fileName(), content))
.map(saved -> Response.ok().build());
}public class FileUploadForm {
@RestForm("file")
public FileUpload file;
@RestForm
public String description;
}
@POST
@Path("/upload")
@Consumes(MediaType.MULTIPART_FORM_DATA)
@Blocking
public Response uploadWithValidation(FileUploadForm form) {
FileUpload file = form.file;
// Validate file size
if (file.size() > 10 * 1024 * 1024) { // 10MB
return Response.status(413)
.entity("File too large (max 10MB)")
.build();
}
// Validate content type
if (!file.contentType().startsWith("image/")) {
return Response.status(400)
.entity("Only image files allowed")
.build();
}
// Validate filename
String filename = file.fileName();
if (!filename.matches("^[a-zA-Z0-9._-]+$")) {
return Response.status(400)
.entity("Invalid filename")
.build();
}
try {
byte[] content = Files.readAllBytes(file.uploadedFile());
fileService.save(filename, content);
return Response.ok().build();
} catch (IOException e) {
return Response.status(500)
.entity("Upload failed")
.build();
}
}Uploaded files are stored as temporary files. They are automatically deleted after the request completes. To keep the file, copy it to a permanent location:
@POST
@Path("/upload")
@Consumes(MediaType.MULTIPART_FORM_DATA)
@Blocking
public Response upload(@RestForm FileUpload file) throws IOException {
// Temporary file path
Path tempFile = file.uploadedFile();
// Copy to permanent location
Path permanentLocation = Paths.get("/data/uploads/" + file.fileName());
Files.copy(tempFile, permanentLocation, StandardCopyOption.REPLACE_EXISTING);
return Response.ok().build();
}Configure multipart handling via application.properties:
# Default charset for multipart parts
quarkus.rest.multipart.input-part.default-charset=UTF-8File I/O operations are blocking. Use @Blocking annotation to execute on a worker thread:
import io.smallrye.common.annotation.Blocking;
@POST
@Path("/upload")
@Consumes(MediaType.MULTIPART_FORM_DATA)
@Blocking // Execute on worker thread
public Response upload(@RestForm FileUpload file) throws IOException {
byte[] content = Files.readAllBytes(file.uploadedFile());
fileService.save(file.fileName(), content);
return Response.ok().build();
}Alternatively, use reactive approaches with Uni for non-blocking file operations.
Class for constructing multipart responses on the server.
public class MultipartFormDataOutput {
// Get all form data
Map<String, List<PartItem>> getAllFormData();
// Add form data parts
PartItem addFormData(String key, Object entity, MediaType mediaType);
PartItem addFormData(String key, Object entity, String genericType, MediaType mediaType);
PartItem addFormData(String key, Object entity, MediaType mediaType, String filename);
}Usage:
@GET
@Path("/download/multipart")
@Produces(MediaType.MULTIPART_FORM_DATA)
public MultipartFormDataOutput downloadMultipart() {
MultipartFormDataOutput output = new MultipartFormDataOutput();
// Add text part
output.addFormData("description", "File package", MediaType.TEXT_PLAIN_TYPE);
// Add JSON part
Metadata metadata = new Metadata("file1", 12345);
output.addFormData("metadata", metadata, MediaType.APPLICATION_JSON_TYPE);
// Add file part
File file = new File("/path/to/file.pdf");
output.addFormData("file", file, MediaType.APPLICATION_OCTET_STREAM_TYPE, "document.pdf");
return output;
}Interface for reading multipart request data on the server.
public interface MultipartFormDataInput {
/**
* Get all form values as a map
* @return Map of field names to collections of form values
*/
Map<String, Collection<FormValue>> getValues();
}Usage:
@POST
@Path("/upload/advanced")
@Consumes(MediaType.MULTIPART_FORM_DATA)
@Blocking
public Response uploadAdvanced(MultipartFormDataInput input) throws IOException {
Map<String, Collection<FormValue>> values = input.getValues();
// Get text fields
Collection<FormValue> descriptions = values.get("description");
String description = descriptions.iterator().next().getValue();
// Get file fields
Collection<FormValue> files = values.get("file");
for (FormValue fileValue : files) {
if (fileValue.isFileItem()) {
FileItem fileItem = fileValue.getFileItem();
processFile(fileItem.getFile(), fileItem.getFileName());
}
}
return Response.ok().build();
}Exception thrown when multipart part reading fails.
public class MultipartPartReadingException extends RuntimeException {
// Standard exception class for multipart errors
}Handling:
@ServerExceptionMapper
public RestResponse<ErrorResponse> handleMultipartError(MultipartPartReadingException ex) {
ErrorResponse error = new ErrorResponse();
error.setMessage("Failed to read multipart data: " + ex.getMessage());
return RestResponse.status(400, error);
}
@POST
@Path("/upload")
@Consumes(MediaType.MULTIPART_FORM_DATA)
@Blocking
public Response upload(@RestForm FileUpload file) {
try {
// Process upload
return Response.ok().build();
} catch (Exception e) {
throw new MultipartPartReadingException("Upload failed", e);
}
}