Final Configuration Class Blocks Bean Proxying
ID |
java.config_classes_final |
Severity |
high |
Remediation Complexity |
trivial |
Remediation Risk |
low |
Remediation Effort |
low |
Resource |
Reliability |
Language |
Java |
Tags |
best-practice, reliability |
Description
Reports @Configuration classes declared final when they also declare at least one @Bean method. Spring generates a CGLIB subclass of the configuration class so that a call from one @Bean method to another is intercepted and returns the already-created singleton instead of a fresh object. A final class cannot be subclassed, so that interception never happens.
Rationale
Without the subclass proxy, a call from one @Bean method to another runs as a plain Java method call: it builds a brand-new object instead of returning the bean already registered in the container. Depending on the Spring version, this either surfaces as a startup failure while the context is being built, or it silently produces duplicate instances of what should be a single shared bean.
// Bad: final blocks the CGLIB proxy that inter-bean calls rely on
@Configuration
public final class DataSourceConfig {
@Bean
public ConnectionPool connectionPool() {
return new ConnectionPool();
}
@Bean
public QueryRunner queryRunner() {
return new QueryRunner(connectionPool()); // may get a second ConnectionPool
}
}
Remediation
Drop the final modifier so Spring can create the proxy. If the class genuinely never calls one @Bean method from another, set @Configuration(proxyBeanMethods = false) instead: this tells Spring to skip the proxy, and final becomes safe again, but any cross-bean dependency must then be requested as a method parameter rather than through a direct call.
// Good: non-final, so Spring can proxy the inter-bean call
@Configuration
public class DataSourceConfig {
@Bean
public ConnectionPool connectionPool() {
return new ConnectionPool();
}
@Bean
public QueryRunner queryRunner() {
return new QueryRunner(connectionPool()); // resolves to the managed singleton
}
}