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
- 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
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
- 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
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
- Static method '' cannot be annotated with the @PermissionChe
- Unable to determine if the '${unsecuredMethod}' method shoul
- Interface '${declaringClass}' default method '${method}' has
- The '%s' CDI bean injection point was detected, but there is
- The @Blocking, @NonBlocking and @RunOnVirtualThread annotati
AI-assisted analysis of quarkusio/quarkus@e1c734241f (2026-09-05).
Data as JSON: /api/errors/df707d978e18cdfc.
Report an issue: GitHub.