quarkusio/quarkus · error · java.lang.RuntimeException

@PermissionsAllowed annotation placed on the '%s' has inclus

Error message

@PermissionsAllowed annotation placed on the '%s' has inclusive relation between its permissions.
                                            The '%s' permission has been matched with @PermissionChecker '%s', therefore you must also define
                                            a @PermissionChecker for '%s' permissions.
                                            

What it means

When @PermissionsAllowed uses inclusive permission relations and one permission in the group is backed by a @PermissionChecker method, Quarkus requires every permission in that group to have a matching @PermissionChecker. If a permission (e.g. 'delete') matched a checker but a sibling permission in the same inclusive annotation has none, deployment fails with this message naming the annotation target, matched permission, existing checker, and the missing permission set.

Source

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

                for (PermissionNameAndChecker checkerPermission : checkerPermissions) {
                    String permissionName = PermissionToActionUtil.parse(checkerPermission.permissionName).name();
                    for (var e : permissionToActions.entrySet()) {
                        PermissionNameAndChecker permissionNameKey = e.getKey();
                        // look for permission names that match our permission checker value (before action-to-perm separator)
                        // for example: read:it
                        if (permissionNameKey.checker == null && permissionNameKey.permissionName.equals(permissionName)) {
                            boolean hasActions = e.getValue() != null && !e.getValue().isEmpty();
                            final String permissionsJoinedWithActions;
                            if (hasActions) {
                                permissionsJoinedWithActions = e.getValue()
                                        .stream()
                                        .map(action -> permissionNameKey.permissionName + PERMISSION_TO_ACTION_SEPARATOR
                                                + action)
                                        .collect(Collectors.joining(", "));
                            } else {
                                permissionsJoinedWithActions = permissionNameKey.permissionName;
                            }
                            throw new RuntimeException(
                                    """
                                            @PermissionsAllowed annotation placed on the '%s' has inclusive relation between its permissions.
                                            The '%s' permission has been matched with @PermissionChecker '%s', therefore you must also define
                                            a @PermissionChecker for '%s' permissions.
                                            """
                                            .formatted(toString(annotationTarget), permissionName,
                                                    toString(checkerPermission.checker.checkerMethod),
                                                    permissionsJoinedWithActions));
                        }
                    }
                }
            }

            for (var permissionToAction : permissionToActions.entrySet().stream().sorted(PERMISSION_TO_ACTION_COMPARATOR)
                    .toList()) {
                final var permissionNameKey = permissionToAction.getKey();
                final var permissionActions = permissionToAction.getValue();
                final var key = new PermissionKey(permissionNameKey.permissionName, permissionActions, params, classType,

View on GitHub (pinned to e1c734241f)

Solutions

  1. Define a @PermissionChecker method for every permission listed in the inclusive @PermissionsAllowed annotation, e.g. add a method annotated @PermissionChecker("missing-permission") returning PermissionChecker in a CDI bean.
  2. Set inclusive=false on the annotation if an OR/one-of relation (each permission evaluated independently) is actually intended.
  3. Rename the permission in @PermissionsAllowed so it no longer accidentally matches a checker, if plain string permissions are wanted.

Example fix

// before
@PermissionsAllowed(value = {"read", "delete"}, inclusive = true) // 'read' has a checker, 'delete' does not

// after — add to a CDI bean:
@PermissionChecker("delete")
PermissionChecker deleteChecker() { return ctx -> ctx.getSubject().map(...).orElse(false); }
Defensive patterns

Strategy: validation

Validate before calling

// Convention: maintain a single @PermissionChecker per permission name in one config class,
// and a test that every permission listed in inclusive @PermissionsAllowed annotations has a checker.
// grep your sources: every @PermissionsAllowed(value={...}, inclusive=true) name must have @PermissionChecker(name)

Prevention

When it happens

Trigger: Using @PermissionsAllowed(value={...}, inclusive=true) where permission 'a' resolves to a @PermissionChecker method but sibling permission 'b' in the same annotation resolves to a plain StringPermission (no checker).

Common situations: Mixing @PermissionChecker-annotated producer methods with plain string permissions in one inclusive annotation; adding a new permission to an existing inclusive group without adding its checker; typos so a permission name no longer matches any checker method.

Related errors


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