{"record":{"id":"df707d978e18cdfc","repo":"quarkusio/quarkus","slug":"private-method-cannot-be-annotated-with-the-pe","errorCode":null,"errorMessage":"Private method '' cannot be annotated with the @PermissionChecker annotation","messagePattern":"Private method '' cannot be annotated with the @PermissionChecker annotation","errorType":"exception","errorClass":"java.lang.RuntimeException","httpStatus":null,"severity":"error","filePath":"extensions/security/deployment/src/main/java/io/quarkus/security/deployment/PermissionSecurityChecks.java","lineNumber":133,"sourceCode":"                    // variable 'instances' won't be modified\n                    return 0;\n                }\n            });\n            // this needs to be immutable as build steps that gather security checks\n            // and produce permission augmenter can and did in past run concurrently\n            this.permissionInstances = Collections.unmodifiableList(instances);\n            this.permissionNameToChecker = Collections.unmodifiableMap(getPermissionCheckers(index));\n        }\n\n        private static Map<String, PermissionCheckerMetadata> getPermissionCheckers(IndexView index) {\n            int permissionCheckerIndex = 0; // this ensures generated QuarkusPermission name is unique\n            var permissionCheckers = new HashMap<String, PermissionCheckerMetadata>();\n            for (var annotationInstance : index.getAnnotations(PERMISSION_CHECKER_NAME)) {\n                var checkerMethod = annotationInstance.target().asMethod();\n                if (Modifier.isPrivate(checkerMethod.flags())) {\n                    // we generate QuarkusPermission in the same package as where the @PermissionChecker is detected\n                    // so the checker method must be either public or package-private\n                    throw new RuntimeException(\"Private method '\" + toString(checkerMethod)\n                            + \"' cannot be annotated with the @PermissionChecker annotation\");\n                }\n                if (Modifier.isStatic(checkerMethod.flags())) {\n                    // checkers must be CDI bean member methods for now, so the checker method must not be static\n                    throw new RuntimeException(\"Static method '\" + toString(checkerMethod)\n                            + \"' cannot be annotated with the @PermissionChecker annotation\");\n                }\n                boolean isReactive = isUniBoolean(checkerMethod);\n                if (!isReactive && !isPrimitiveBoolean(checkerMethod)) {\n                    throw new RuntimeException((\"@PermissionChecker method '%s' has return type '%s', but only \" +\n                            \"supported return types are 'boolean' and 'Uni<Boolean>'. \")\n                            .formatted(toString(checkerMethod), checkerMethod.returnType().name()));\n                }\n\n                var permissionName = annotationInstance.value().asString();\n                if (permissionName.isBlank()) {\n                    throw new IllegalArgumentException(\n                            \"@PermissionChecker annotation placed on the '%s' attribute 'value' must not be blank\"","sourceCodeStart":115,"sourceCodeEnd":151,"githubUrl":"https://github.com/quarkusio/quarkus/blob/e1c734241f34c7919086ceb4c9262b4a58f6de44/extensions/security/deployment/src/main/java/io/quarkus/security/deployment/PermissionSecurityChecks.java#L115-L151","documentation":"Methods annotated with @PermissionChecker are used to generate a QuarkusPermission class in the same package as the bean. Because the generated class must be able to call the checker, private methods are rejected at deployment time. This is a build-time validation of the annotation's usage rules.","triggerScenarios":"Annotating a private method (inside a @ApplicationScoped/@RequestScoped CDI bean) with io.quarkus.security.PermissionChecker and building the application.","commonSituations":"Developer writes a private helper-style permission method following ordinary encapsulation habits, e.g. private boolean canDelete(User u) { ... } annotated with @PermissionChecker(\"delete\"), then mvn package fails.","solutions":["Change the method visibility to package-private (default) or public","Keep the bean class in a package where the generated QuarkusPermission can access the method (same package is used)","If the method is an internal helper, split the permission logic: keep the helper private and expose a package-private @PermissionChecker method that delegates to it"],"exampleFix":"// before\n@PermissionChecker(\"can-delete\")\nprivate boolean canDelete(Identity identity) { return identity.hasRole(\"admin\"); }\n// after\n@PermissionChecker(\"can-delete\")\nboolean canDelete(Identity identity) { return identity.hasRole(\"admin\"); }","handlingStrategy":"validation","validationCode":"// at build time in user tests, or as a convention check\nfor (Method m : SecurityChecks.class.getDeclaredMethods()) {\n    if (m.isAnnotationPresent(PermissionChecker.class) && Modifier.isPrivate(m.getModifiers())) {\n        throw new IllegalStateException(\"@PermissionChecker method must not be private: \" + m.getName());\n    }\n}","typeGuard":"boolean isValidCheckerTarget(java.lang.reflect.Method m) {\n    int mods = m.getModifiers();\n    return m.isAnnotationPresent(PermissionChecker.class)\n        && !Modifier.isPrivate(mods) && !Modifier.isStatic(mods);\n}","tryCatchPattern":"try {\n    appBootstrap();\n} catch (RuntimeException e) {\n    if (e.getMessage() != null && e.getMessage().contains(\"cannot be annotated with the @PermissionChecker\")) {\n        throw new IllegalStateException(\"Fix @PermissionChecker visibility: use package-private or public, non-static\", e);\n    }\n    throw e;\n}","preventionTips":["Declare permission checkers package-private by default","Keep checkers as instance methods on @ApplicationScoped beans","Run the Quarkus security quickstart/conventions when introducing @PermissionChecker","Add an ArchUnit/convention test enforcing non-private checker methods"],"tags":["security","cdi","build-time","annotation-validation"],"backgroundTag":"invalid-annotation-target","analyzedSha":"e1c734241f34c7919086ceb4c9262b4a58f6de44","analyzedAt":"2026-09-05T17:01:29.979Z","contentChangedAt":"2026-09-05T17:01:29.979Z","schemaVersion":2},"datasetVersion":"2026-09-12T22:17:10.623Z"}