spring-projects/spring-ai · error · RuntimeException

ClassNotFoundException (wrapped RuntimeException)

Error message

ClassNotFoundException (wrapped RuntimeException)

What it means

AiRuntimeHints.findJsonAnnotatedClassesInPackage scans classpath resources for @JsonSerializable/JSON-annotated classes to register them for native-image reflection; if Class.forName fails to load a discovered class name it wraps ClassNotFoundException in a RuntimeException. This typically indicates inconsistent classpath metadata.

Source

Thrown at spring-ai-model/src/main/java/org/springframework/ai/aot/AiRuntimeHints.java:65

	private static final Log log = LogFactory.getLog(AiRuntimeHints.class);

	/**
	 * Finds classes in a package that are annotated with JsonInclude or have Jackson
	 * annotations.
	 * @param packageName The name of the package to search for annotated classes.
	 * @return A set of TypeReference objects representing the annotated classes found.
	 */
	public static Set<TypeReference> findJsonAnnotatedClassesInPackage(String packageName) {
		var annotationTypeFilter = new AnnotationTypeFilter(JsonInclude.class);
		TypeFilter typeFilter = (metadataReader, metadataReaderFactory) -> {
			try {
				var clazz = Class.forName(metadataReader.getClassMetadata().getClassName());
				return annotationTypeFilter.match(metadataReader, metadataReaderFactory)
						|| !discoverJacksonAnnotatedTypesFromRootType(clazz).isEmpty();
			}
			catch (ClassNotFoundException e) {
				throw new RuntimeException(e);
			}
		};

		return findClassesInPackage(packageName, typeFilter);
	}

	/**
	 * Finds classes in a package that are annotated with JsonInclude or have Jackson
	 * annotations.
	 * @param packageClass The class in the package to search for annotated classes.
	 * @return A set of TypeReference objects representing the annotated classes found.
	 */
	public static Set<TypeReference> findJsonAnnotatedClassesInPackage(Class<?> packageClass) {
		return findJsonAnnotatedClassesInPackage(packageClass.getPackageName());
	}

	/**
	 * Finds all classes in the specified package that match the given type filter.

View on GitHub (pinned to 98a7beda4f)

Solutions

  1. Run a clean build (mvn clean) to remove stale classpath metadata
  2. Verify the class named in the message still exists and matches the scanned package
  3. Fix shading/relocation config so scanned classes remain loadable in the native image
  4. Update the packageName passed to findJsonAnnotatedClassesInPackage to a package that actually contains the JSON-annotated types

Example fix

// before
new AiRuntimeHints().findJsonAnnotatedClassesInPackage("com.example.model");
// after (after clean rebuild, narrow to existing package)
Assert.isTrue(SomeJsonDto.class.getPackageName().startsWith("com.example.model"),
    "Scanned package no longer matches relocated classes");
new AiRuntimeHints().findJsonAnnotatedClassesInPackage("com.example.model");
Defensive patterns

Strategy: try-catch

Validate before calling

// before registering hints, verify the scanned classes resolve
Class.forName("com.example.model.SomeJsonDto", false, getClass().getClassLoader());

Try / catch

try {
    hints.findJsonAnnotatedClassesInPackage(pkg);
} catch (RuntimeException e) {
    if (e.getCause() instanceof ClassNotFoundException cnfe) {
        throw new IllegalStateException("Stale classpath metadata for " + cnfe.getMessage() + "; run a clean build");
    }
    throw e;
}

Prevention

When it happens

Trigger: Running native-image hint registration when a class listed in classpath metadata was removed/renamed, shading broke class visibility, or a scanning filter matched a class not present at runtime.

Common situations: GraalVM native-image builds after package refactor; fat-jar shading that excludes scanned classes; duplicate/stale class entries from old build artifacts.

Related errors


AI-assisted analysis of spring-projects/spring-ai@98a7beda4f (2026-09-11). Data as JSON: /api/errors/172d78c33d100bfe. Report an issue: GitHub.