oracle/graal · error · IllegalArgumentException

Annotation value type %s has no host class

Error message

Annotation value type %s has no host class

What it means

HostAnnotationValueConverter.toAnnotation resolves the annotation's ResolvedJavaType to a host Class via the caller-supplied typeToClass function; if that function returns null for the annotation type itself, this IllegalArgumentException is thrown naming the type. It means the type lookup/mapping you provided cannot find a host class for an annotation type that actually has elements to convert.

Source

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

     * @param annotationValue the annotation metadata to convert
     * @param expectedType the annotation type to which the result must be assignable
     * @param typeToClass maps JVMCI types to their corresponding host classes
     * @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();

View on GitHub (pinned to a66e9ccd1d)

Solutions

  1. Extend the typeToClass function to resolve the named annotation type (delegate to Class.forName with the right loader, or a JVMCI host-mapping service).
  2. Ensure the classloader used for lookup can see the annotation interface (add the containing module/jar).
  3. If the type genuinely has no host class, filter such AnnotationValues out before conversion instead of calling toAnnotation.
  4. Check annotationValue.isError() first — error values carry their own exception path.

Example fix

// before
Function<ResolvedJavaType, Class<?>> f = t -> JAVA_ONLY.get(t.toJavaName()); // null for com.foo.Bar
// after
Function<ResolvedJavaType, Class<?>> f = t -> Class.forName(t.toJavaName(), false, appLoader);
Defensive patterns

Strategy: validation

Validate before calling

ResolvedJavaType at = annotationValue.getAnnotationType();
if (typeToClass.apply(at) == null) { /* exclude or resolve the type before calling toAnnotation */ }

Try / catch

try { HostAnnotationValueConverter.toAnnotation(av, Expected.class, fn); } catch (IllegalArgumentException e) { if (e.getMessage().contains("no host class")) { /* widen fn's loader/search set */ } else throw e; }

Prevention

When it happens

Trigger: Calling toAnnotation with a typeToClass implementation that returns null for the annotation's own type — e.g. a lookup restricted to a specific classloader that cannot see the annotation, or that only maps a whitelist of types.

Common situations: Custom classloader hierarchies (embedded/isolated loads), snapshot-replay where the annotation type was not recorded, or a converter configured to map only java.* types while the annotation lives in a jdk.internal.* or application package.

Related errors


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