elastic/elasticsearch · error · InvalidModuleDescriptorException
{name} found in top-level directory (unnamed package not all
Error message
{name} found in top-level directory (unnamed package not allowed in module) What it means
Thrown as an InvalidModuleDescriptorException by EmbeddedModulePath.toPackageName(Path, String) when a .class file (other than module-info.class) is found at the top-level directory of a module's jar — i.e. it has no parent directory and thus belongs to the unnamed package. The Java Platform explicitly forbids the unnamed package in modules, so this is a fatal descriptor error during automatic-module scanning.
Source
Thrown at libs/core/src/main/java/org/elasticsearch/core/internal/provider/EmbeddedModulePath.java:218
// Drops comments and trims the given string.
private static String dropCommentAndTrim(String line) {
int ci = line.indexOf('#');
if (ci >= 0) {
line = line.substring(0, ci);
}
return line.trim();
}
// Returns an optional containing the package name from a given path and separator, or an
// empty optional if none.
static Optional<String> toPackageName(Path file, String separator) {
Path parent = file.getParent();
if (parent == null) {
String name = file.toString();
if (name.endsWith(".class") && name.equals(MODULE_INFO) == false) {
String msg = name + " found in top-level directory (unnamed package not allowed in module)";
throw new InvalidModuleDescriptorException(msg);
}
return Optional.empty();
}
String pn = parent.toString().replace(separator, ".");
if (isPackageName(pn)) {
return Optional.of(pn);
} else {
// not a valid package name
return Optional.empty();
}
}
// Returns an optional containing the package name from a given binary class path name, or an
// empty optional if none.
static Optional<String> toPackageName(String name, String separator) {
assert name.endsWith(separator) == false;
int index = name.lastIndexOf(separator);View on GitHub (pinned to db6a809a66)
Solutions
- Move the offending .class file into a directory matching its package declaration; never place classes at the jar root.
- If the class genuinely has no package, delete it or rewrite it to declare a package before packaging.
- Rebuild the jar with proper structure (Gradle/Maven jar tasks preserve package dirs by default — investigate any custom shading).
- Inspect the jar: `jar tf x.jar | grep -E '^[^/]+\.class$'` to find top-level class files.
Defensive patterns
Strategy: validation
Validate before calling
// Pre-scan a jar for top-level class files before module-mode loading
boolean hasNoTopLevelClasses(Path jar) throws IOException {
try (JarFile j = new JarFile(jar.toFile())) {
return j.stream().noneMatch(e -> e.getName().endsWith(".class")
&& !e.getName().equals("module-info.class")
&& !e.getName().contains("/"));
}
} Type guard
static boolean isModuleSafeJar(Path jar) throws IOException {
try (JarFile j = new JarFile(jar.toFile())) {
return j.stream().filter(e -> e.getName().endsWith(".class")).allMatch(e -> e.getName().contains("/") || e.getName().equals("module-info.class"));
}
} Try / catch
try {
return EmbeddedModulePath.descriptorFor(path);
} catch (InvalidModuleDescriptorException e) {
if (e.getMessage().contains("top-level directory")) {
// repack the jar with proper package directories; identify the entry via the message
throw new RuntimeException("jar has unnamed-package class: " + path, e);
}
throw e;
} Prevention
- Never place .class files at the jar root; always under their package directory.
- Every compiled class must declare a package.
- Inspect jars with `jar tf x.jar | grep -E '^[^/]+\.class$'` before embedding.
- Investigate shading steps that may flatten directory structures.
When it happens
Trigger: An embedded jar that will become a module contains a top-level .class file (e.g. 'Foo.class' at the jar root, not under any package directory) other than module-info.class. The scan hits toPackageName, parent is null, and the check at line 216 fires.
Common situations: Shading classes into a jar without preserving package directories. A compiled class whose package declaration was removed/empty (legacy code in the default package). Bundling a script-generated .class at the root. A bad zip/jar assembly step that flattens directories.
Related errors
- automatic module without a manifest name is not supported, f
- unexpected jar name: {jarName}
- unknown scheme:{rootURI.getScheme()}
- missing %s provider jars list
- Automatic-Module-Name: {e.getMessage()}
AI-assisted analysis of elastic/elasticsearch@db6a809a66 (2026-08-12).
Data as JSON: /api/errors/4c9c60a1fe0402b7.
Report an issue: GitHub.