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
- Make the constrained class loadable by exactly one loader: fix delegation (parent-first) so both loaders share the same Class instance.
- Remove duplicate copies of the class/jar from the classpaths of the involved loaders.
- If the constraint comes from a redefinition/redeployment, drop references and unload the stale class version (or restart the context).
- 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
- Load shared API types through a single parent loader (parent-first delegation).
- Keep exactly one copy of each shared class on the combined classpaths.
- Drop old loaders fully on redeploy so stale classes cannot participate in constraints.
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
- New loading constraint violates an older one!
- Cannot signal lock that is not held
- Class circularity detected
- Array type with more than 255 dimensions
- Method {}{} from type {} overrides final method {}{} from ty
AI-assisted analysis of oracle/graal@a66e9ccd1d (2026-08-14).
Data as JSON: /api/errors/26294ddeca755905.
Report an issue: GitHub.