elastic/elasticsearch · error · IllegalStateException

entitlement class [{}] has more than one constructor annotat

Error message

entitlement class [{}] has more than one constructor annotated with ExternalEntitlement

What it means

Thrown by PolicyParser while scanning an Entitlement class's constructors via reflection. The parser allows at most one constructor per class to carry @ExternalEntitlement; finding a second annotated constructor means the class declares two competing entry points and the parser cannot decide which to invoke when materialising the entitlement from a policy file.

Source

Thrown at libs/entitlement/src/main/java/org/elasticsearch/entitlement/runtime/policy/PolicyParser.java:229

        }
    }

    protected Entitlement parseEntitlement(String scopeName, String entitlementType) throws IOException {
        XContentLocation startLocation = policyParser.getTokenLocation();
        Class<?> entitlementClass = externalEntitlements.get(entitlementType);

        if (entitlementClass == null) {
            throw newPolicyParserException(scopeName, "unknown entitlement type [" + entitlementType + "]");
        }

        Constructor<?> entitlementConstructor = null;
        Method entitlementMethod = null;
        ExternalEntitlement entitlementMetadata = null;
        for (var ctor : entitlementClass.getConstructors()) {
            var metadata = ctor.getAnnotation(ExternalEntitlement.class);
            if (metadata != null) {
                if (entitlementMetadata != null) {
                    throw new IllegalStateException(
                        "entitlement class ["
                            + entitlementClass.getName()
                            + "] has more than one constructor annotated with ExternalEntitlement"
                    );
                }
                entitlementConstructor = ctor;
                entitlementMetadata = metadata;
            }
        }
        for (var method : entitlementClass.getMethods()) {
            var metadata = method.getAnnotation(ExternalEntitlement.class);
            if (metadata != null) {
                if (Modifier.isStatic(method.getModifiers()) == false) {
                    throw new IllegalStateException(
                        "entitlement class [" + entitlementClass.getName() + "] has non-static method annotated with ExternalEntitlement"
                    );
                }
                if (entitlementMetadata != null) {

View on GitHub (pinned to db6a809a66)

Solutions

  1. Inspect the named class and remove @ExternalEntitlement from all but one constructor.
  2. If you genuinely need multiple construction shapes, route them through a single annotated static factory method instead and remove annotations from all constructors.
  3. Rebuild and re-run the policy parse to confirm only one annotated constructor remains.

Example fix

// before
public final class MyEntitlement implements Entitlement {
    @ExternalEntitlement(parameterNames = {"a"})
    public MyEntitlement(String a) { ... }

    @ExternalEntitlement(parameterNames = {"b"})
    public MyEntitlement(int b) { ... }
}

// after
public final class MyEntitlement implements Entitlement {
    public MyEntitlement(String a) { ... }

    @ExternalEntitlement(parameterNames = {"a"})
    public static MyEntitlement build(String a) { return new MyEntitlement(a); }
}
Defensive patterns

Strategy: validation

Validate before calling

// At test time, assert each @ExternalEntitlement class has exactly one annotated member.
public static void assertSingleAnnotatedMember(Class<?> entitlementClass) {
    int ctors = Arrays.stream(entitlementClass.getConstructors())
        .filter(c -> c.isAnnotationPresent(ExternalEntitlement.class))
        .toList().size();
    int methods = Arrays.stream(entitlementClass.getDeclaredMethods())
        .filter(m -> m.isAnnotationPresent(ExternalEntitlement.class))
        .toList().size();
    if (ctors + methods != 1) {
        throw new AssertionError(entitlementClass.getName() + " must have exactly one @ExternalEntitlement member, found "
            + ctors + " ctor(s), " + methods + " method(s)");
    }
}

Prevention

When it happens

Trigger: An Entitlement implementation class has two (or more) constructors, and both are annotated with @ExternalEntitlement. The error fires the first time PolicyParser.parseEntitlement resolves that class for any scope that references its entitlement type.

Common situations: Authoring a new Entitlement subclass and copy-pasting the @ExternalEntitlement annotation onto an overloaded constructor; refactoring a constructor into a builder-style multi-ctor setup without removing the annotation from the old one; merging two entitlement classes during a refactor.

Related errors


AI-assisted analysis of elastic/elasticsearch@db6a809a66 (2026-08-12). Data as JSON: /api/errors/584e08bdf6649d86. Report an issue: GitHub.