oracle/graal · error · IllegalArgumentException

Annotation value type %s is not assignable to %s

Error message

Annotation value type %s is not assignable to %s

What it means

The third structural check in HostAnnotationValueConverter.toAnnotation: the resolved annotation class must be assignable to the caller's expectedType parameter, otherwise this exception names both classes. It exists so a mismatched cast fails immediately with context instead of ClassCastException deep inside the JDK proxy creation.

Source

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

     */
    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();
        for (Method member : annotationType.getDeclaredMethods()) {
            String memberName = member.getName();
            Object memberValue = annotationElements.get(memberName);

View on GitHub (pinned to a66e9ccd1d)

Solutions

  1. Pass the expectedType that matches the annotation actually stored in the AnnotationValue (check getAnnotationType().toJavaName()).
  2. Branch on the resolved annotation type before conversion when handling heterogeneous annotations.
  3. If a member annotation can legitimately be several types, dispatch on getAnnotationType() rather than forcing one expectedType.
  4. Update stale expectedType constants after refactors.

Example fix

// before
HostAnnotationValueConverter.toAnnotation(av, CacheKey.class, fn); // av is really @Cached
// after
Class<? extends Annotation> t = switch (av.getAnnotationType().toJavaName()) {
    case "jdk.vm.ci.meta.Cached" -> Cached.class;
    default -> throw new IllegalStateException("unexpected " + av.getAnnotationType());
};
HostAnnotationValueConverter.toAnnotation(av, t, fn);
Defensive patterns

Strategy: validation

Validate before calling

String actual = annotationValue.getAnnotationType().toJavaName();
if (!expectedType.getName().equals(actual) && !expectedType.getName().startsWith(actual)) {
    // pick the right expectedType for `actual` before calling toAnnotation
}

Type guard

static boolean annotationTypeIs(AnnotationValue av, Class<? extends Annotation> expected) {
    return av.getAnnotationType().toJavaName().equals(expected.getName());
}

Try / catch

try { toAnnotation(av, Expected.class, fn); } catch (IllegalArgumentException e) { if (e.getMessage().contains("not assignable to")) { /* dispatch on av.getAnnotationType() instead */ } else throw e; }

Prevention

When it happens

Trigger: Calling toAnnotation(annotationValue, MyAnnotation.class, ...) when the AnnotationValue's actual type is a different annotation (e.g. passing the wrong expectedType constant, or receiving an annotation you did not ask for).

Common situations: Generic code that assumes all annotations in a set share a type; refactorings that change the queried annotation class but not all call sites; processing mixed annotation metadata where a member annotation has a different type than the outer one.

Related errors


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