{"record":{"id":"7ff8969b0859cdd4","repo":"quarkusio/quarkus","slug":"permissionchecker-method-s-declares-checked-ex","errorCode":null,"errorMessage":"@PermissionChecker method '%s' declares checked exceptions which is not allowed","messagePattern":"@PermissionChecker method '(.+?)' declares checked exceptions which is not allowed","errorType":"exception","errorClass":"java.lang.RuntimeException","httpStatus":null,"severity":"error","filePath":"extensions/security/deployment/src/main/java/io/quarkus/security/deployment/PermissionSecurityChecks.java","lineNumber":202,"sourceCode":"            if (checkerMethod.returnType().kind() == Type.Kind.PARAMETERIZED_TYPE) {\n                var parametrizedType = checkerMethod.returnType().asParameterizedType();\n                boolean returnsUni = UNI.equals(parametrizedType.name());\n                boolean booleanArg = parametrizedType.arguments().size() == 1\n                        && BOOLEAN.equals(parametrizedType.arguments().get(0).name());\n                return returnsUni && booleanArg;\n            }\n            return false;\n        }\n\n        private static boolean isPrimitiveBoolean(MethodInfo checkerMethod) {\n            return checkerMethod.returnType().kind() == Type.Kind.PRIMITIVE\n                    && Primitive.BOOLEAN.equals(checkerMethod.returnType().asPrimitiveType().primitive());\n        }\n\n        private static MethodInfo getGeneratedPermissionConstructor(MethodInfo checkerMethod,\n                MethodParameterMapper[] paramMappers) {\n            if (!checkerMethod.exceptions().isEmpty()) {\n                throw new RuntimeException(\"@PermissionChecker method '%s' declares checked exceptions which is not allowed\"\n                        .formatted(toString(checkerMethod)));\n            }\n\n            // Permission constructor: permission name, <<secured-method-parameters>>...\n            // Permission checker method: [optionally at any place SecurityIdentity], <<secured-method-parameters>>...\n            // that is constructor param length great or equal to checker method param length\n            int constructorParameterCount = checkerMethod.parametersCount() + (hasSecurityIdentityParam(checkerMethod) ? 0 : 1);\n            final Type[] constructorParameterTypes = new Type[constructorParameterCount];\n            final String[] constructorParameterNames = new String[constructorParameterCount];\n\n            constructorParameterNames[0] = \"permissionName\";\n            constructorParameterTypes[0] = Type.create(String.class);\n\n            for (int i = 0, j = 1; i < checkerMethod.parametersCount(); i++) {\n                var parameterType = checkerMethod.parameterType(i);\n                if (SECURITY_IDENTITY_NAME.equals(parameterType.name())) {\n                    paramMappers[i] = new MethodParameterMapper(i, MethodParameterMapper.SECURITY_IDENTITY_IDX);\n                } else {","sourceCodeStart":184,"sourceCodeEnd":220,"githubUrl":"https://github.com/quarkusio/quarkus/blob/e1c734241f34c7919086ceb4c9262b4a58f6de44/extensions/security/deployment/src/main/java/io/quarkus/security/deployment/PermissionSecurityChecks.java#L184-L220","documentation":"A @PermissionChecker method declares thrown checked exceptions. Quarkus generates a Permission constructor mirroring the checker's parameters and cannot propagate checked exceptions through it, so checked exceptions on permission checker methods are disallowed at build time.","triggerScenarios":"Declaring 'throws SomeCheckedException' (or extending a class that does) on a method annotated with @PermissionChecker, discovered via MethodInfo.exceptions() during build.","commonSituations":"Checker methods calling IO/DAO code that throws checked exceptions (SQLException, IOException); wrapping legacy service methods as permission checkers without adapting their signature.","solutions":["Remove the 'throws' clause and handle the exception inside the method (return false or wrap)","Catch the checked exception inside the checker and translate it to a boolean/Uni<Boolean> result","Move the throwing logic out of the checker into a helper that throws a RuntimeException"],"exampleFix":"// before\n@PermissionChecker(\"book:read\")\nboolean canRead(Book b) throws SQLException { ... }\n\n// after\n@PermissionChecker(\"book:read\")\nboolean canRead(Book b) {\n    try { ...; return true; } catch (SQLException e) { throw new RuntimeException(e); }\n}","handlingStrategy":"validation","validationCode":"for (Class<?> ex : MyBean.class.getMethod(\"canRead\").getExceptionTypes()) {\n    if (!RuntimeException.class.isAssignableFrom(ex) && !Error.class.isAssignableFrom(ex))\n        throw new IllegalStateException(\"@PermissionChecker must not declare checked exception: \" + ex);\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Never add 'throws' clauses to permission checker methods","Catch and translate checked exceptions inside the checker body","Wrap throwing DAOs in non-throwing service helpers"],"tags":["quarkus","security","build-time","checked-exception"],"backgroundTag":"checked-exception-not-allowed","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"}