Attempted to upgrade from v2.6.13 to 2.7.11 recently due to some security issues requiring Spring Framework upgrade. Trying to run the application was resulting in a circular reference failure.
Description:
The dependencies of some of the beans in the application context form a cycle:
webMvcMetricsFilter defined in class path resource [org/springframework/boot/actuate/autoconfigure/metrics/web/servlet/WebMvcMetricsAutoConfiguration.class]
???????
| simpleMeterRegistry defined in class path resource [org/springframework/boot/actuate/autoconfigure/metrics/export/simple/SimpleMetricsExportAutoConfiguration.class]
? ?
| dataSourcePoolMetadataMeterBinder defined in class path resource [org/springframework/boot/actuate/autoconfigure/metrics/jdbc/DataSourcePoolMetricsAutoConfiguration$DataSourcePoolMetadataMetricsConfiguration.class]
? ?
| getDataSource defined in com.example.demo.DemoApplication
? ?
| getRestTemplate defined in com.example.demo.DemoApplication
? ?
| restTemplateBuilder defined in class path resource [org/springframework/boot/autoconfigure/web/client/RestTemplateAutoConfiguration.class]
? ?
| restTemplateBuilderConfigurer defined in class path resource [org/springframework/boot/autoconfigure/web/client/RestTemplateAutoConfiguration.class]
? ?
| metricsRestTemplateCustomizer defined in class path resource [org/springframework/boot/actuate/autoconfigure/metrics/web/client/RestTemplateMetricsConfiguration.class]
???????
Action:
Relying upon circular references is discouraged and they are prohibited by default. Update your application to remove the dependency cycle between beans. As a last resort, it may be possible to break the cycle automatically by setting spring.main.allow-circular-references to true.
Created a new application from Spring Initilizr using Maven, Java, v2.7.11, JAR, JDK 17 to try and determine if it was anything custom in my existing application, and was able to reproduce the issue with very minimal changes and dependencies.
The error started occurring after adding the spring-boot-starter-actuator and spring-boot-starter-data-jpa dependencies and injecting the RestTemplateBuilder class so that it is part of the getDataSource Bean dependencies. The same error happens if injecting the RestTemplateBuilder directly to the getDataSource Bean rather than indirectly through the getRestTemplate Bean.
@Bean
public RestTemplate getRestTemplate(RestTemplateBuilder builder) {
return builder.build();
}
@Bean
public DataSource getDataSource(RestTemplate restTemplate) {
DataSourceBuilder dataSourceBuilder = DataSourceBuilder.create();
dataSourceBuilder.driverClassName("com.microsoft.sqlserver.jdbc.SQLServerDriver");
dataSourceBuilder.url("<DB URL>");
dataSourceBuilder.username("<DB USER NAME>");
dataSourceBuilder.password("DB PASSWORD");
return dataSourceBuilder.build();
}
The only work-around I found is to use the spring.main.allow-circular-references=true setting in the application.properties file.
Example application: https://github.com/joshua-phillips/spring-boot-2-7-x-circular-reference-example
Comment From: wilkinsona
Why are you injecting RestTemplate into a @Bean method that creates a DataSource?
Comment From: joshua-phillips
@wilkinsona we retrieve the data source values as well as other secret data from Hashicorp Vault using their REST API. Just as a quick test, I checked if it is just the fact that RestTemplateBuilder is being instanced prior to the getDataSource Bean method regardless of whether it is directly injected into getDataSource and it is the same circular reference failure.
So I can't even do something like this where the getVaultData Bean method gets the RestTemplate instance to retrieve the data and then the VaultData instance is injected to getDataSource
@Bean
public RestTemplate getRestTemplate(RestTemplateBuilder builder) {
return builder.build();
}
@Bean
public VaultData getVaultData(RestTemplate restTemplate) {
// perform HC Vault REST API calls here to get secret data
return new VaultData();
}
@Bean
public DataSource getDataSource(VaultData vaultData) {
DataSourceBuilder dataSourceBuilder = DataSourceBuilder.create();
dataSourceBuilder.driverClassName("com.microsoft.sqlserver.jdbc.SQLServerDriver");
dataSourceBuilder.url(vaultData.getUrl());
dataSourceBuilder.username(vaultData.getUsername());
dataSourceBuilder.password(vaultData.getPassword());
return dataSourceBuilder.build();
}
For my test VaultData is just a hard-coded class
public class VaultData {
public String getUrl() {
return "<DB URL>";
}
public String getUsername() {
return "<DB USER NAME>";
}
public String getPassword() {
return "<DB PASSWORD>";
}
}
Comment From: wilkinsona
Thanks for the additional information.
This has been fixed in 3.0 by the changes made for https://github.com/spring-projects/spring-boot/issues/30636. Unfortunately, we don't feel that we can make those changes in a maintenance release so they're only available in 3.0 and later.
In the meantime, you can avoid the problem by creating your own RestTemplate to make the Vault calls. That can either be through directly creating a new RestTemplate instance or by using a new RestTemplateBuilder to create the RestTemplate.
Comment From: joshua-phillips
@wilkinsona thanks for the info and options! That will hopefully allow us to remove the spring.main.allow-circular-references=true setting :)
Comment From: nightswimmings
The problem of not using RestTemplateBuilder and a custom RestTemplate instead is that you lose Observability autoconfiguration