quarkusio/quarkus · error · DeploymentException
Endpoints that produce a Multipart result can only be used o
Error message
Endpoints that produce a Multipart result can only be used on blocking methods. Offending method is '%s#%s'
What it means
An endpoint that returns a Multipart result (server-sent multipart response) must be a blocking method, because streaming multipart output relies on blocking I/O. EndpointIndexer enforces that methods with returnsMultipart true also have blocking=true, otherwise deployment fails.
Source
Thrown at independent-projects/resteasy-reactive/common/processor/src/main/java/org/jboss/resteasy/reactive/common/processor/EndpointIndexer.java:783
boolean blocking = isBlocking(currentMethodInfo, defaultBlocking);
boolean runOnVirtualThread = isRunOnVirtualThread(currentMethodInfo, blocking, defaultBlocking);
// we want to allow "overriding" the blocking/non-blocking setting from an implementation class
// when the class defining the annotations is an interface
if (!actualEndpointInfo.equals(currentClassInfo) && Modifier.isInterface(currentClassInfo.flags())) {
MethodInfo actualMethodInfo = actualEndpointInfo.method(currentMethodInfo.name(),
currentMethodInfo.parameterTypes().toArray(new Type[0]));
if (actualMethodInfo != null) {
//we don't pass AUTOMATIC here, as the method signature would be the same, so the same determination
//would be reached for a default
blocking = isBlocking(actualMethodInfo,
blocking ? BlockingDefault.BLOCKING : BlockingDefault.NON_BLOCKING);
runOnVirtualThread = isRunOnVirtualThread(actualMethodInfo, blocking,
blocking ? BlockingDefault.BLOCKING : BlockingDefault.NON_BLOCKING);
}
}
if (returnsMultipart && !blocking) {
throw new DeploymentException(
String.format(
"Endpoints that produce a Multipart result can only be used on blocking methods. Offending method is '%s#%s'",
currentMethodInfo.declaringClass().name(), currentMethodInfo));
}
methodContext.put(METHOD_PRODUCES, produces);
ResourceMethod method = createResourceMethod(currentMethodInfo, actualEndpointInfo, methodContext)
.setHttpMethod(httpMethod == null ? null : httpAnnotationToMethod.get(httpMethod))
.setPath(sanitizePath(methodPath))
.setConsumes(consumes)
.setProduces(produces)
.setNameBindingNames(nameBindingNames)
.setName(currentMethodInfo.name())
.setBlocking(blocking)
.setRunOnVirtualThread(runOnVirtualThread)
.setSuspended(suspended)
.setSse(sse)
.setEncoded(currentMethodInfo.hasDeclaredAnnotation(ENCODED))View on GitHub (pinned to e1c734241f)
Solutions
- Mark the method @Blocking so it is executed on the worker thread pool
- Remove @NonBlocking/@RunOnVirtualThread from the method or class
- Restructure the endpoint to avoid multipart output if it must remain non-blocking (e.g. emit a different media type)
Example fix
// before
@GET
@Produces(MediaType.MULTIPART_FORM_DATA)
@NonBlocking
public Multi<Part> stream() { ... }
// after
@GET
@Produces(MediaType.MULTIPART_FORM_DATA)
@Blocking
public Multi<Part> stream() { ... } Defensive patterns
Strategy: validation
Validate before calling
Method m = MyResource.class.getMethod("stream");
boolean nonBlocking = m.isAnnotationPresent(NonBlocking.class)
|| MyResource.class.isAnnotationPresent(NonBlocking.class);
Produces produces = m.getAnnotation(Produces.class);
boolean multipart = produces != null && java.util.Arrays.stream(produces.value())
.anyMatch(mt -> mt.contains("multipart"));
if (multipart && nonBlocking) throw new IllegalStateException("Multipart-producing endpoint must be blocking"); Prevention
- Never mark multipart-producing endpoints @NonBlocking or @RunOnVirtualThread
- Check class-level threading annotations before adding new endpoint types
- When switching return types to multipart, re-verify threading annotations
- Document that multipart streaming requires blocking execution in team guidelines
When it happens
Trigger: A resource method returns a type that produces a multipart response (e.g. Multi<Part>, MultipartResult, or annotated to produce multipart/form-data) while the method (or its class) is marked @NonBlocking, runs on virtual threads as non-blocking, or is otherwise resolved as non-blocking.
Common situations: Adding @NonBlocking or @RunOnVirtualThread to a streaming multipart endpoint; returning a Multi<...> multipart stream after annotating a controller as reactive; changing return type to multipart without revisiting threading annotations.
Related errors
- Method '' of class '' contains both @Blocking and @NonBlocki
- Class '' contains both @Blocking and @NonBlocking annotation
- Parameter: ${i} of the constructor of class '${resourceDotNa
- Unable to load handled exception type ${i.getProvidedType()}
- '@FormParam' and '@RestForm' cannot be used in a resource me
AI-assisted analysis of quarkusio/quarkus@e1c734241f (2026-09-05).
Data as JSON: /api/errors/5d57ad0e46114821.
Report an issue: GitHub.