elastic/elasticsearch · error · IllegalArgumentException

{} is not a valid Entitlement class name. A valid class name

Error message

{} is not a valid Entitlement class name. A valid class name must end with 'Entitlement'

What it means

Thrown by PolicyParser.buildEntitlementNameFromClass when an Entitlement subclass's simple name does not end with the literal suffix 'Entitlement'. The parser derives the policy key (e.g. 'files_entitlement') by stripping that suffix and splitting on camel-case; without it the naming convention is broken and the class cannot be referenced by name in policy files.

Source

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

        WriteAllSystemPropertiesEntitlement.class,
        WriteSystemPropertiesEntitlement.class
    ).collect(Collectors.toUnmodifiableMap(PolicyParser::buildEntitlementNameFromClass, Function.identity()));

    private static final Map<Class<? extends Entitlement>, String> EXTERNAL_ENTITLEMENT_NAMES_BY_CLASS =
        EXTERNAL_ENTITLEMENT_CLASSES_BY_NAME.entrySet()
            .stream()
            .collect(Collectors.toUnmodifiableMap(Map.Entry::getValue, Map.Entry::getKey));

    protected final XContentParser policyParser;
    protected final String policyName;
    private final boolean isExternalPlugin;
    private final Map<String, Class<? extends Entitlement>> externalEntitlements;

    static String buildEntitlementNameFromClass(Class<? extends Entitlement> entitlementClass) {
        var entitlementClassName = entitlementClass.getSimpleName();

        if (entitlementClassName.endsWith("Entitlement") == false) {
            throw new IllegalArgumentException(
                entitlementClassName + " is not a valid Entitlement class name. A valid class name must end with 'Entitlement'"
            );
        }

        var strippedClassName = entitlementClassName.substring(0, entitlementClassName.indexOf("Entitlement"));
        return Arrays.stream(strippedClassName.split("(?=\\p{Lu})"))
            .filter(Predicate.not(String::isEmpty))
            .map(s -> s.toLowerCase(Locale.ROOT))
            .collect(Collectors.joining("_"));
    }

    public static String getEntitlementName(Class<? extends Entitlement> entitlementClass) {
        return EXTERNAL_ENTITLEMENT_NAMES_BY_CLASS.get(entitlementClass);
    }

    public PolicyParser(InputStream inputStream, String policyName, boolean isExternalPlugin) throws IOException {
        this(inputStream, policyName, isExternalPlugin, EXTERNAL_ENTITLEMENT_CLASSES_BY_NAME);
    }

View on GitHub (pinned to db6a809a66)

Solutions

  1. Rename the class so its simple name ends with 'Entitlement' (e.g. MyAccessEntitlement).
  2. Rebuild the plugin so the renamed class is picked up.
  3. Ensure any registered external entitlement class follows the '<Name>Entitlement' convention.

Example fix

// before
public class MyAccess implements Entitlement { ... }

// after
public class MyAccessEntitlement implements Entitlement { ... }
Defensive patterns

Strategy: validation

Validate before calling

// Assert naming convention before registering a custom entitlement
String simple = entitlementClass.getSimpleName();
if (!simple.endsWith("Entitlement")) {
  throw new IllegalArgumentException(simple + " must end with 'Entitlement'");
}

Type guard

boolean isValidEntitlementName(Class<? extends Entitlement> c) {
  return c.getSimpleName().endsWith("Entitlement");
}

Prevention

When it happens

Trigger: An external plugin registers a custom Entitlement subclass whose getSimpleName() does not end with 'Entitlement'. buildEntitlementNameFromClass is called during the static EXTERNAL_ENTITLEMENT_CLASSES_BY_NAME map construction, so this fails at class-registration time.

Common situations: Naming a custom entitlement class 'MyAccess' instead of 'MyAccessEntitlement'; a refactor dropped the suffix; copy-pasting a base class and forgetting the naming convention.

Related errors


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