elastic/elasticsearch · error · IllegalStateException

entitlement class [{}] has more than one constructor and/or

Error message

entitlement class [{}] has more than one constructor and/or method annotated with ExternalEntitlement

What it means

Thrown when both a constructor and a static method (or multiple methods) of the same Entitlement class carry @ExternalEntitlement. The parser permits exactly one annotated entry point per class; this fires after the static-method check, when the loop encounters a second annotated member while one is already recorded.

Source

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

                        "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) {
                    throw new IllegalStateException(
                        "entitlement class ["
                            + entitlementClass.getName()
                            + "] has more than one constructor and/or method annotated with ExternalEntitlement"
                    );
                }
                entitlementMethod = method;
                entitlementMetadata = metadata;
            }
        }

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

        if (entitlementMetadata.esModulesOnly() && isExternalPlugin) {
            throw newPolicyParserException("entitlement type [" + entitlementType + "] is allowed only on modules");
        }

View on GitHub (pinned to db6a809a66)

Solutions

  1. Choose exactly one entry point (constructor OR a single static method) and remove @ExternalEntitlement from all others.
  2. Prefer the static-factory style for new entitlements because it composes better with the parser's parameter mapping.

Example fix

// before
public class MyEntitlement implements Entitlement {
    @ExternalEntitlement(parameterNames = {"paths"})
    public MyEntitlement(List<Object> paths) { ... }

    @ExternalEntitlement(parameterNames = {"paths"})
    public static MyEntitlement build(List<Object> paths) { return new MyEntitlement(paths); }
}

// after
public class MyEntitlement implements Entitlement {
    public MyEntitlement(List<Object> paths) { ... }

    @ExternalEntitlement(parameterNames = {"paths"})
    public static MyEntitlement build(List<Object> paths) { return new MyEntitlement(paths); }
}
Defensive patterns

Strategy: validation

Validate before calling

// Reuse the single-member scan from error 500; it counts ctors + methods together
// and asserts the sum is exactly 1, catching the constructor+method combo as well.
assertSingleAnnotatedMember(entitlementClass);

Prevention

When it happens

Trigger: An Entitlement class has @ExternalEntitlement on at least one constructor AND on at least one static method, or on more than one static method. The error is raised as soon as the second annotated member is found.

Common situations: Migrating from a constructor-based to a factory-based entitlement and forgetting to strip the annotation from the old constructor; merging two entitlement factory methods during a refactor.

Related errors


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