oracle/graal · error · IllegalArgumentException

cannot have non-public interfaces in different packages

Error message

cannot have non-public interfaces in different packages

What it means

Thrown by EspressoForeignProxyGenerator while assembling the proxy class context: when the interfaces being proxied include non-public ones, all of them must live in the same runtime package (same package name AND same module). If two package-private interfaces come from different modules or different packages, configuration aborts with this IllegalArgumentException. It replicates java.lang.reflect.Proxy's rule for non-public interfaces.

Source

Thrown at espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/nodes/interop/EspressoForeignProxyGenerator.java:359

                    nonExported = true;
                }
            }
        }

        if (!packagePrivateTypes.isEmpty()) {
            // all package-private types must be in the same runtime package
            // i.e. same package name and same module (named or unnamed)
            //
            // Configuration will fail if M1 and in M2 defined by the same loader
            // and both have the same package p (so no need to check class loader)
            ModuleTable.ModuleEntry targetModule = null;
            PackageTable.PackageEntry targetPackage = null;
            for (Map.Entry<ObjectKlass, ModuleTable.ModuleEntry> e : packagePrivateTypes.entrySet()) {
                PackageTable.PackageEntry currentPackage = e.getKey().packageEntry();
                ModuleTable.ModuleEntry m = e.getValue();
                if ((targetModule != null && targetModule != m) ||
                                (targetPackage != null && !targetPackage.equals(currentPackage))) {
                    throw new IllegalArgumentException(
                                    "cannot have non-public interfaces in different packages");
                }
                if (m.classLoader() != proxyClassLoader) {
                    // the specified loader is not the same class loader
                    // of the non-public interface
                    throw new IllegalArgumentException(
                                    "non-public interface is not defined by the given loader");
                }

                targetModule = m;
                targetPackage = currentPackage;
            }

            // validate if the target module can access all other interfaces
            for (ObjectKlass intf : interfaces) {
                ModuleTable.ModuleEntry m = intf.module();
                if (m == targetModule) {
                    continue;

View on GitHub (pinned to a66e9ccd1d)

Solutions

  1. Make the non-public interfaces public so they route to the dynamic proxy module path.
  2. Restrict the proxy to package-private interfaces that all live in the same package and module.
  3. Split the proxy into two proxies: one for the package-private group, one for the rest, and bridge them.
  4. Eliminate split packages so all interfaces in a package come from one module.

Example fix

// before (two package-private interfaces from different packages)
Object proxy = proxyGenerator.getProxy(ctx, klassLoader, ifaceA /* pkg p1 */, ifaceB /* pkg p2 */);

// after: proxy them separately or make one public
Object proxyA = proxyGenerator.getProxy(ctx, klassLoader, ifaceA);
Object proxyB = proxyGenerator.getProxy(ctx, klassLoader, ifaceB);
Defensive patterns

Strategy: validation

Validate before calling

// all package-private interfaces must share package AND module
Set<String> pkgs = new HashSet<>();
Set<Object> mods = new HashSet<>();
for (ObjectKlass k : packagePrivateInterfaces) {
    pkgs.add(k.packageEntry().packageName());
    mods.add(k.module());
}
if (pkgs.size() > 1 || mods.size() > 1) {
    throw new IllegalArgumentException("non-public interfaces span multiple packages/modules");
}

Try / catch

try {
    generator.getProxy(context, loader, interfaces);
} catch (IllegalArgumentException e) {
    // message says 'different packages': split the proxy or make interfaces public
}

Prevention

When it happens

Trigger: Calling the Espresso proxy generator (proxying guest interfaces, e.g. via Polyglot proxies or lambdametafactory-style requests) with a mix of package-private interfaces from different packages or different modules; combining a package-private interface with a public one from another module is fine, but two private ones must agree.

Common situations: Host code implementing multiple guest SPI interfaces that happen to be package-private; guest interfaces in split packages across jars; migrating a codebase where an interface's visibility was reduced from public to package-private, silently breaking proxying combinations.

Related errors


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