oracle/graal · critical · IllegalArgumentException

Package %s in more than one module

Error message

Package %s in more than one module

What it means

HostVMAccessClassLoader builds a module layer from the user-supplied module path (VMAccess.Builder#modulePath) and, like jdk.internal.loader.Loader, builds a package->module map. The JPMS configuration forbids the same package appearing in two modules in one layer, so if two resolved modules both declare a package, construction fails with this IllegalArgumentException. This is the classic JPMS 'split package' error surfaced at image-build/VMAccess startup.

Source

Thrown at compiler/src/jdk.graal.compiler.hostvmaccess/src/jdk/graal/compiler/hostvmaccess/HostVMAccessClassLoader.java:160

     */
    HostVMAccessClassLoader(List<Path> classpath, Configuration configuration, ClassLoader parent) {
        super(parent);

        Objects.requireNonNull(parent);
        this.parent = parent;

        // Checkstyle: stop stable iteration order check
        Map<String, ModuleReference> nameToModule = new HashMap<>();
        Map<String, LoadedModule> packageToModule = new HashMap<>();
        // Checkstyle: resume stable iteration order check
        for (ResolvedModule resolvedModule : configuration.modules()) {
            ModuleReference mref = resolvedModule.reference();
            ModuleDescriptor descriptor = mref.descriptor();
            nameToModule.put(descriptor.name(), mref);
            descriptor.packages().forEach(pn -> {
                LoadedModule lm = new LoadedModule(mref);
                if (packageToModule.put(pn, lm) != null) {
                    throw new IllegalArgumentException("Package " + pn + " in more than one module");
                }
            });
        }
        localNameToModule = Collections.unmodifiableMap(nameToModule);
        localPackageToModule = Collections.unmodifiableMap(packageToModule);
        /*
         * Unlike {@code jdk.internal.loader.Loader}, we initialize remotePackageToLoader here which
         * allows us to use an unmodifiable map instead of a ConcurrentHashMap.
         */
        remotePackageToLoader = initRemotePackageMap(configuration, List.of(ModuleLayer.boot()));

        /* The only map that gets updated concurrently during the lifetime of this loader. */
        moduleToReader = new ConcurrentHashMap<>();

        /* Initialize URLClassPath that is used to lookup classes from class-path. */
        ucp = new URLClassPath(classpath.stream().map(HostVMAccessClassLoader::toURL).toArray(URL[]::new), null);

    }

View on GitHub (pinned to a66e9ccd1d)

Solutions

  1. Run 'jdeps --module-path ... --print-module-deps' or java --describe-module on each module to find which two declare the conflicting package, then remove or rename one
  2. If the duplicate comes from a shaded/uber module, exclude the overlapping dependency so classes ship once
  3. Move the offending classes to a distinct package name in one of the modules
  4. As a last resort, put the artifact only on the class path (unnamed module) instead of the module path, since split packages between class path and module path are tolerated

Example fix

# before
--module-path app.jar:legacy-util.jar   # both contain com.acme.util

# after
--module-path app.jar                    # com.acme.util kept only in app.jar
# or relocate: com.acme.util -> com.acme.legacyutil in legacy-util.jar
Defensive patterns

Strategy: validation

Validate before calling

Configuration cf = Configuration.resolve(roots, List.of(ModuleLayer.boot().configuration()), ModuleFinder.of(modulePaths));
// pre-detect split packages before constructing VMAccess
Map<String, String> pkgToModule = new HashMap<>();
for (ResolvedModule rm : cf.modules()) {
    for (String pn : rm.reference().descriptor().packages()) {
        String prev = pkgToModule.putIfAbsent(pn, rm.name());
        if (prev != null) throw new IllegalArgumentException("Split package " + pn + ": " + prev + " vs " + rm.name());
    }
}

Try / catch

catch (IllegalArgumentException e) { if (e.getMessage().contains("in more than one module")) { print both module names from the message; fix the module path; } }

Prevention

When it happens

Trigger: Two modules on the module path (or pulled in transitively via requires) whose module descriptors both list the same package, e.g. an app module and a library module both containing com.acme.util. Thrown from the HostVMAccessClassLoader constructor while iterating configuration.modules().

Common situations: Duplicating a library as both a module and a jar patch; automatic modules that overlap with named modules (e.g. the same artifact on the class path AND module path in overlapping packages); fat/uber module re-bundling classes that also ship in a dependency module.

Related errors


AI-assisted analysis of oracle/graal@a66e9ccd1d (2026-08-14). Data as JSON: /api/errors/f406e6f4f20ce78b. Report an issue: GitHub.