{"record":{"id":"5d57ad0e46114821","repo":"quarkusio/quarkus","slug":"endpoints-that-produce-a-multipart-result-can-only","errorCode":null,"errorMessage":"Endpoints that produce a Multipart result can only be used on blocking methods. Offending method is '%s#%s'","messagePattern":"Endpoints that produce a Multipart result can only be used on blocking methods\\. Offending method is '(.+?)#(.+?)'","errorType":"validation","errorClass":"DeploymentException","httpStatus":null,"severity":"error","filePath":"independent-projects/resteasy-reactive/common/processor/src/main/java/org/jboss/resteasy/reactive/common/processor/EndpointIndexer.java","lineNumber":783,"sourceCode":"            boolean blocking = isBlocking(currentMethodInfo, defaultBlocking);\n            boolean runOnVirtualThread = isRunOnVirtualThread(currentMethodInfo, blocking, defaultBlocking);\n            // we want to allow \"overriding\" the blocking/non-blocking setting from an implementation class\n            // when the class defining the annotations is an interface\n            if (!actualEndpointInfo.equals(currentClassInfo) && Modifier.isInterface(currentClassInfo.flags())) {\n                MethodInfo actualMethodInfo = actualEndpointInfo.method(currentMethodInfo.name(),\n                        currentMethodInfo.parameterTypes().toArray(new Type[0]));\n                if (actualMethodInfo != null) {\n                    //we don't pass AUTOMATIC here, as the method signature would be the same, so the same determination\n                    //would be reached for a default\n                    blocking = isBlocking(actualMethodInfo,\n                            blocking ? BlockingDefault.BLOCKING : BlockingDefault.NON_BLOCKING);\n                    runOnVirtualThread = isRunOnVirtualThread(actualMethodInfo, blocking,\n                            blocking ? BlockingDefault.BLOCKING : BlockingDefault.NON_BLOCKING);\n                }\n            }\n\n            if (returnsMultipart && !blocking) {\n                throw new DeploymentException(\n                        String.format(\n                                \"Endpoints that produce a Multipart result can only be used on blocking methods. Offending method is '%s#%s'\",\n                                currentMethodInfo.declaringClass().name(), currentMethodInfo));\n            }\n\n            methodContext.put(METHOD_PRODUCES, produces);\n            ResourceMethod method = createResourceMethod(currentMethodInfo, actualEndpointInfo, methodContext)\n                    .setHttpMethod(httpMethod == null ? null : httpAnnotationToMethod.get(httpMethod))\n                    .setPath(sanitizePath(methodPath))\n                    .setConsumes(consumes)\n                    .setProduces(produces)\n                    .setNameBindingNames(nameBindingNames)\n                    .setName(currentMethodInfo.name())\n                    .setBlocking(blocking)\n                    .setRunOnVirtualThread(runOnVirtualThread)\n                    .setSuspended(suspended)\n                    .setSse(sse)\n                    .setEncoded(currentMethodInfo.hasDeclaredAnnotation(ENCODED))","sourceCodeStart":765,"sourceCodeEnd":801,"githubUrl":"https://github.com/quarkusio/quarkus/blob/e1c734241f34c7919086ceb4c9262b4a58f6de44/independent-projects/resteasy-reactive/common/processor/src/main/java/org/jboss/resteasy/reactive/common/processor/EndpointIndexer.java#L765-L801","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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)"],"exampleFix":"// before\n@GET\n@Produces(MediaType.MULTIPART_FORM_DATA)\n@NonBlocking\npublic Multi<Part> stream() { ... }\n// after\n@GET\n@Produces(MediaType.MULTIPART_FORM_DATA)\n@Blocking\npublic Multi<Part> stream() { ... }","handlingStrategy":"validation","validationCode":"Method m = MyResource.class.getMethod(\"stream\");\nboolean nonBlocking = m.isAnnotationPresent(NonBlocking.class)\n    || MyResource.class.isAnnotationPresent(NonBlocking.class);\nProduces produces = m.getAnnotation(Produces.class);\nboolean multipart = produces != null && java.util.Arrays.stream(produces.value())\n    .anyMatch(mt -> mt.contains(\"multipart\"));\nif (multipart && nonBlocking) throw new IllegalStateException(\"Multipart-producing endpoint must be blocking\");","typeGuard":null,"tryCatchPattern":null,"preventionTips":["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"],"tags":["quarkus","resteasy-reactive","multipart","blocking","deployment"],"backgroundTag":"non-blocking-endpoint-io-restriction","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"}