elastic/elasticsearch · error · RuntimeException

Unable to locate {} on build classpath.

Error message

Unable to locate {} on build classpath.

What it means

ReaperService.locateReaperJar() throws RuntimeException (internal mode) when the URL of the reaper class resource does not match the REAPER_JAR_PATH_PATTERN regex `file:(.*)!/org/elasticsearch/gradle/reaper/Reaper.class`. In internal mode the build expects the class to live inside a JAR on the runtime classpath (a `file:...!/...` jar URL); a mismatch means the class is loaded from an exploded directory, a different protocol, or the JAR isn't where the build expects it.

Source

Thrown at build-tools/src/main/java/org/elasticsearch/gradle/ReaperService.java:138

        }
    }

    private Path logFilePath() {
        return getParameters().getInputDir().get().getAsFile().toPath().resolve("reaper.log");
    }

    private Path locateReaperJar() {
        if (getParameters().getInternal()) {
            // when running inside the Elasticsearch build just pull find the jar in the runtime classpath
            URL main = this.getClass().getClassLoader().getResource(REAPER_CLASS);
            String mainPath = main.getFile();
            Matcher matcher = REAPER_JAR_PATH_PATTERN.matcher(mainPath);

            if (matcher.matches()) {
                String path = matcher.group(1);
                return Path.of(OS.<String>conditional().onWindows(() -> path.substring(1)).onUnix(() -> path).supply());
            } else {
                throw new RuntimeException("Unable to locate " + REAPER_CLASS + " on build classpath.");
            }
        } else {
            // copy the reaper jar
            Path jarPath = getParameters().getBuildDir().get().getAsFile().toPath().resolve("reaper").resolve("reaper.jar");
            try {
                Files.createDirectories(jarPath.getParent());
            } catch (IOException e) {
                throw new UncheckedIOException("Unable to create reaper JAR output directory " + jarPath.getParent(), e);
            }

            try (
                OutputStream out = Files.newOutputStream(jarPath);
                InputStream jarInput = this.getClass().getResourceAsStream("/META-INF/reaper.jar");
            ) {
                logger.info("Copying reaper.jar...");
                jarInput.transferTo(out);
            } catch (IOException e) {
                throw new UncheckedIOException(e);

View on GitHub (pinned to db6a809a66)

Solutions

  1. Ensure build-tools is packaged as a JAR on the runtime classpath (run the normal Gradle build, not an IDE exploded run).
  2. If main == null (resource missing), the build-tools JAR is incomplete — rebuild it with the reaper classes included.
  3. For non-internal contexts, use Params.internal = false so the JAR is copied from /META-INF/reaper.jar instead of located on the classpath.
Defensive patterns

Strategy: try-catch

Validate before calling

URL main = ReaperService.class.getClassLoader().getResource("org/elasticsearch/gradle/reaper/Reaper.class");
if (main == null || !main.getFile().contains("!/.")) {
    throw new IllegalStateException("Reaper class not packaged in a JAR; rebuild build-tools");
}

Try / catch

try { Path jar = reaperService.locateReaperJar(); } catch (RuntimeException e) { /* ensure build-tools JAR contains reaper classes */ throw e; }

Prevention

When it happens

Trigger: Running build-tools in internal mode (Params.internal = true) where getClass().getClassLoader().getResource(REAPER_CLASS) returns a URL that is not a JAR-entry URL — e.g., the class is on an exploded classpath (IDE run configs, some test harnesses), the resource is missing (main == null → NPE on main.getFile() would actually fire first), or the URL uses a non-file protocol.

Common situations: Running build-tools from an IDE with exploded class output instead of the packaged JAR; a broken build-tools JAR that omits the reaper classes; a custom classloader that returns a non-standard resource URL scheme.

Related errors


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