{"id":"4aa9f9b4b44bda84","repo":"spring-projects/spring-framework","slug":"failed-to-obtain-beaninfo-for-class-beanclass-g","errorCode":null,"errorMessage":"Failed to obtain BeanInfo for class [${beanClass.getName()}]","messagePattern":"Failed to obtain BeanInfo for class \\[(.+?)\\]","errorType":"exception","errorClass":"FatalBeanException","httpStatus":null,"severity":"error","filePath":"spring-beans/src/main/java/org/springframework/beans/CachedIntrospectionResults.java","lineNumber":299,"sourceCode":"\t\t\t\t\treadMethodNames.add(readMethod.getName());\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Explicitly check implemented interfaces for setter/getter methods as well,\n\t\t\t// in particular for interface default methods.\n\t\t\tClass<?> currClass = beanClass;\n\t\t\twhile (currClass != null && currClass != Object.class) {\n\t\t\t\tintrospectInterfaces(beanClass, currClass, readMethodNames);\n\t\t\t\tcurrClass = currClass.getSuperclass();\n\t\t\t}\n\n\t\t\t// Check for record-style accessors without prefix: for example, \"lastName()\"\n\t\t\t// - accessor method directly referring to instance field of same name\n\t\t\t// - same convention for component accessors of Java 15 record classes\n\t\t\tintrospectPlainAccessors(beanClass, readMethodNames);\n\t\t}\n\t\tcatch (IntrospectionException ex) {\n\t\t\tthrow new FatalBeanException(\"Failed to obtain BeanInfo for class [\" + beanClass.getName() + \"]\", ex);\n\t\t}\n\t}\n\n\tprivate void introspectInterfaces(Class<?> beanClass, Class<?> currClass, Set<String> readMethodNames)\n\t\t\tthrows IntrospectionException {\n\n\t\tfor (Class<?> ifc : currClass.getInterfaces()) {\n\t\t\tif (!ClassUtils.isJavaLanguageInterface(ifc)) {\n\t\t\t\tfor (PropertyDescriptor pd : getBeanInfo(ifc).getPropertyDescriptors()) {\n\t\t\t\t\tPropertyDescriptor existingPd = this.propertyDescriptors.get(pd.getName());\n\t\t\t\t\tif (existingPd == null ||\n\t\t\t\t\t\t\t(existingPd.getReadMethod() == null && pd.getReadMethod() != null)) {\n\t\t\t\t\t\t// GenericTypeAwarePropertyDescriptor leniently resolves a set* write method\n\t\t\t\t\t\t// against a declared read method, so we prefer read method descriptors here.\n\t\t\t\t\t\tpd = buildGenericTypeAwarePropertyDescriptor(beanClass, pd);\n\t\t\t\t\t\tif (pd.getWriteMethod() == null &&\n\t\t\t\t\t\t\t\tisInvalidReadOnlyPropertyType(pd.getPropertyType(), beanClass)) {\n\t\t\t\t\t\t\t// Ignore read-only properties such as ClassLoader - no need to bind to those","sourceCodeStart":281,"sourceCodeEnd":317,"githubUrl":"https://github.com/spring-projects/spring-framework/blob/e8729d043887bf0d0baf91e062e909b56eb2b708/spring-beans/src/main/java/org/springframework/beans/CachedIntrospectionResults.java#L281-L317","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Inspect the attached IntrospectionException cause for which method/class pair triggered it.","Align the conflicting getter/setter so their types are mutually assignable.","Avoid exposing the problematic class to Spring's BeanWrapper; map it manually or via a converter.","If generated bytecode, regenerate with a stable tool version and verify with java.beans.Introspector.getBeanInfo in a standalone test."],"exampleFix":"// reproducer\njava.beans.BeanInfo info = java.beans.Introspector.getBeanInfo(MyClass.class);\n// before: setItems(List<String>) getItems() returns Collection -> may conflict\n// after: align types\npublic List<String> getItems() { ... }\npublic void setItems(List<String> items) { ... }","handlingStrategy":"try-catch","validationCode":"// Pre-flight: ask the JDK Introspector before Spring wraps it\ntry {\n    java.beans.BeanInfo info = java.beans.Introspector.getBeanInfo(MyClass.class);\n    // exercise descriptors to surface latent IntrospectionException\n    for (PropertyDescriptor pd : info.getPropertyDescriptors()) {\n        pd.getReadMethod(); pd.getWriteMethod();\n    }\n} catch (java.beans.IntrospectionException e) {\n    throw new IllegalStateException(\"Class cannot be introspected; fix accessors\", e);\n}","typeGuard":"static boolean isIntrospectable(Class<?> c) {\n    try { java.beans.Introspector.getBeanInfo(c); return true; }\n    catch (java.beans.IntrospectionException e) { return false; }\n}","tryCatchPattern":"try {\n    return CachedIntrospectionResults.forClass(beanClass);\n} catch (FatalBeanException ex) {\n    if (ex.getCause() instanceof java.beans.IntrospectionException) {\n        // log offending class and switch to manual mapping\n        log.error(\"Cannot introspect {}\", beanClass.getName(), ex.getCause());\n    }\n    throw ex;\n}","preventionTips":["Keep bean accessors standard (get/set with consistent types).","Unit-test Introspector.getBeanInfo for any class exposed to BeanWrapper.","Watch generated/proxied classes - validate their introspection in isolation.","Avoid method overloads that confuse the Introspector."],"tags":["introspection","fatal-bean-exception","spring-beans","beaninfo"],"analyzedSha":"e8729d043887bf0d0baf91e062e909b56eb2b708","analyzedAt":"2026-08-04T19:07:39.725Z","schemaVersion":2}