-
Notifications
You must be signed in to change notification settings - Fork 38.9k
Description
When MethodValidationPostProcessor (which extends AbstractAdvisingBeanPostProcessor) encounters a bean whose class is final — such as a Java Record — it attempts to create a CGLIB proxy and fails with:
java.lang.IllegalArgumentException: Cannot subclass final class com.example.MyConfigurationProperties
With proxyTargetClass=false, it falls back to a JDK dynamic proxy, which then causes injection failures downstream when the bean is injected by concrete class type:
The bean 'my-config-com.example.MyConfigurationProperties' could not be injected because it is a JDK dynamic proxy
Use case
A third-party library defines a @ConfigurationProperties class as a Java Record implementing an interface with @NotNull constraints:
@ConfigurationProperties("my.config")
@Validated
public record MyConfigurationProperties(List<Entry> entries) implements MyInterface {
// ...
}A service in the same library injects this bean by concrete type:
public class MyService {
public MyService(MyConfigurationProperties config) { /* ... */ }
}The consuming application registers a MethodValidationPostProcessor bean. Because the record has @Validated, the post-processor attempts to create an AOP proxy — which is impossible for a final class regardless of the proxy strategy:
proxyTargetClass=true→IllegalArgumentException: Cannot subclass final classproxyTargetClass=false→ JDK proxy created, but injection by concrete type fails
Current workaround
Override postProcessAfterInitialization to skip final classes:
@Bean
public static MethodValidationPostProcessor methodValidationPostProcessor() {
return new MethodValidationPostProcessor() {
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) {
if (Modifier.isFinal(bean.getClass().getModifiers())) {
return bean;
}
return super.postProcessAfterInitialization(bean, beanName);
}
};
}Suggested improvement
AbstractAdvisingBeanPostProcessor.postProcessAfterInitialization (or AopUtils) should detect final classes and gracefully skip proxy creation — logging a debug/warning message instead of throwing an exception. This is especially relevant given that:
- Spring Boot recommends Java Records for
@ConfigurationPropertiessince 3.x @Validatedon@ConfigurationPropertiesis a common and documented pattern- Kotlin classes are
finalby default - The consuming application often cannot modify library classes
Spring Framework version: 6.2.15 (Spring Boot 3.5.10)