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
- Pass the expectedType that matches the annotation actually stored in the AnnotationValue (check getAnnotationType().toJavaName()).
- Branch on the resolved annotation type before conversion when handling heterogeneous annotations.
- If a member annotation can legitimately be several types, dispatch on getAnnotationType() rather than forcing one expectedType.
- 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
- Dispatch on getAnnotationType().toJavaName() for heterogeneous annotation sets.
- Update expectedType constants after refactors.
- Do not assume all member annotations share the outer annotation's type.
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
- Annotation member %s.%s does not match declared type %s
- Annotation value type %s has no host class
- Annotation value type %s is not an annotation interface
- Unsupported annotation error element: %s
- Annotation class element type %s has no host class
AI-assisted analysis of oracle/graal@a66e9ccd1d (2026-08-14).
Data as JSON: /api/errors/b0af063f040d95f4.
Report an issue: GitHub.