{"record":{"id":"c35e80bb20e507d3","repo":"quarkusio/quarkus","slug":"negotiation-or-dynamic-media-type-resolution-for-m","errorCode":null,"errorMessage":"Negotiation or dynamic media type resolution for Multi is only supported when using 'org.jboss.resteasy.reactive.RestMulti'","messagePattern":"Negotiation or dynamic media type resolution for Multi is only supported when using 'org\\.jboss\\.resteasy\\.reactive\\.RestMulti'","errorType":"exception","errorClass":"IllegalStateException","httpStatus":null,"severity":"error","filePath":"independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/handlers/PublisherResponseHandler.java","lineNumber":284,"sourceCode":"\n    @Override\n    public void handle(ResteasyReactiveRequestContext requestContext) throws Exception {\n        // FIXME: handle Response with entity being a Multi\n        Object requestContextResult = requestContext.getResult();\n        if (requestContextResult instanceof org.reactivestreams.Publisher) {\n            requestContextResult = AdaptersToFlow.publisher((org.reactivestreams.Publisher<?>) requestContextResult);\n        }\n        if (requestContextResult instanceof Publisher<?> result) {\n            // FIXME: if we make a pretend Response and go through the normal route, we will get\n            // media type negotiation and fixed entity writer set up, perhaps it's better than\n            // cancelling the normal route?\n            // or make this SSE produce build-time\n            ServerMediaType produces = requestContext.getTarget().getProduces();\n            if (produces == null) {\n                if (result instanceof RestMulti) {\n                    produces = REST_MULTI_DEFAULT_SERVER_MEDIA_TYPE;\n                } else {\n                    throw new IllegalStateException(\n                            \"Negotiation or dynamic media type resolution for Multi is only supported when using 'org.jboss.resteasy.reactive.RestMulti'\");\n                }\n            }\n            MediaType[] mediaTypes = produces.getSortedOriginalMediaTypes();\n            if (mediaTypes.length != 1) {\n                throw new IllegalStateException(\n                        \"Negotiation or dynamic media type resolution for Multi is only supported when using 'org.jboss.resteasy.reactive.RestMulti'\");\n            }\n\n            MediaType mediaType = mediaTypes[0];\n            requestContext.setResponseContentType(mediaType);\n            // this is the non-async return type\n            requestContext.setGenericReturnType(requestContext.getTarget().getReturnType());\n\n            if (mediaType.isCompatible(MediaType.SERVER_SENT_EVENTS_TYPE)) {\n                handleSse(requestContext, result);\n            } else {\n                requestContext.suspend();","sourceCodeStart":266,"sourceCodeEnd":302,"githubUrl":"https://github.com/quarkusio/quarkus/blob/e1c734241f34c7919086ceb4c9262b4a58f6de44/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/handlers/PublisherResponseHandler.java#L266-L302","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":"// before\npublic Multi<String> stream() { return multi; }\n// after\n@Produces(MediaType.SERVER_SENT_EVENTS)\npublic Multi<String> stream() { return multi; }","handlingStrategy":"try-catch","validationCode":"// build-time sanity check for resource methods returning Multi\nif (methodReturnsMulti && !usesRestMulti && producesAnnotation == null) {\n    throw new IllegalStateException(\n        \"Method \" + method + \" returns Multi: add @Produces or return RestMulti\");\n}","typeGuard":"boolean isRestMulti(Object result) {\n    return result instanceof org.jboss.resteasy.reactive.RestMulti;\n}","tryCatchPattern":"try {\n    return multiEndpoint.call();\n} catch (IllegalStateException e) {\n    if (e.getMessage().contains(\"RestMulti\")) {\n        throw new IllegalStateException(\"Server resource missing @Produces or RestMulti return type\", e);\n    }\n    throw e;\n}","preventionTips":["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."],"tags":["rest","reactive","mutiny","sse","media-type"],"backgroundTag":"missing-produces-media-type","analyzedSha":"e1c734241f34c7919086ceb4c9262b4a58f6de44","analyzedAt":"2026-09-05T17:01:29.979Z","contentChangedAt":"2026-09-05T17:01:29.979Z","schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}