When enabling connection pooling for R2DBC through both, the URL (spring.r2dbc.url=r2dbc:pool:mysql:…) and by setting the corresponding property (spring.r2dbc.pool.enabled=true), then Spring Boot backs off entirely from creating a ConnectionFactory. In further consequence, downstream components such as R2dbcEntityTemplate are not created leading to hard-to-understand errors such as:

Error creating bean with name 'healthCheckRepository' […] nested exception is org.springframework.beans.factory.NoSuchBeanDefinitionException: No bean named 'r2dbcEntityTemplate' available
//... 
Description:

Parameter 0 of constructor in application.service.HealthCheckService required a bean named 'r2dbcEntityTemplate' that could not be found. 


Action:

Consider defining a bean named 'r2dbcEntityTemplate' in your configuration.

It would be good to improve the response by either failing hard in such a case or by dropping the pooling configuration advice from the property-based configuration.

Original ticket: spring-projects/spring-data-r2dbc#659

Comment From: wilkinsona

Thanks for raising this, @mp911de. Another discrepancy in the behaviour is when the user's indicated that they want to use pooling but r2dbc-pool isn't on the classpath. If they set spring.r2dbc.pool.enabled then the auto-configuration backs off and they're silently left without a ConnectionFactory. On the other hand, if they set spring.r2dbc.url=r2dbc:pool:… then the app fails to start:

org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'connectionFactory' defined in class path resource [org/springframework/boot/autoconfigure/r2dbc/ConnectionFactoryConfigurations$Generic.class]: Bean instantiation via factory method failed; nested exception is org.springframework.beans.BeanInstantiationException: Failed to instantiate [io.r2dbc.spi.ConnectionFactory]: Factory method 'connectionFactory' threw exception; nested exception is java.lang.IllegalStateException: Unable to create a ConnectionFactory for 'ConnectionFactoryOptions{options={driver=pool, protocol=h2:mem, database=testing-stuff, options=DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=FALSE}}'. Available drivers: [ h2 ]
    at org.springframework.beans.factory.support.ConstructorResolver.instantiate(ConstructorResolver.java:658) ~[spring-beans-5.3.10.jar:5.3.10]
    at org.springframework.beans.factory.support.ConstructorResolver.instantiateUsingFactoryMethod(ConstructorResolver.java:638) ~[spring-beans-5.3.10.jar:5.3.10]
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.instantiateUsingFactoryMethod(AbstractAutowireCapableBeanFactory.java:1352) ~[spring-beans-5.3.10.jar:5.3.10]
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBeanInstance(AbstractAutowireCapableBeanFactory.java:1195) ~[spring-beans-5.3.10.jar:5.3.10]
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:582) ~[spring-beans-5.3.10.jar:5.3.10]
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:542) ~[spring-beans-5.3.10.jar:5.3.10]
    at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:335) ~[spring-beans-5.3.10.jar:5.3.10]
    at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:234) ~[spring-beans-5.3.10.jar:5.3.10]
    at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:333) ~[spring-beans-5.3.10.jar:5.3.10]
    at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:208) ~[spring-beans-5.3.10.jar:5.3.10]
    at org.springframework.beans.factory.support.DefaultListableBeanFactory.preInstantiateSingletons(DefaultListableBeanFactory.java:944) ~[spring-beans-5.3.10.jar:5.3.10]
    at org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(AbstractApplicationContext.java:918) ~[spring-context-5.3.10.jar:5.3.10]
    at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:583) ~[spring-context-5.3.10.jar:5.3.10]
    at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:754) ~[spring-boot-2.5.5.jar:2.5.5]
    at org.springframework.boot.SpringApplication.refreshContext(SpringApplication.java:434) ~[spring-boot-2.5.5.jar:2.5.5]
    at org.springframework.boot.SpringApplication.run(SpringApplication.java:338) ~[spring-boot-2.5.5.jar:2.5.5]
    at org.springframework.boot.SpringApplication.run(SpringApplication.java:1343) ~[spring-boot-2.5.5.jar:2.5.5]
    at org.springframework.boot.SpringApplication.run(SpringApplication.java:1332) ~[spring-boot-2.5.5.jar:2.5.5]
    at com.example.demo.R2dbcWithoutPoolTestApplication.main(R2dbcWithoutPoolTestApplication.java:12) ~[main/:na]
Caused by: org.springframework.beans.BeanInstantiationException: Failed to instantiate [io.r2dbc.spi.ConnectionFactory]: Factory method 'connectionFactory' threw exception; nested exception is java.lang.IllegalStateException: Unable to create a ConnectionFactory for 'ConnectionFactoryOptions{options={driver=pool, protocol=h2:mem, database=testing-stuff, options=DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=FALSE}}'. Available drivers: [ h2 ]
    at org.springframework.beans.factory.support.SimpleInstantiationStrategy.instantiate(SimpleInstantiationStrategy.java:185) ~[spring-beans-5.3.10.jar:5.3.10]
    at org.springframework.beans.factory.support.ConstructorResolver.instantiate(ConstructorResolver.java:653) ~[spring-beans-5.3.10.jar:5.3.10]
    ... 18 common frames omitted
Caused by: java.lang.IllegalStateException: Unable to create a ConnectionFactory for 'ConnectionFactoryOptions{options={driver=pool, protocol=h2:mem, database=testing-stuff, options=DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=FALSE}}'. Available drivers: [ h2 ]
    at io.r2dbc.spi.ConnectionFactories.get(ConnectionFactories.java:145) ~[r2dbc-spi-0.8.5.RELEASE.jar:na]
    at org.springframework.boot.r2dbc.ConnectionFactoryBuilder$OptionsCapableWrapper.buildAndWrap(ConnectionFactoryBuilder.java:203) ~[spring-boot-2.5.5.jar:2.5.5]
    at org.springframework.boot.r2dbc.ConnectionFactoryBuilder.build(ConnectionFactoryBuilder.java:189) ~[spring-boot-2.5.5.jar:2.5.5]
    at org.springframework.boot.autoconfigure.r2dbc.ConnectionFactoryConfigurations.createConnectionFactory(ConnectionFactoryConfigurations.java:61) ~[spring-boot-autoconfigure-2.5.5.jar:2.5.5]
    at org.springframework.boot.autoconfigure.r2dbc.ConnectionFactoryConfigurations$Generic.connectionFactory(ConnectionFactoryConfigurations.java:100) ~[spring-boot-autoconfigure-2.5.5.jar:2.5.5]
    at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:na]
    at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) ~[na:na]
    at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[na:na]
    at java.base/java.lang.reflect.Method.invoke(Method.java:566) ~[na:na]
    at org.springframework.beans.factory.support.SimpleInstantiationStrategy.instantiate(SimpleInstantiationStrategy.java:154) ~[spring-beans-5.3.10.jar:5.3.10]
    ... 19 common frames omitted

I'll flag this one for team discussion as I'm not sure what our best course of action is to make things behave more consistently and intuitively and what changes we'll be able to make in a maintenance release versus our next minor release.

Comment From: philwebb

We're going to make a URL with pool and the use of Pool properties mutually exclusive. Since this change might be a little involved, and there's a work-around, we'll fix this in 2.6.x rather than 2.4.x.

Comment From: wilkinsona

Making the properties mutually exclusive isn't too bad: https://github.com/wilkinsona/spring-boot/tree/gh-28144.

The implementation thus far has raised a couple of questions:

  • What should happen if the user sets spring.r2dbc.url=r2dbc:pool:… and spring.r2dbc.pool.enabled=false? With the changes as currently written it fails due to the properties now being mutually exclusive. I wonder if users would expect it to honour the URL-based configuration but without pooling.
  • Do we want to do anything about the difference in behaviour with spring.r2dbc.pool.enabled=true or spring.r2dbc.url=r2dbc:pool:… when r2dbc-pool is missing. The former silently backs off and gives you a plain ConnectionFactory. The latter fails.

Comment From: philwebb

What should happen if the user sets spring.r2dbc.url=r2dbc:pool:… and spring.r2dbc.pool.enabled=false? With the changes as currently written it fails due to the properties now being mutually exclusive.

I think that's our best option for now. We can always revisit it if someone raises an issue later.

Do we want to do anything about the difference in behaviour with spring.r2dbc.pool.enabled=true or spring.r2dbc.url=r2dbc:pool:… when r2dbc-pool is missing

I think we should always fail with an exception. It feels like a configuration error to me.