{"record":{"id":"85267e38b83b87e9","repo":"elastic/elasticsearch","slug":"extra-jar-file-doesn-t-appear-to-be-a-jar","errorCode":null,"errorMessage":"extra jar file {} doesn't appear to be a JAR","messagePattern":"extra jar file (.+?) doesn't appear to be a JAR","errorType":"validation","errorClass":"IllegalArgumentException","httpStatus":null,"severity":"error","filePath":"build-tools/src/main/java/org/elasticsearch/gradle/testclusters/ElasticsearchNode.java","lineNumber":645,"sourceCode":"            }\n        });\n    }\n\n    /**\n     * Copies extra jars to the `/lib` directory.\n     * //TODO: Remove this when system modules are available\n     */\n    private void copyExtraJars() {\n        List<File> extraJarFiles = this.extraJarConfigurations.stream()\n            .flatMap(fileCollection -> fileCollection.getFiles().stream())\n            .toList();\n\n        if (extraJarFiles.isEmpty() == false) {\n            logToProcessStdout(\"Setting up \" + this.extraJarConfigurations.size() + \" additional jar dependencies\");\n        }\n        extraJarFiles.forEach(from -> {\n            if (from.getName().endsWith(\".jar\") == false) {\n                throw new IllegalArgumentException(\"extra jar file \" + from + \" doesn't appear to be a JAR\");\n            }\n\n            Path destination = getDistroDir().resolve(\"lib\").resolve(from.getName());\n            try {\n                Files.copy(from.toPath(), destination, StandardCopyOption.REPLACE_EXISTING);\n                LOGGER.info(\"Added extra jar {} to {}\", from.getName(), destination);\n            } catch (IOException e) {\n                throw new UncheckedIOException(\"Can't copy extra jar dependency \" + from.getName() + \" to \" + destination, e);\n            }\n        });\n    }\n\n    private void configureSecurity() {\n        if (credentials.isEmpty() == false) {\n            logToProcessStdout(\"Setting up \" + credentials.size() + \" users\");\n\n            credentials.forEach(\n                paramMap -> runElasticsearchBinScript(","sourceCodeStart":627,"sourceCodeEnd":663,"githubUrl":"https://github.com/elastic/elasticsearch/blob/db6a809a667c081ca1dc7500389d26975573215f/build-tools/src/main/java/org/elasticsearch/gradle/testclusters/ElasticsearchNode.java#L627-L663","documentation":"Thrown by copyExtraJars() when a File obtained from an extraJarFiles FileCollection does not end with '.jar'. The node copies extra jars into <distro>/lib and rejects anything that is not obviously a jar to avoid corrupting the classpath. The exception type is IllegalArgumentException (not TestClustersException), signalling a programming/configuration error rather than a runtime cluster fault.","triggerScenarios":"Calling extraJarFiles(from) with a FileCollection that resolves to a non-jar artifact: a pom, sources jar classifier stripped by mistake, a directory, a zip, or a plain class file. Often happens when a configuration is mistyped (e.g. using 'runtimeClasspath' or 'sources' instead of 'runtimeElements').","commonSituations":"Mistakenly passing a sources/javadoc configuration. A third-party dependency whose artifact lacks the .jar extension. A test fixture that bundles classes in a directory. Shadow/fat-jar task output named without .jar.","solutions":["Inspect the offending path printed in the message and confirm it should be a jar.","Use the correct configuration: a runtime classpath whose artifacts end with .jar (e.g. project.configurations.runtimeClasspath filtered to .jar).","If you genuinely need a non-jar on the classpath, package it as a jar first.","Filter the FileCollection before passing: configurations.myConfig.filter { it.name.endsWith('.jar') }."],"exampleFix":"// before: passes a configuration that contains pom/sources\nextraJarFiles(configurations.implementation)\n// after: filter to jars only\nextraJarFiles(configurations.implementation.filter { it.name.endsWith('.jar') })","handlingStrategy":"type-guard","validationCode":"// Filter any FileCollection to jars before registering\nFileCollection jars = myConfig.filter(f -> f.getName().toLowerCase().endsWith(\".jar\"));\nnode.extraJarFiles(jars);","typeGuard":"// Type guard: only jars are valid inputs to extraJarFiles\nstatic FileCollection onlyJars(FileCollection in) {\n    return in.filter(f -> f.getName().toLowerCase().endsWith(\".jar\"));\n}","tryCatchPattern":null,"preventionTips":["Pass runtime classpath configurations filtered to .jar.","Avoid passing 'sources', 'javadoc', or 'pom' configurations.","If a non-jar must be on the classpath, package it as a jar first."],"tags":["testclusters","classpath","jar","validation","config"],"backgroundTag":null,"analyzedSha":"db6a809a667c081ca1dc7500389d26975573215f","analyzedAt":"2026-08-12T01:39:14.192Z","schemaVersion":2},"datasetVersion":"2026-08-12T13:17:24.610Z"}