Skip to content

AbstractAdvisingBeanPostProcessor should gracefully skip final classes instead of throwing IllegalArgumentException #36353

@sleicht

Description

@sleicht

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=trueIllegalArgumentException: Cannot subclass final class
  • proxyTargetClass=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 @ConfigurationProperties since 3.x
  • @Validated on @ConfigurationProperties is a common and documented pattern
  • Kotlin classes are final by default
  • The consuming application often cannot modify library classes

Spring Framework version: 6.2.15 (Spring Boot 3.5.10)

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions