quarkusio/quarkus · error · IllegalArgumentException

Policy has already been set

Error message

Policy has already been set

What it means

Thrown by HttpSecurityImpl's nested HttpPermission builder when a policy-defining method (permit, deny, roles, permissions, policy, authenticated) is called after a policy has already been assigned to this permission. Each HttpPermission can carry exactly one authorization policy, so a second call makes the configuration ambiguous and the builder fails fast with IllegalArgumentException.

Source

Thrown at extensions/vertx-http/runtime/src/main/java/io/quarkus/vertx/http/runtime/security/HttpSecurityImpl.java:364

        @Override
        public HttpSecurity policy(Predicate<SecurityIdentity> predicate) {
            return policy((identity, request) -> !identity.isAnonymous() && predicate.test(identity));
        }

        @Override
        public HttpSecurity policy(BiPredicate<SecurityIdentity, RoutingContext> predicate) {
            return policy(new SimpleHttpSecurityPolicy(predicate));
        }

        private HttpSecurity authenticated() {
            validatePolicyNotSetYet();
            this.policy = new Policy(AuthenticatedHttpSecurityPolicy.NAME, null);
            return HttpSecurityImpl.this;
        }

        private void validatePolicyNotSetYet() {
            if (policy != null) {
                throw new IllegalArgumentException("Policy has already been set");
            }
        }
    }

    private final class HttpPermissionImpl implements HttpPermission, HttpPermissionCarrier {

        private final String[] paths;
        private boolean shared;
        private boolean applyToJaxRs;
        private String[] methods;
        private HttpSecurityConfiguration.AuthenticationMechanisms authMechanism;
        private AuthorizationPolicy authorizationPolicy;

        private HttpPermissionImpl(String[] paths) {
            this.paths = Arrays.copyOf(paths, paths.length);
            this.authMechanism = null;
            this.authorizationPolicy = null;
            this.shared = false;

View on GitHub (pinned to e1c734241f)

Solutions

  1. Remove the duplicate policy call so each HttpPermission has exactly one of permit/deny/roles/permissions/policy/authenticated.
  2. Create a fresh HttpPermission via HttpSecurity.paths(...) for each distinct rule instead of reusing one instance.
  3. If combining behavior is intended, use the policy(...) API once with a composed HttpSecurityPolicy.

Example fix

// before
var perm = httpSecurity.paths("/api/*");
perm.permit();
perm.deny(); // IllegalArgumentException
// after
httpSecurity.paths("/api/*").permit();
httpSecurity.paths("/api/admin/*").deny();
Defensive patterns

Strategy: validation

Validate before calling

if (perm.isPolicySet()) throw new IllegalStateException("policy already assigned to this permission");

Try / catch

try { perm.permit(); } catch (IllegalArgumentException e) { if (!e.getMessage().contains("Policy has already been set")) throw e; }

Prevention

When it happens

Trigger: Calling two policy methods on the same HttpPermission instance, e.g. chaining .permit().deny(), .roles("admin").permissions("read"), or .authenticated().permit() on the same object returned from httpSecurity.paths("/x").

Common situations: Reusing a captured HttpPermission variable to build several different rules; copy-pasted fluent chains where an extra policy call was left in; refactors that split one chain into two without creating a new permission via paths().

Related errors


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