elastic/elasticsearch · error · InvalidUserDataException

Missing 'classesDirs' or 'classpath' property.

Error message

Missing 'classesDirs' or 'classpath' property.

What it means

Thrown by CheckForbiddenApisTask.createClassLoader(FileCollection classpath, FileCollection classesDirs) when either classesDirs or classpath is null. The forbidden-apis task needs both — class files to check and a classpath to resolve them against — and refuses to build the loader otherwise. Configuration-time guard analogous to [241] but for this task.

Source

Thrown at build-tools-internal/src/main/java/org/elasticsearch/gradle/internal/precommit/CheckForbiddenApisTask.java:560

            } catch (Exception e) {
                throw new RuntimeException(e);
            } finally {
                // Close the classloader to free resources:
                try {
                    if (urlLoader != null) urlLoader.close();
                } catch (IOException ioe) {
                    // getLogger().warn("Cannot close classloader: ".concat(ioe.toString()));
                }
            }
        }

        private void writeMarker(File successMarker) throws IOException {
            Files.write(successMarker.toPath(), new byte[] {}, StandardOpenOption.CREATE);
        }

        private URLClassLoader createClassLoader(FileCollection classpath, FileCollection classesDirs) {
            if (classesDirs == null || classpath == null) {
                throw new InvalidUserDataException("Missing 'classesDirs' or 'classpath' property.");
            }

            final Set<File> cpElements = new LinkedHashSet<>();
            cpElements.addAll(classpath.getFiles());
            cpElements.addAll(classesDirs.getFiles());
            final URL[] urls = new URL[cpElements.size()];
            try {
                int i = 0;
                for (final File cpElement : cpElements) {
                    urls[i++] = cpElement.toURI().toURL();
                }
                assert i == urls.length;
            } catch (MalformedURLException mfue) {
                throw new InvalidUserDataException("Failed to build classpath URLs.", mfue);
            }

            RegularFileProperty foreignApiJarProp = getParameters().getForeignApiJar();
            if (foreignApiJarProp.isPresent()) {

View on GitHub (pinned to db6a809a66)

Solutions

  1. Locate the task registration and set both classesDirs and classpath, e.g. from the matching source set.
  2. Wire classesDirs = sourceSets.main.output.classesDirs and classpath = sourceSets.main.compileClasspath.
  3. If the task is unused for that source set, delete the registration.

Example fix

// before
tasks.named('forbiddenApisMain', CheckForbiddenApisTask) {
  // missing wiring
}

// after
tasks.named('forbiddenApisMain', CheckForbiddenApisTask) {
  classesDirs = sourceSets.main.output.classesDirs
  classpath = sourceSets.main.compileClasspath
}
Defensive patterns

Strategy: validation

Validate before calling

// Assert both properties are wired at configuration time
tasks.named('forbiddenApisMain').configure(t -> {
    CheckForbiddenApisTask task = (CheckForbiddenApisTask) t;
    if (task.getClassesDirs() == null || task.getClasspath() == null) {
        throw new InvalidUserDataException("forbiddenApisMain needs classesDirs and classpath");
    }
});

Prevention

When it happens

Trigger: A CheckForbiddenApisTask registration where classFiles/classesDirs or classpath was never assigned, so the createClassLoader call at execution receives a null.

Common situations: Custom task registration missing the wiring; a source set rename that broke the property assignment; disabling a source set but keeping its forbidden-apis task.

Related errors


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