{"record":{"id":"9de4a14028e62840","repo":"elastic/elasticsearch","slug":"entitlement-class-has-more-than-one-construct-9de4a1","errorCode":null,"errorMessage":"entitlement class [{}] has more than one constructor and/or method annotated with ExternalEntitlement","messagePattern":"entitlement class \\[(.+?)\\] has more than one constructor and/or method annotated with ExternalEntitlement","errorType":"exception","errorClass":"IllegalStateException","httpStatus":null,"severity":"error","filePath":"libs/entitlement/src/main/java/org/elasticsearch/entitlement/runtime/policy/PolicyParser.java","lineNumber":248,"sourceCode":"                        \"entitlement class [\"\n                            + entitlementClass.getName()\n                            + \"] has more than one constructor annotated with ExternalEntitlement\"\n                    );\n                }\n                entitlementConstructor = ctor;\n                entitlementMetadata = metadata;\n            }\n        }\n        for (var method : entitlementClass.getMethods()) {\n            var metadata = method.getAnnotation(ExternalEntitlement.class);\n            if (metadata != null) {\n                if (Modifier.isStatic(method.getModifiers()) == false) {\n                    throw new IllegalStateException(\n                        \"entitlement class [\" + entitlementClass.getName() + \"] has non-static method annotated with ExternalEntitlement\"\n                    );\n                }\n                if (entitlementMetadata != null) {\n                    throw new IllegalStateException(\n                        \"entitlement class [\"\n                            + entitlementClass.getName()\n                            + \"] has more than one constructor and/or method annotated with ExternalEntitlement\"\n                    );\n                }\n                entitlementMethod = method;\n                entitlementMetadata = metadata;\n            }\n        }\n\n        if (entitlementMetadata == null) {\n            throw newPolicyParserException(scopeName, \"unknown entitlement type [\" + entitlementType + \"]\");\n        }\n\n        if (entitlementMetadata.esModulesOnly() && isExternalPlugin) {\n            throw newPolicyParserException(\"entitlement type [\" + entitlementType + \"] is allowed only on modules\");\n        }\n","sourceCodeStart":230,"sourceCodeEnd":266,"githubUrl":"https://github.com/elastic/elasticsearch/blob/db6a809a667c081ca1dc7500389d26975573215f/libs/entitlement/src/main/java/org/elasticsearch/entitlement/runtime/policy/PolicyParser.java#L230-L266","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Choose exactly one entry point (constructor OR a single static method) and remove @ExternalEntitlement from all others.","Prefer the static-factory style for new entitlements because it composes better with the parser's parameter mapping."],"exampleFix":"// before\npublic class MyEntitlement implements Entitlement {\n    @ExternalEntitlement(parameterNames = {\"paths\"})\n    public MyEntitlement(List<Object> paths) { ... }\n\n    @ExternalEntitlement(parameterNames = {\"paths\"})\n    public static MyEntitlement build(List<Object> paths) { return new MyEntitlement(paths); }\n}\n\n// after\npublic class MyEntitlement implements Entitlement {\n    public MyEntitlement(List<Object> paths) { ... }\n\n    @ExternalEntitlement(parameterNames = {\"paths\"})\n    public static MyEntitlement build(List<Object> paths) { return new MyEntitlement(paths); }\n}","handlingStrategy":"validation","validationCode":"// Reuse the single-member scan from error 500; it counts ctors + methods together\n// and asserts the sum is exactly 1, catching the constructor+method combo as well.\nassertSingleAnnotatedMember(entitlementClass);","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Settle on one idiom for an entitlement class: either an annotated constructor OR an annotated static factory, never both.","Run the single-annotation architectural test on every build.","During refactor from ctor to factory (or vice versa), delete the old annotation in the same commit."],"tags":["entitlements","policy","annotation","reflection"],"backgroundTag":null,"analyzedSha":"db6a809a667c081ca1dc7500389d26975573215f","analyzedAt":"2026-08-12T01:39:14.192Z","schemaVersion":2},"datasetVersion":"2026-08-12T12:31:55.035Z"}