spring-projects/spring-framework · error · FatalBeanException

Failed to obtain BeanInfo for class [${beanClass.getName()}]

Error message

Failed to obtain BeanInfo for class [${beanClass.getName()}]

What it means

FatalBeanException thrown by CachedIntrospectionResults when java.beans.Introspector.getBeanInfo(beanClass) (or the surrounding introspection loop) raises IntrospectionException. It means the JDK's bean introspection refused to process the class at all - the class could not be analysed for property descriptors, blocking any BeanWrapper / copyProperties / DataBinder usage of it.

Source

Thrown at spring-beans/src/main/java/org/springframework/beans/CachedIntrospectionResults.java:299

					readMethodNames.add(readMethod.getName());
				}
			}

			// Explicitly check implemented interfaces for setter/getter methods as well,
			// in particular for interface default methods.
			Class<?> currClass = beanClass;
			while (currClass != null && currClass != Object.class) {
				introspectInterfaces(beanClass, currClass, readMethodNames);
				currClass = currClass.getSuperclass();
			}

			// Check for record-style accessors without prefix: for example, "lastName()"
			// - accessor method directly referring to instance field of same name
			// - same convention for component accessors of Java 15 record classes
			introspectPlainAccessors(beanClass, readMethodNames);
		}
		catch (IntrospectionException ex) {
			throw new FatalBeanException("Failed to obtain BeanInfo for class [" + beanClass.getName() + "]", ex);
		}
	}

	private void introspectInterfaces(Class<?> beanClass, Class<?> currClass, Set<String> readMethodNames)
			throws IntrospectionException {

		for (Class<?> ifc : currClass.getInterfaces()) {
			if (!ClassUtils.isJavaLanguageInterface(ifc)) {
				for (PropertyDescriptor pd : getBeanInfo(ifc).getPropertyDescriptors()) {
					PropertyDescriptor existingPd = this.propertyDescriptors.get(pd.getName());
					if (existingPd == null ||
							(existingPd.getReadMethod() == null && pd.getReadMethod() != null)) {
						// GenericTypeAwarePropertyDescriptor leniently resolves a set* write method
						// against a declared read method, so we prefer read method descriptors here.
						pd = buildGenericTypeAwarePropertyDescriptor(beanClass, pd);
						if (pd.getWriteMethod() == null &&
								isInvalidReadOnlyPropertyType(pd.getPropertyType(), beanClass)) {
							// Ignore read-only properties such as ClassLoader - no need to bind to those

View on GitHub (pinned to e8729d0438)

Solutions

  1. Inspect the attached IntrospectionException cause for which method/class pair triggered it.
  2. Align the conflicting getter/setter so their types are mutually assignable.
  3. Avoid exposing the problematic class to Spring's BeanWrapper; map it manually or via a converter.
  4. If generated bytecode, regenerate with a stable tool version and verify with java.beans.Introspector.getBeanInfo in a standalone test.

Example fix

// reproducer
java.beans.BeanInfo info = java.beans.Introspector.getBeanInfo(MyClass.class);
// before: setItems(List<String>) getItems() returns Collection -> may conflict
// after: align types
public List<String> getItems() { ... }
public void setItems(List<String> items) { ... }
Defensive patterns

Strategy: try-catch

Validate before calling

// Pre-flight: ask the JDK Introspector before Spring wraps it
try {
    java.beans.BeanInfo info = java.beans.Introspector.getBeanInfo(MyClass.class);
    // exercise descriptors to surface latent IntrospectionException
    for (PropertyDescriptor pd : info.getPropertyDescriptors()) {
        pd.getReadMethod(); pd.getWriteMethod();
    }
} catch (java.beans.IntrospectionException e) {
    throw new IllegalStateException("Class cannot be introspected; fix accessors", e);
}

Type guard

static boolean isIntrospectable(Class<?> c) {
    try { java.beans.Introspector.getBeanInfo(c); return true; }
    catch (java.beans.IntrospectionException e) { return false; }
}

Try / catch

try {
    return CachedIntrospectionResults.forClass(beanClass);
} catch (FatalBeanException ex) {
    if (ex.getCause() instanceof java.beans.IntrospectionException) {
        // log offending class and switch to manual mapping
        log.error("Cannot introspect {}", beanClass.getName(), ex.getCause());
    }
    throw ex;
}

Prevention

When it happens

Trigger: First access of a class through CachedIntrospectionResults.forClass(...) when the class has a malformed PropertyDescriptor pair the Introspector cannot reconcile (e.g. a setter whose parameter type is incompatible with the getter return in a way the Introspector flags), or getBeanInfo fails on a classloader/security issue.

Common situations: A class with conflicting getter/setter signatures introduced by a refactor; a class generated by a bytecode tool producing signatures the Introspector rejects; rare on hand-written POJOs, more common on generated/proxied classes with non-standard accessor methods.

Related errors


AI-assisted analysis of spring-projects/spring-framework@e8729d0438 (2026-08-04). Data as JSON: /data/errors/4aa9f9b4b44bda84.json. Report an issue: GitHub.