quarkusio/quarkus · error · java.lang.RuntimeException

@PermissionChecker declared on method '%s', but no matching

Error message

@PermissionChecker declared on method '%s', but no matching CDI bean could be found for the declaring class '%s'.

What it means

Methods annotated with @PermissionChecker must live on a class that is a discoverable CDI bean, because Quarkus will obtain the checker by looking up a bean assignable to the declaring class during build. If bean discovery finds no matching bean for the declaring class, this error is thrown. Synthetic beans are explicitly not supported for permission checkers.

Source

Thrown at extensions/security/deployment/src/main/java/io/quarkus/security/deployment/SecurityProcessor.java:878

            // - this processor relies on the bean archive index (cycle: idx -> additional bean -> idx)
            // - we have injection points (=> better validation from Arc) as checker beans are only requested from this augmentor
            var syntheticBeanConfigurator = SyntheticBeanBuildItem
                    .configure(QuarkusPermissionSecurityIdentityAugmentor.class)
                    .addType(SecurityIdentityAugmentor.class)
                    // ATM we do get augmentors from CDI once, no need to keep the instance in the CDI container
                    .scope(Dependent.class)
                    .unremovable()
                    .addInjectionPoint(Type.create(BlockingSecurityExecutor.class))
                    .createWith(recorder.createPermissionAugmentor());

            checkerBuilder.instance.getPermissionCheckers().stream().forEach(checkerMethod -> {
                var checkerClassType = Type.create(checkerMethod.declaringClass().name(), Type.Kind.CLASS);

                // validate permission checker method's declaring class is a CDI bean
                // synthetic beans are not taken into consideration which makes them not supported
                var matchingBeans = beanDiscoveryFinishedBuildItem.beanStream().assignableTo(checkerClassType).collect();
                if (matchingBeans.isEmpty()) {
                    throw new RuntimeException(
                            """
                                    @PermissionChecker declared on method '%s', but no matching CDI bean could be found for the declaring class '%s'.
                                    """
                                    .formatted(checkerMethod.name(), checkerClassType.name()));
                }
                // Using @Dependent is problematic because we would have to destroy beans manually at some point (which?)
                matchingBeans.stream().filter(b -> BuiltinScope.DEPENDENT.getInfo().equals(b.getScope())).findFirst()
                        .ifPresent(bi -> {
                            throw new RuntimeException(
                                    """
                                            Found @PermissionChecker annotation instance declared on the CDI bean method '%s#%s'.
                                            The CDI bean is a dependent scoped bean, but only the '@Singleton' bean or normal scoped beans are supported
                                            """
                                            .formatted(checkerMethod.name(), checkerClassType.name()));
                        });

                syntheticBeanConfigurator.addInjectionPoint(checkerClassType);
            });

View on GitHub (pinned to e1c734241f)

Solutions

  1. Add a bean-defining annotation (e.g. @ApplicationScoped or @Singleton) to the class declaring the @PermissionChecker method.
  2. Verify the class is in a bean-discovery archive (has beans.xml or bean-defining annotations).
  3. If the class was produced by an extension as a synthetic bean, move the @PermissionChecker method to a regular CDI bean instead.
  4. Check that the declaring class name matches the actual bean class (no accidental subclass mismatch).

Example fix

// before
public class PaymentChecker {
    @PermissionChecker("pay")
    boolean canPay() { ... }
}

// after
@ApplicationScoped
public class PaymentChecker {
    @PermissionChecker("pay")
    boolean canPay() { ... }
}
Defensive patterns

Strategy: validation

Validate before calling

// ensure declaring class is a CDI bean before adding @PermissionChecker
boolean isBean = PaymentChecker.class.isAnnotationPresent(jakarta.enterprise.context.ApplicationScoped.class) || PaymentChecker.class.isAnnotationPresent(jakarta.inject.Singleton.class);

Prevention

When it happens

Trigger: Declaring a @PermissionChecker method on a class that is not a CDI bean — missing bean-defining annotation (@ApplicationScoped, @Singleton, etc.), or the class is only registered as a synthetic bean via an extension.

Common situations: Forgetting a scope annotation on the checker class; putting @PermissionChecker on a utility/helper class outside bean discovery; checker in a library package excluded from bean discovery; relying on @Unremovable synthetic beans.

Related errors


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