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
- Fix the type resolution so the correct annotation interface is returned (fully qualified binary names, correct loader).
- Eliminate the name collision: rename the shadowing class or adjust loader delegation.
- Add a pre-check Class.isAnnotation() in your own lookup with a descriptive error before calling toAnnotation.
- 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
- Resolve types by binary name with an explicit loader.
- Avoid simple-name-based type mappings.
- Watch for application classes shadowing annotation binary names.
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
- Annotation value type %s has no host class
- Annotation class element type %s has no host class
- Annotation member %s.%s does not match declared type %s
- Annotation value type %s is not assignable to %s
- Unsupported annotation error element: %s
AI-assisted analysis of oracle/graal@a66e9ccd1d (2026-08-14).
Data as JSON: /api/errors/3b7e034f57b11461.
Report an issue: GitHub.