quarkusio/quarkus · error · IllegalStateException
Negotiation or dynamic media type resolution for Multi is on
Error message
Negotiation or dynamic media type resolution for Multi is only supported when using 'org.jboss.resteasy.reactive.RestMulti'
What it means
PublisherResponseHandler.handle throws this IllegalStateException when a resource method returns a reactive Multi (streaming/SSE) but no @Produces media type is available at runtime (ServerMediaType produces == null) and the result is not a org.jboss.resteasy.reactive.RestMulti. Media type negotiation/dynamic resolution for streaming Multi results requires RestMulti, because only it carries the default media type needed when none was declared. This is a server-side programming error, not a client error.
Source
Thrown at independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/handlers/PublisherResponseHandler.java:284
@Override
public void handle(ResteasyReactiveRequestContext requestContext) throws Exception {
// FIXME: handle Response with entity being a Multi
Object requestContextResult = requestContext.getResult();
if (requestContextResult instanceof org.reactivestreams.Publisher) {
requestContextResult = AdaptersToFlow.publisher((org.reactivestreams.Publisher<?>) requestContextResult);
}
if (requestContextResult instanceof Publisher<?> result) {
// FIXME: if we make a pretend Response and go through the normal route, we will get
// media type negotiation and fixed entity writer set up, perhaps it's better than
// cancelling the normal route?
// or make this SSE produce build-time
ServerMediaType produces = requestContext.getTarget().getProduces();
if (produces == null) {
if (result instanceof RestMulti) {
produces = REST_MULTI_DEFAULT_SERVER_MEDIA_TYPE;
} else {
throw new IllegalStateException(
"Negotiation or dynamic media type resolution for Multi is only supported when using 'org.jboss.resteasy.reactive.RestMulti'");
}
}
MediaType[] mediaTypes = produces.getSortedOriginalMediaTypes();
if (mediaTypes.length != 1) {
throw new IllegalStateException(
"Negotiation or dynamic media type resolution for Multi is only supported when using 'org.jboss.resteasy.reactive.RestMulti'");
}
MediaType mediaType = mediaTypes[0];
requestContext.setResponseContentType(mediaType);
// this is the non-async return type
requestContext.setGenericReturnType(requestContext.getTarget().getReturnType());
if (mediaType.isCompatible(MediaType.SERVER_SENT_EVENTS_TYPE)) {
handleSse(requestContext, result);
} else {
requestContext.suspend();View on GitHub (pinned to e1c734241f)
Solutions
- Annotate the resource method with @Produces (e.g. MediaType.SERVER_SENT_EVENTS or APPLICATION_JSON) so a ServerMediaType exists.
- Change the return type to org.jboss.resteasy.reactive.RestMulti when media type negotiation without a fixed @Produces is needed.
- If neither streaming nor negotiation is intended, return a plain Uni/Response instead of Multi.
Example fix
// before
public Multi<String> stream() { return multi; }
// after
@Produces(MediaType.SERVER_SENT_EVENTS)
public Multi<String> stream() { return multi; } Defensive patterns
Strategy: try-catch
Validate before calling
// build-time sanity check for resource methods returning Multi
if (methodReturnsMulti && !usesRestMulti && producesAnnotation == null) {
throw new IllegalStateException(
"Method " + method + " returns Multi: add @Produces or return RestMulti");
} Type guard
boolean isRestMulti(Object result) {
return result instanceof org.jboss.resteasy.reactive.RestMulti;
} Try / catch
try {
return multiEndpoint.call();
} catch (IllegalStateException e) {
if (e.getMessage().contains("RestMulti")) {
throw new IllegalStateException("Server resource missing @Produces or RestMulti return type", e);
}
throw e;
} Prevention
- Always annotate streaming endpoints returning Multi with @Produces.
- Use RestMulti when media type negotiation for streams is required.
- Add an arquillian/integration test that invokes every Multi-returning endpoint.
When it happens
Trigger: A resource method returns io.smallrye.mutiny.Multi without @Produces (so requestContext.getTarget().getProduces() is null) and the result is a plain Multi rather than RestMulti — the else branch at line 284 throws. E.g. `public Multi<String> stream() { ... }` with no @Produces annotation and no RestMulti wrapper.
Common situations: Migrating an SSE/streaming endpoint and accidentally removing the @Produces annotation; returning Multi<String> for JSON streaming without knowing RestMulti is required for negotiation; mixing plain Multi returns with endpoints expected to negotiate media types dynamically.
Related errors
- Expected a ClientInvocationBuilder
- Expected a ClientInvocationBuilder
- dummy2
- Extra steps left over
- Unsupported value type: %s
AI-assisted analysis of quarkusio/quarkus@e1c734241f (2026-09-05).
Data as JSON: /api/errors/c35e80bb20e507d3.
Report an issue: GitHub.