oracle/graal · error · LoadingConstraintViolationException

Loading constraint violated !

Error message

Loading constraint violated !

What it means

The JVM spec (JVMS 5.4.3/5.3) records loading constraints so that a type name used across different class loaders denotes the same runtime type. LoadingConstraintsShared.checkOrAdd registers/validates such a constraint per Symbol<Type>; when both keys k1 and k2 already resolve to existing (different) classes, the constraint cannot hold and a LoadingConstraintViolationException is thrown.

Source

Thrown at espresso-shared/src/com.oracle.truffle.espresso.shared/src/com/oracle/truffle/espresso/shared/constraints/LoadingConstraintsShared.java:208

     * <p>
     * As such, the entire set of constraints for a particular type, called a bucket, is a list of
     * constraints, one per Klass instance of this type. Each such constraint record all class
     * loaders that resolves the type as the recorded Klass instance.
     * <p>
     * To represent this, we use a map from types to buckets. Buckets are a doubly linked list. To
     * support concurrency, we use a ConcurrentHashMap. Failure to insert an item in the map due to
     * concurrency simply means someone was faster than us, we can therefore simply use the one that
     * is already present.
     * <p>
     * Once the bucket is obtained, we immediately synchronize on it. This prevents concurrency
     * problems as a whole, while allowing multiple threads to do constraint checking on different
     * types.
     */
    private final ConcurrentHashMap<Symbol<Type>, ConstraintBucket<Loader, Storage>> pairings = new ConcurrentHashMap<>();

    private void checkOrAdd(Symbol<Type> type, long k1, long k2, Loader loader1, Loader loader2) throws LoadingConstraintViolationException {
        if (exists(k1) && exists(k2) && k1 != k2) {
            throw new LoadingConstraintViolationException("Loading constraint violated !");
        }
        long klass = !exists(k1) ? k2 : k1;
        ConstraintBucket<Loader, Storage> bucket = lookup(type);
        if (bucket == null) {
            Constraint<Loader, Storage> newConstraint = Constraint.create(this, klass, loader1, loader2);
            bucket = new ConstraintBucket<>(newConstraint);
            ConstraintBucket<Loader, Storage> previous = pairings.putIfAbsent(type, bucket);
            if (previous != null) {
                bucket = previous;
            }
        }
        synchronized (bucket) {
            Constraint<Loader, Storage> c1 = bucket.lookupLoader(this, loader1);
            klass = checkConstraint(klass, c1);
            Constraint<Loader, Storage> c2 = bucket.lookupLoader(this, loader2);
            klass = checkConstraint(klass, c2);
            if (c1 == null && c2 == null) {
                bucket.add(Constraint.create(this, klass, loader1, loader2));

View on GitHub (pinned to a66e9ccd1d)

Solutions

  1. Make the constrained class loadable by exactly one loader: fix delegation (parent-first) so both loaders share the same Class instance.
  2. Remove duplicate copies of the class/jar from the classpaths of the involved loaders.
  3. If the constraint comes from a redefinition/redeployment, drop references and unload the stale class version (or restart the context).
  4. Catch the resulting LinkageError at the integration boundary to fail that operation instead of the whole VM.

Example fix

// before: child-first loader loads its own copy of api.Foo -> constraint violation on cross-loader call

// after: delegate shared API packages to the parent loader
class ChildLoader extends ClassLoader {
    @Override
    protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
        if (name.startsWith("api.")) return super.loadClass(name, resolve); // parent-first for shared types
        return findClass(name);
    }
}
Defensive patterns

Strategy: try-catch

Try / catch

try {
    crossLoaderInvoke();
} catch (LinkageError e) {
    // LoadingConstraintViolationException surfaces as a LinkageError subtype
    if (e.getMessage() != null && e.getMessage().contains("Loading constraint")) {
        reportLoaderConflict(e); // identify the two loaders / type name involved
    }
    throw e;
}

Prevention

When it happens

Trigger: Constraint-deriving operations (method resolution, invokevirtual/invokeinterface, checkcast on constrained signatures) with multiple class loaders where loader1 and loader2 resolve the same type name to different Class objects (k1 != k2, both already loaded).

Common situations: Custom loader hierarchies with child-first delegation; the same library/class duplicated on classpaths visible to two loaders; hot-reload or plugin containers (OSGi-like) where an older class version stays loaded in one loader.

Related errors


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