quarkusio/quarkus · error · java.lang.RuntimeException

Static method '' cannot be annotated with the @PermissionChe

Error message

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

What it means

@PermissionChecker methods must be instance members of a CDI bean because the security runtime invokes them via the bean. Static methods cannot be proxied/called as bean members, so the deployment rejects them at build time.

Source

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

            // 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"
                                    .formatted(toString(checkerMethod)));
                }
                boolean isBlocking = checkerMethod.hasDeclaredAnnotation(BLOCKING);
                if (isBlocking && isReactive) {
                    throw new IllegalArgumentException("""

View on GitHub (pinned to e1c734241f)

Solutions

  1. Remove the 'static' modifier so the method is an instance member of a CDI bean
  2. Ensure the enclosing class is a CDI bean (e.g. @ApplicationScoped) so the checker can be invoked
  3. If logic is shared statically, keep the static helper and have a non-static @PermissionChecker method call it

Example fix

// before
@PermissionChecker("can-view")
static boolean canView(Identity id) { return id.hasRole("viewer"); }
// after
@ApplicationScoped
class SecurityChecks {
    @PermissionChecker("can-view")
    boolean canView(Identity id) { return id.hasRole("viewer"); }
}
Defensive patterns

Strategy: validation

Validate before calling

for (Method m : SecurityChecks.class.getDeclaredMethods()) {
    if (m.isAnnotationPresent(PermissionChecker.class) && Modifier.isStatic(m.getModifiers())) {
        throw new IllegalStateException("@PermissionChecker method must not be static: " + m.getName());
    }
}

Type guard

boolean isInstanceChecker(java.lang.reflect.Method m) {
    return m.isAnnotationPresent(PermissionChecker.class) && !Modifier.isStatic(m.getModifiers());
}

Try / catch

try {
    appBootstrap();
} catch (RuntimeException e) {
    if (e.getMessage() != null && e.getMessage().contains("Static method") && e.getMessage().contains("@PermissionChecker")) {
        throw new IllegalStateException("Convert static @PermissionChecker to an instance method on a CDI bean", e);
    }
    throw e;
}

Prevention

When it happens

Trigger: Annotating a static method with @PermissionChecker and building the application; the build step getPermissionCheckers scans the Jandex index and throws.

Common situations: Developer writes a stateless utility-style checker as a static method (a common Java idiom) inside a bean or plain class, e.g. static boolean isOwner(...) annotated with @PermissionChecker, and the build fails.

Related errors


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