{"record":{"id":"f406e6f4f20ce78b","repo":"oracle/graal","slug":"package-s-in-more-than-one-module","errorCode":null,"errorMessage":"Package %s in more than one module","messagePattern":"Package (.+?) in more than one module","errorType":"validation","errorClass":"IllegalArgumentException","httpStatus":null,"severity":"critical","filePath":"compiler/src/jdk.graal.compiler.hostvmaccess/src/jdk/graal/compiler/hostvmaccess/HostVMAccessClassLoader.java","lineNumber":160,"sourceCode":"     */\n    HostVMAccessClassLoader(List<Path> classpath, Configuration configuration, ClassLoader parent) {\n        super(parent);\n\n        Objects.requireNonNull(parent);\n        this.parent = parent;\n\n        // Checkstyle: stop stable iteration order check\n        Map<String, ModuleReference> nameToModule = new HashMap<>();\n        Map<String, LoadedModule> packageToModule = new HashMap<>();\n        // Checkstyle: resume stable iteration order check\n        for (ResolvedModule resolvedModule : configuration.modules()) {\n            ModuleReference mref = resolvedModule.reference();\n            ModuleDescriptor descriptor = mref.descriptor();\n            nameToModule.put(descriptor.name(), mref);\n            descriptor.packages().forEach(pn -> {\n                LoadedModule lm = new LoadedModule(mref);\n                if (packageToModule.put(pn, lm) != null) {\n                    throw new IllegalArgumentException(\"Package \" + pn + \" in more than one module\");\n                }\n            });\n        }\n        localNameToModule = Collections.unmodifiableMap(nameToModule);\n        localPackageToModule = Collections.unmodifiableMap(packageToModule);\n        /*\n         * Unlike {@code jdk.internal.loader.Loader}, we initialize remotePackageToLoader here which\n         * allows us to use an unmodifiable map instead of a ConcurrentHashMap.\n         */\n        remotePackageToLoader = initRemotePackageMap(configuration, List.of(ModuleLayer.boot()));\n\n        /* The only map that gets updated concurrently during the lifetime of this loader. */\n        moduleToReader = new ConcurrentHashMap<>();\n\n        /* Initialize URLClassPath that is used to lookup classes from class-path. */\n        ucp = new URLClassPath(classpath.stream().map(HostVMAccessClassLoader::toURL).toArray(URL[]::new), null);\n\n    }","sourceCodeStart":142,"sourceCodeEnd":178,"githubUrl":"https://github.com/oracle/graal/blob/a66e9ccd1d7bf2552883939aa0788dfd0e294aab/compiler/src/jdk.graal.compiler.hostvmaccess/src/jdk/graal/compiler/hostvmaccess/HostVMAccessClassLoader.java#L142-L178","documentation":"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.","triggerScenarios":"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().","commonSituations":"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.","solutions":["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","If the duplicate comes from a shaded/uber module, exclude the overlapping dependency so classes ship once","Move the offending classes to a distinct package name in one of the modules","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"],"exampleFix":"# before\n--module-path app.jar:legacy-util.jar   # both contain com.acme.util\n\n# after\n--module-path app.jar                    # com.acme.util kept only in app.jar\n# or relocate: com.acme.util -> com.acme.legacyutil in legacy-util.jar","handlingStrategy":"validation","validationCode":"Configuration cf = Configuration.resolve(roots, List.of(ModuleLayer.boot().configuration()), ModuleFinder.of(modulePaths));\n// pre-detect split packages before constructing VMAccess\nMap<String, String> pkgToModule = new HashMap<>();\nfor (ResolvedModule rm : cf.modules()) {\n    for (String pn : rm.reference().descriptor().packages()) {\n        String prev = pkgToModule.putIfAbsent(pn, rm.name());\n        if (prev != null) throw new IllegalArgumentException(\"Split package \" + pn + \": \" + prev + \" vs \" + rm.name());\n    }\n}","typeGuard":null,"tryCatchPattern":"catch (IllegalArgumentException e) { if (e.getMessage().contains(\"in more than one module\")) { print both module names from the message; fix the module path; } }","preventionTips":["Run jdeps over the module path before builds to catch split packages early","Keep each library on exactly one path (module path XOR class path)","Beware automatic modules: their packages are derived from the jar contents and easily overlap"],"tags":["graalvm","jpms","module-path","split-package","classloader"],"backgroundTag":null,"analyzedSha":"a66e9ccd1d7bf2552883939aa0788dfd0e294aab","analyzedAt":"2026-08-14T13:58:47.161Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}