{"record":{"id":"cfffa312f1b04056","repo":"elastic/elasticsearch","slug":"is-not-a-valid-entitlement-class-name-a-valid","errorCode":null,"errorMessage":"{} is not a valid Entitlement class name. A valid class name must end with 'Entitlement'","messagePattern":"(.+?) is not a valid Entitlement class name\\. A valid class name must end with 'Entitlement'","errorType":"validation","errorClass":"IllegalArgumentException","httpStatus":null,"severity":"error","filePath":"libs/entitlement/src/main/java/org/elasticsearch/entitlement/runtime/policy/PolicyParser.java","lineNumber":78,"sourceCode":"        WriteAllSystemPropertiesEntitlement.class,\n        WriteSystemPropertiesEntitlement.class\n    ).collect(Collectors.toUnmodifiableMap(PolicyParser::buildEntitlementNameFromClass, Function.identity()));\n\n    private static final Map<Class<? extends Entitlement>, String> EXTERNAL_ENTITLEMENT_NAMES_BY_CLASS =\n        EXTERNAL_ENTITLEMENT_CLASSES_BY_NAME.entrySet()\n            .stream()\n            .collect(Collectors.toUnmodifiableMap(Map.Entry::getValue, Map.Entry::getKey));\n\n    protected final XContentParser policyParser;\n    protected final String policyName;\n    private final boolean isExternalPlugin;\n    private final Map<String, Class<? extends Entitlement>> externalEntitlements;\n\n    static String buildEntitlementNameFromClass(Class<? extends Entitlement> entitlementClass) {\n        var entitlementClassName = entitlementClass.getSimpleName();\n\n        if (entitlementClassName.endsWith(\"Entitlement\") == false) {\n            throw new IllegalArgumentException(\n                entitlementClassName + \" is not a valid Entitlement class name. A valid class name must end with 'Entitlement'\"\n            );\n        }\n\n        var strippedClassName = entitlementClassName.substring(0, entitlementClassName.indexOf(\"Entitlement\"));\n        return Arrays.stream(strippedClassName.split(\"(?=\\\\p{Lu})\"))\n            .filter(Predicate.not(String::isEmpty))\n            .map(s -> s.toLowerCase(Locale.ROOT))\n            .collect(Collectors.joining(\"_\"));\n    }\n\n    public static String getEntitlementName(Class<? extends Entitlement> entitlementClass) {\n        return EXTERNAL_ENTITLEMENT_NAMES_BY_CLASS.get(entitlementClass);\n    }\n\n    public PolicyParser(InputStream inputStream, String policyName, boolean isExternalPlugin) throws IOException {\n        this(inputStream, policyName, isExternalPlugin, EXTERNAL_ENTITLEMENT_CLASSES_BY_NAME);\n    }","sourceCodeStart":60,"sourceCodeEnd":96,"githubUrl":"https://github.com/elastic/elasticsearch/blob/db6a809a667c081ca1dc7500389d26975573215f/libs/entitlement/src/main/java/org/elasticsearch/entitlement/runtime/policy/PolicyParser.java#L60-L96","documentation":"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.","triggerScenarios":"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.","commonSituations":"Naming a custom entitlement class 'MyAccess' instead of 'MyAccessEntitlement'; a refactor dropped the suffix; copy-pasting a base class and forgetting the naming convention.","solutions":["Rename the class so its simple name ends with 'Entitlement' (e.g. MyAccessEntitlement).","Rebuild the plugin so the renamed class is picked up.","Ensure any registered external entitlement class follows the '<Name>Entitlement' convention."],"exampleFix":"// before\npublic class MyAccess implements Entitlement { ... }\n\n// after\npublic class MyAccessEntitlement implements Entitlement { ... }","handlingStrategy":"validation","validationCode":"// Assert naming convention before registering a custom entitlement\nString simple = entitlementClass.getSimpleName();\nif (!simple.endsWith(\"Entitlement\")) {\n  throw new IllegalArgumentException(simple + \" must end with 'Entitlement'\");\n}","typeGuard":"boolean isValidEntitlementName(Class<? extends Entitlement> c) {\n  return c.getSimpleName().endsWith(\"Entitlement\");\n}","tryCatchPattern":null,"preventionTips":["Adopt the '<Name>Entitlement' naming convention for all custom entitlement classes.","Add a unit test that validates every registered entitlement class name."],"tags":["entitlement","naming-convention","policy","plugin"],"backgroundTag":null,"analyzedSha":"db6a809a667c081ca1dc7500389d26975573215f","analyzedAt":"2026-08-12T01:39:14.192Z","schemaVersion":2},"datasetVersion":"2026-08-13T09:17:06.757Z"}