oracle/graal · error · IllegalArgumentException

%s is not an annotation interface

Error message

%s is not an annotation interface

What it means

checkNonExistingGlobalReference scans all registered Cleaner entries and throws IllegalArgumentException if an identical object already has a global JNI reference (checked via JNI IsSameObject). The library intentionally avoids duplicate global refs for the same host object, because each global ref is a leak-prone native resource and the cleaner registry assumes one entry per object.

Source

Thrown at compiler/src/jdk.graal.compiler/src/jdk/graal/compiler/annotation/AnnotationValueSupport.java:67

/**
 * Support for parsing class file annotation attributes.
 */
public class AnnotationValueSupport {

    /**
     * Gets the annotation directly present on {@code annotated} whose type is
     * {@code annotationType}. Class initialization is not triggered for enum types referenced by
     * the returned annotation. This method ignores inherited annotations.
     *
     * @param annotationType the type object corresponding to the annotation interface type
     * @return {@code annotated}'s annotation for the specified annotation type if directly present
     *         on this element, else null
     * @throws IllegalArgumentException if {@code annotationType} is not an annotation interface
     *             type
     */
    public static AnnotationValue getDeclaredAnnotationValue(ResolvedJavaType annotationType, Annotated annotated) {
        if (!annotationType.isAnnotation()) {
            throw new IllegalArgumentException(annotationType.toJavaName() + " is not an annotation interface");
        }
        return getDeclaredAnnotationValues(annotated).get(annotationType);
    }

    /**
     * Checks if an annotation of the specified type is directly present on the given
     * {@code annotated} element. Class initialization is not triggered for enum types referenced by
     * the returned annotation. This method ignores inherited annotations.
     *
     * @param annotationType the type object corresponding to the annotation interface type
     * @return true if an annotation of the specified type is directly present on the given
     *         {@code annotated} element
     * @throws IllegalArgumentException if {@code annotationType} is not an annotation interface
     *             type
     */
    public static boolean isAnnotationPresent(ResolvedJavaType annotationType, Annotated annotated) {
        return getDeclaredAnnotationValue(annotationType, annotated) != null;
    }

View on GitHub (pinned to a66e9ccd1d)

Solutions

  1. Track existing global refs and reuse them instead of creating a second one for the same object.
  2. Delete the old global reference (JNIUtil.release / DeleteGlobalRef) before creating a new one for the same object.
  3. Guard creation code with a single initialization point (e.g. synchronized lazy init or a static registry keyed by object) so the same object is never registered twice.

Example fix

// before
JObject g1 = NewGlobalRef(env, obj, "obj");
JObject g2 = NewGlobalRef(env, obj, "obj"); // throws

// after
JObject g = registry.getOrCreateGlobalRef(env, obj); // returns existing ref if IsSameObject match
// and call JNIUtil.release(env, g) when done, before re-creating
Defensive patterns

Strategy: validation

Validate before calling

// Before creating a global ref, check whether you already hold one for the same object:
JObject existing = registry.lookupSameObject(env, candidate);
if (existing != null && JNIUtil.IsSameObject(env, existing, candidate)) {
    return existing; // reuse, do not create a duplicate
}
NewGlobalRef(env, candidate, "name");

Try / catch

try {
    NewGlobalRef(env, obj, tag);
} catch (IllegalArgumentException e) {
    if (e.getMessage() != null && e.getMessage().startsWith("Global JNI handle already exists")) {
        // fetch the existing ref from your registry instead of creating a new one
    } else {
        throw e;
    }
}

Prevention

When it happens

Trigger: Calling NewGlobalRef (the JNIUtil helper that routes through this check) twice for the same host object without deleting the first reference; retry paths that re-create a global ref after a failure without cleaning up; two features both pinning the same well-known object (e.g. the same class or exception instance).

Common situations: Idempotency bugs in JNI glue code where a setup routine runs twice; concurrent initialization racing to pin the same singleton object; missing DeleteGlobalRef on a previous reference before re-creating it.

Related errors


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