{"record":{"id":"225d5ace06926c33","repo":"quarkusio/quarkus","slug":"clientexceptionmapper-is-only-supported-on-static","errorCode":null,"errorMessage":"@ClientExceptionMapper is only supported on static methods of REST Client interfaces that take 'jakarta.ws.rs.core.Response' as a single parameter and return 'java.lang.RuntimeException'. Offending instance is '${className}#${methodName}'","messagePattern":"@ClientExceptionMapper is only supported on static methods of REST Client interfaces that take 'jakarta\\.ws\\.rs\\.core\\.Response' as a single parameter and return 'java\\.lang\\.RuntimeException'\\. Offending instance is '(.+?)#(.+?)'","errorType":"exception","errorClass":"IllegalStateException","httpStatus":null,"severity":"error","filePath":"extensions/resteasy-reactive/rest-client/deployment/src/main/java/io/quarkus/rest/client/reactive/deployment/ClientExceptionMapperHandler.java","lineNumber":102,"sourceCode":"                try {\n                    boolean returnsRuntimeException = RuntimeException.class.isAssignableFrom(\n                            Class.forName(returnTypeClassName, false, Thread.currentThread().getContextClassLoader()));\n                    if (returnsRuntimeException) {\n                        isValid = true;\n                    }\n                } catch (ClassNotFoundException ignored) {\n\n                }\n            }\n        }\n        if (!isValid) {\n            String message = DotNames.CLIENT_EXCEPTION_MAPPER\n                    + \" is only supported on static methods of REST Client interfaces that take 'jakarta.ws.rs.core.Response' as a single parameter and return 'java.lang.RuntimeException'.\";\n            if (targetMethod != null) {\n                message += \" Offending instance is '\" + targetMethod.declaringClass().name().toString() + \"#\"\n                        + targetMethod.name() + \"'\";\n            }\n            throw new IllegalStateException(message);\n        }\n\n        int priority = Priorities.USER;\n        AnnotationValue priorityAnnotationValue = instance.value(\"priority\");\n        if (priorityAnnotationValue != null) {\n            priority = priorityAnnotationValue.asInt();\n        }\n\n        ClassInfo restClientInterfaceClassInfo = targetMethod.declaringClass();\n        String generatedClassName = getGeneratedClassName(targetMethod);\n        final MethodInfo target = targetMethod;\n        final int finalPriority = priority;\n        gizmo.class_(generatedClassName, cc -> {\n            cc.implements_(ResteasyReactiveResponseExceptionMapper.class);\n            cc.defaultConstructor();\n            cc.method(\"toThrowable\", mc -> {\n                mc.returning(Throwable.class);\n                ParamVar response = mc.parameter(\"response\", Response.class);","sourceCodeStart":84,"sourceCodeEnd":120,"githubUrl":"https://github.com/quarkusio/quarkus/blob/e1c734241f34c7919086ceb4c9262b4a58f6de44/extensions/resteasy-reactive/rest-client/deployment/src/main/java/io/quarkus/rest/client/reactive/deployment/ClientExceptionMapperHandler.java#L84-L120","documentation":"@ClientExceptionMapper marks a static method in a REST client interface that maps a failed Response to a RuntimeException. Quarkus enforces a fixed signature: exactly one parameter of type jakarta.ws.rs.core.Response and a return type of java.lang.RuntimeException (or subclass). This IllegalStateException is thrown at build time when the annotated method deviates from that contract.","triggerScenarios":"@ClientExceptionMapper method that: is not static; takes zero, multiple, or non-Response parameters; returns Throwable/Exception/IOException instead of RuntimeException; is declared outside a REST client interface.","commonSituations":"Returning a custom checked exception; adding extra convenience parameters (e.g. URI or method) not allowed here; copy-pasting a @ServerExceptionMapper (which allows more signatures) into a client interface.","solutions":["Change the method to return RuntimeException (or a subclass) and accept exactly one jakarta.ws.rs.core.Response parameter","Make the method static and keep it inside the REST client interface","Wrap checked exceptions in a RuntimeException subclass before returning","If you need richer context, capture it in fields/closure-like static state or use ClientRedirectHandler-style alternatives"],"exampleFix":"// before\n@ClientExceptionMapper\nIOException map(Response r) { return new IOException(); } // wrong return type\n// after\n@ClientExceptionMapper\nstatic RuntimeException map(Response r) {\n    if (r.getStatus() == 404) return new NotFoundException();\n    return new InternalServerErrorException();\n}","handlingStrategy":"validation","validationCode":"// Validate signature before annotating:\n// static, one jakarta.ws.rs.core.Response param, returns RuntimeException\nstatic boolean validMapperSignature(Method m) {\n    return Modifier.isStatic(m.getModifiers())\n        && m.getParameterCount() == 1\n        && m.getParameterTypes()[0] == jakarta.ws.rs.core.Response.class\n        && RuntimeException.class.isAssignableFrom(m.getReturnType());\n}","typeGuard":null,"tryCatchPattern":"try {\n    handler.generateResponseExceptionMapper(classResult, instance);\n} catch (IllegalStateException e) {\n    if (e.getMessage().contains(\"ClientExceptionMapper\")) { /* correct signature per message */ }\n    throw e;\n}","preventionTips":["Copy the canonical signature: static RuntimeException name(Response r)","Do not return checked exceptions; wrap them in RuntimeException subclasses","Do not confuse with @ServerExceptionMapper, which allows more signatures","Keep the mapper method inside the REST client interface"],"tags":["rest-client","exception-mapper","annotation-processing","build-time"],"backgroundTag":"rest-client-annotation-misuse","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"}