quarkusio/quarkus · error · java.lang.RuntimeException

Private method '' cannot be annotated with the @PermissionCh

Error message

Private method '' cannot be annotated with the @PermissionChecker annotation

What it means

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.

Source

Thrown at extensions/security/deployment/src/main/java/io/quarkus/security/deployment/PermissionSecurityChecks.java:133

                    // variable 'instances' won't be modified
                    return 0;
                }
            });
            // this needs to be immutable as build steps that gather security checks
            // and produce permission augmenter can and did in past run concurrently
            this.permissionInstances = Collections.unmodifiableList(instances);
            this.permissionNameToChecker = Collections.unmodifiableMap(getPermissionCheckers(index));
        }

        private static Map<String, PermissionCheckerMetadata> getPermissionCheckers(IndexView index) {
            int permissionCheckerIndex = 0; // this ensures generated QuarkusPermission name is unique
            var permissionCheckers = new HashMap<String, PermissionCheckerMetadata>();
            for (var annotationInstance : index.getAnnotations(PERMISSION_CHECKER_NAME)) {
                var checkerMethod = annotationInstance.target().asMethod();
                if (Modifier.isPrivate(checkerMethod.flags())) {
                    // we generate QuarkusPermission in the same package as where the @PermissionChecker is detected
                    // so the checker method must be either public or package-private
                    throw new RuntimeException("Private method '" + toString(checkerMethod)
                            + "' cannot be annotated with the @PermissionChecker annotation");
                }
                if (Modifier.isStatic(checkerMethod.flags())) {
                    // checkers must be CDI bean member methods for now, so the checker method must not be static
                    throw new RuntimeException("Static method '" + toString(checkerMethod)
                            + "' cannot be annotated with the @PermissionChecker annotation");
                }
                boolean isReactive = isUniBoolean(checkerMethod);
                if (!isReactive && !isPrimitiveBoolean(checkerMethod)) {
                    throw new RuntimeException(("@PermissionChecker method '%s' has return type '%s', but only " +
                            "supported return types are 'boolean' and 'Uni<Boolean>'. ")
                            .formatted(toString(checkerMethod), checkerMethod.returnType().name()));
                }

                var permissionName = annotationInstance.value().asString();
                if (permissionName.isBlank()) {
                    throw new IllegalArgumentException(
                            "@PermissionChecker annotation placed on the '%s' attribute 'value' must not be blank"

View on GitHub (pinned to e1c734241f)

Solutions

  1. Change the method visibility to package-private (default) or public
  2. Keep the bean class in a package where the generated QuarkusPermission can access the method (same package is used)
  3. 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

Example fix

// before
@PermissionChecker("can-delete")
private boolean canDelete(Identity identity) { return identity.hasRole("admin"); }
// after
@PermissionChecker("can-delete")
boolean canDelete(Identity identity) { return identity.hasRole("admin"); }
Defensive patterns

Strategy: validation

Validate before calling

// at build time in user tests, or as a convention check
for (Method m : SecurityChecks.class.getDeclaredMethods()) {
    if (m.isAnnotationPresent(PermissionChecker.class) && Modifier.isPrivate(m.getModifiers())) {
        throw new IllegalStateException("@PermissionChecker method must not be private: " + m.getName());
    }
}

Type guard

boolean isValidCheckerTarget(java.lang.reflect.Method m) {
    int mods = m.getModifiers();
    return m.isAnnotationPresent(PermissionChecker.class)
        && !Modifier.isPrivate(mods) && !Modifier.isStatic(mods);
}

Try / catch

try {
    appBootstrap();
} catch (RuntimeException e) {
    if (e.getMessage() != null && e.getMessage().contains("cannot be annotated with the @PermissionChecker")) {
        throw new IllegalStateException("Fix @PermissionChecker visibility: use package-private or public, non-static", e);
    }
    throw e;
}

Prevention

When it happens

Trigger: Annotating a private method (inside a @ApplicationScoped/@RequestScoped CDI bean) with io.quarkus.security.PermissionChecker and building the application.

Common situations: 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.

Related errors


AI-assisted analysis of quarkusio/quarkus@e1c734241f (2026-09-05). Data as JSON: /api/errors/df707d978e18cdfc. Report an issue: GitHub.