oracle/graal · error · IllegalArgumentException

Annotation value type %s is not an annotation interface

Error message

Annotation value type %s is not an annotation interface

What it means

After resolving the annotation value's type to a host Class, toAnnotation verifies it is actually an annotation interface via Class.isAnnotation(); if not (a class, interface, or enum was found where an annotation type was expected), this exception names the offending class. It guards the subsequent asSubclass(Annotation.class) from failing less clearly.

Source

Thrown at compiler/src/jdk.graal.compiler.vmaccess/src/jdk/graal/compiler/vmaccess/HostAnnotationValueConverter.java:95

     * @return the converted annotation, or {@code null} when {@code annotationValue} is null
     * @throws IllegalArgumentException if the annotation or one of its elements is incompatible
     *             with its declared type
     */
    public static <T extends Annotation> T toAnnotation(AnnotationValue annotationValue, Class<T> expectedType, Function<ResolvedJavaType, Class<?>> typeToClass) {
        if (annotationValue == null) {
            return null;
        }
        Objects.requireNonNull(expectedType);
        Objects.requireNonNull(typeToClass);
        if (annotationValue.isError()) {
            throw annotationValue.getError();
        }
        Class<?> actualType = typeToClass.apply(annotationValue.getAnnotationType());
        if (actualType == null) {
            throw new IllegalArgumentException("Annotation value type " + annotationValue.getAnnotationType().toJavaName() + " has no host class");
        }
        if (!actualType.isAnnotation()) {
            throw new IllegalArgumentException("Annotation value type " + actualType.getName() + " is not an annotation interface");
        }
        if (!expectedType.isAssignableFrom(actualType)) {
            throw new IllegalArgumentException("Annotation value type " + actualType.getName() + " is not assignable to " + expectedType.getName());
        }
        Class<? extends Annotation> annotationType = actualType.asSubclass(Annotation.class);
        Annotation annotation = annotationValue.toAnnotation(annotationType, (value, type) -> createAnnotation(value, type, typeToClass));
        return expectedType.cast(annotation);
    }

    /**
     * Materializes a host-owned JDK annotation proxy from JVMCI annotation metadata.
     */
    private static <T extends Annotation> T createAnnotation(AnnotationValue annotationValue, Class<T> annotationType, Function<ResolvedJavaType, Class<?>> typeToClass) {
        Map<String, Object> memberValues = new LinkedHashMap<>();
        Map<String, Object> annotationElements = annotationValue.getElements();
        AnnotationValueType annotationValueType = AnnotationValueType.getInstance(annotationValue.getAnnotationType());
        AnnotationValueValidation.validateElements(annotationValue, annotationValueType);
        Map<String, Object> memberDefaults = annotationValueType.memberDefaults();

View on GitHub (pinned to a66e9ccd1d)

Solutions

  1. Fix the type resolution so the correct annotation interface is returned (fully qualified binary names, correct loader).
  2. Eliminate the name collision: rename the shadowing class or adjust loader delegation.
  3. Add a pre-check Class.isAnnotation() in your own lookup with a descriptive error before calling toAnnotation.
  4. Verify no stale compiled classes are on the classpath overriding the expected annotation.

Example fix

// before
Class<?> c = appLoader.loadClass("com.foo.MyAnno" /* actually a plain class */);
// after
Class<?> c = appLoader.loadClass("com.foo.MyAnno");
if (!c.isAnnotation()) throw new IllegalStateException(c + " shadows annotation com.foo.MyAnno");
Defensive patterns

Strategy: validation

Validate before calling

Class<?> c = typeToClass.apply(annotationValue.getAnnotationType());
if (c != null && !c.isAnnotation()) { /* wrong type resolved — fix mapping before converting */ }

Type guard

static boolean isAnnotationClass(Class<?> c) { return c != null && c.isAnnotation(); }

Try / catch

try { toAnnotation(av, Expected.class, fn); } catch (IllegalArgumentException e) { if (e.getMessage().contains("not an annotation interface")) { /* binary-name collision: audit classloaders */ } else throw e; }

Prevention

When it happens

Trigger: typeToClass returns a non-annotation Class for the AnnotationValue's type — typically because two different types share the binary name across classloaders, or a lookup function maps by simple name and picks the wrong type.

Common situations: Classloader shadowing where an application class shadows an annotation's binary name; typo'd manual type mappings; replay/snapshot systems that recorded a placeholder class for what should be an annotation.

Related errors


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