elastic/elasticsearch · error · InvalidUserDataException
classname is a forbidden for stable esplugin
Error message
classname is a forbidden for stable esplugin
What it means
The mirror of error 41: thrown when a plugin declared as stable (isStable=true) ALSO supplies a classname. Stable plugins intentionally have no Plugin entry-point class, so supplying classname contradicts the stable contract and the task refuses to emit it.
Source
Thrown at build-tools/src/main/java/org/elasticsearch/gradle/plugin/GeneratePluginPropertiesTask.java:116
@OutputFile
public abstract RegularFileProperty getOutputFile();
@Input
public abstract Property<Boolean> getIsStable();
@Input
@Optional
public abstract Property<String> getDeploymentTarget();
@TaskAction
public void generatePropertiesFile() throws IOException {
String classname = getClassname().getOrElse("");
boolean stablePlugin = getIsStable().getOrElse(false);
if (stablePlugin == false && classname.isEmpty()) {
throw new InvalidUserDataException("classname is a required setting for esplugin");
}
if (stablePlugin && classname.isEmpty() == false) {
throw new InvalidUserDataException("classname is a forbidden for stable esplugin");
}
Map<String, Object> props = new HashMap<>();
props.put("name", getPluginName().get());
props.put("description", getPluginDescription().get());
props.put("version", getPluginVersion().get());
props.put("elasticsearchVersion", getElasticsearchVersion().get());
props.put("javaVersion", getJavaVersion().get());
props.put("classname", classname);
props.put("extendedPlugins", String.join(",", getExtendedPlugins().get()));
props.put("hasNativeController", getHasNativeController().get());
props.put("nativeControllerEnabledSettings", String.join(",", getNativeControllerEnabledSettings().get()));
props.put("requiresKeystore", getRequiresKeystore().get());
props.put("licensed", getIsLicensed().get());
props.put("modulename", findModuleName());
String deploymentTarget = getDeploymentTarget().getOrElse("ALL");
if (DEPLOYMENT_TARGETS.contains(deploymentTarget) == false) {View on GitHub (pinned to db6a809a66)
Solutions
- Remove the `classname '...'` line from the esplugin block for stable plugins.
- If you actually need a Plugin entry class, set `isStable = false` (the default) so classname becomes required instead of forbidden.
- Re-run the generatePluginPropertiesTask to confirm it emits a descriptor with no java policy/plugin entry class.
Example fix
// before
esplugin {
name 'my-stable-plugin'
isStable = true
classname 'com.acme.MyPlugin' // forbidden when stable
}
// after
esplugin {
name 'my-stable-plugin'
isStable = true
// no classname
} Defensive patterns
Strategy: validation
Validate before calling
project.tasks.withType(GeneratePluginPropertiesTask.class).configureEach {
if (it.isStable.get() && !it.classname.getOrElse('').trim().isEmpty()) {
throw new GradleException("esplugin '${it.pluginName.get()}' is stable — remove classname")
}
} Prevention
- Document the stable-plugin contract: no Plugin entry class.
- Add a convention-plugin check forbidding classname when isStable=true.
- When flipping isStable, audit the esplugin block for leftover classname.
When it happens
Trigger: esplugin { isStable = true; classname = '...' } — both set at once. The second guard in generatePropertiesFile fires.
Common situations: Converting a regular plugin to a stable plugin and forgetting to remove classname, or copy-pasting an esplugin block and toggling isStable without clearing classname.
Understand the failure class
- Authentication and authorization failures — expired tokens, bad credentials, and missing scopes.
Related errors
- classname is a required setting for esplugin
- invalid deploymentTarget '{}', expected one of {}
- unrecognized classpath entry: {}
- Number of nodes should be >= 1 but was {} for {}
- Cannot shrink {} to have {} nodes as it already has {}
AI-assisted analysis of elastic/elasticsearch@db6a809a66 (2026-08-12).
Data as JSON: /api/errors/09034415cde24e50.
Report an issue: GitHub.