Spring boot fails to start when using UCP and Spring Boot 3.1.5 with JndiPropertySource enabled.

This happens because UCP exposes a property SSLConext of type javax.net.ssl.SSLContext. The constructor for SSLContext has a parameter of type java.security.Provider. This Provider class implements Map and is therefore treated by Spring Boot as a Map, an attempt will be made to create a Map using CollectionFactory which will cause the error because java.security.Provider does not expose a constructor with no parameters.

The following workarounds are possible :

  1. if JndiPropertySource is not being used, disable it by setting the following property in the spring.properties file: spring.jndi.ignore=true
  2. create the data source programatically.

This is the error when deploying the WAR on a Tomcat server:

23-Nov-2023 15:23:37.329 SEVERE [RMI TCP Connection(2)-127.0.0.1] org.apache.tomcat.util.modeler.BaseModelMBean.invoke Exception invoking method [createStandardContext]
    javax.management.RuntimeOperationsException: Exception invoking method [manageApp]
        at org.apache.tomcat.util.modeler.BaseModelMBean.invoke(BaseModelMBean.java:304)
        at java.management/com.sun.jmx.interceptor.DefaultMBeanServerInterceptor.invoke(DefaultMBeanServerInterceptor.java:803)
        at java.management/com.sun.jmx.mbeanserver.JmxMBeanServer.invoke(JmxMBeanServer.java:802)
        at org.apache.catalina.mbeans.MBeanFactory.createStandardContext(MBeanFactory.java:422)
        at org.apache.catalina.mbeans.MBeanFactory.createStandardContext(MBeanFactory.java:376)
        at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:103)
        at java.base/java.lang.reflect.Method.invoke(Method.java:580)
        at org.apache.tomcat.util.modeler.BaseModelMBean.invoke(BaseModelMBean.java:294)
        at java.management/com.sun.jmx.interceptor.DefaultMBeanServerInterceptor.invoke(DefaultMBeanServerInterceptor.java:803)
        at java.management/com.sun.jmx.mbeanserver.JmxMBeanServer.invoke(JmxMBeanServer.java:802)
        at java.management/com.sun.jmx.remote.security.MBeanServerAccessController.invoke(MBeanServerAccessController.java:472)
        at java.management.rmi/javax.management.remote.rmi.RMIConnectionImpl.doOperation(RMIConnectionImpl.java:1472)
        at java.management.rmi/javax.management.remote.rmi.RMIConnectionImpl$PrivilegedOperation.run(RMIConnectionImpl.java:1310)
        at java.base/java.security.AccessController.doPrivileged(AccessController.java:714)
        at java.management.rmi/javax.management.remote.rmi.RMIConnectionImpl.doPrivilegedOperation(RMIConnectionImpl.java:1412)
        at java.management.rmi/javax.management.remote.rmi.RMIConnectionImpl.invoke(RMIConnectionImpl.java:829)
        at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:103)
        at java.base/java.lang.reflect.Method.invoke(Method.java:580)
        at java.rmi/sun.rmi.server.UnicastServerRef.dispatch(UnicastServerRef.java:360)
        at java.rmi/sun.rmi.transport.Transport$1.run(Transport.java:200)
        at java.rmi/sun.rmi.transport.Transport$1.run(Transport.java:197)
        at java.base/java.security.AccessController.doPrivileged(AccessController.java:714)
        at java.rmi/sun.rmi.transport.Transport.serviceCall(Transport.java:196)
        at java.rmi/sun.rmi.transport.tcp.TCPTransport.handleMessages(TCPTransport.java:598)
        at java.rmi/sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.run0(TCPTransport.java:844)
        at java.rmi/sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.lambda$run$0(TCPTransport.java:721)
        at java.base/java.security.AccessController.doPrivileged(AccessController.java:400)
        at java.rmi/sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.run(TCPTransport.java:720)
        at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1144)
        at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:642)
        at java.base/java.lang.Thread.run(Thread.java:1583)
    Caused by: java.lang.IllegalStateException: Error starting child
        at org.apache.catalina.core.ContainerBase.addChildInternal(ContainerBase.java:686)
        at org.apache.catalina.core.ContainerBase.addChild(ContainerBase.java:658)
        at org.apache.catalina.core.StandardHost.addChild(StandardHost.java:712)
        at org.apache.catalina.startup.HostConfig.manageApp(HostConfig.java:1824)
        at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:103)
        at java.base/java.lang.reflect.Method.invoke(Method.java:580)
        at org.apache.tomcat.util.modeler.BaseModelMBean.invoke(BaseModelMBean.java:294)
        ... 30 more
    Caused by: org.apache.catalina.LifecycleException: Failed to start component [StandardEngine[Catalina].StandardHost[localhost].StandardContext[/my_todo_list_war_exploded]]
        at org.apache.catalina.util.LifecycleBase.handleSubClassException(LifecycleBase.java:419)
        at org.apache.catalina.util.LifecycleBase.start(LifecycleBase.java:186)
        at org.apache.catalina.core.ContainerBase.addChildInternal(ContainerBase.java:683)
        ... 36 more
    Caused by: org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'dataSourceScriptDatabaseInitializer' defined in class path resource [org/springframework/boot/autoconfigure/sql/init/DataSourceInitializationConfiguration.class]: Unsatisfied dependency expressed through method 'dataSourceScriptDatabaseInitializer' parameter 0: Error creating bean with name 'dataSource': Could not bind properties to 'PoolDataSourceImpl' : prefix=spring.datasource.oracleucp, ignoreInvalidFields=false, ignoreUnknownFields=true
        at org.springframework.beans.factory.support.ConstructorResolver.createArgumentArray(ConstructorResolver.java:801)
        at org.springframework.beans.factory.support.ConstructorResolver.instantiateUsingFactoryMethod(ConstructorResolver.java:545)
        at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.instantiateUsingFactoryMethod(AbstractAutowireCapableBeanFactory.java:1332)
        at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBeanInstance(AbstractAutowireCapableBeanFactory.java:1162)
        at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:560)
        at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:520)
        at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:325)
        at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:234)
        at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:323)
        at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:199)
        at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:312)
        at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:199)
        at org.springframework.context.support.AbstractApplicationContext.getBean(AbstractApplicationContext.java:1166)
        at org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(AbstractApplicationContext.java:940)
        at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:616)
        at org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext.refresh(ServletWebServerApplicationContext.java:146)
        at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:738)
        at org.springframework.boot.SpringApplication.refreshContext(SpringApplication.java:440)
        at org.springframework.boot.SpringApplication.run(SpringApplication.java:316)
        at org.springframework.boot.web.servlet.support.SpringBootServletInitializer.run(SpringBootServletInitializer.java:174)
        at org.springframework.boot.web.servlet.support.SpringBootServletInitializer.createRootApplicationContext(SpringBootServletInitializer.java:154)
        at org.springframework.boot.web.servlet.support.SpringBootServletInitializer.onStartup(SpringBootServletInitializer.java:96)
        at org.springframework.web.SpringServletContainerInitializer.onStartup(SpringServletContainerInitializer.java:171)
        at org.apache.catalina.core.StandardContext.startInternal(StandardContext.java:4850)
        at org.apache.catalina.util.LifecycleBase.start(LifecycleBase.java:171)
        ... 37 more
    Caused by: org.springframework.boot.context.properties.ConfigurationPropertiesBindException: Error creating bean with name 'dataSource': Could not bind properties to 'PoolDataSourceImpl' : prefix=spring.datasource.oracleucp, ignoreInvalidFields=false, ignoreUnknownFields=true
        at org.springframework.boot.context.properties.ConfigurationPropertiesBindingPostProcessor.bind(ConfigurationPropertiesBindingPostProcessor.java:99)
        at org.springframework.boot.context.properties.ConfigurationPropertiesBindingPostProcessor.postProcessBeforeInitialization(ConfigurationPropertiesBindingPostProcessor.java:79)
        at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.applyBeanPostProcessorsBeforeInitialization(AbstractAutowireCapableBeanFactory.java:419)
        at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean(AbstractAutowireCapableBeanFactory.java:1762)
        at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:598)
        at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:520)
        at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:325)
        at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:234)
        at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:323)
        at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:199)
        at org.springframework.beans.factory.config.DependencyDescriptor.resolveCandidate(DependencyDescriptor.java:254)
        at org.springframework.beans.factory.support.DefaultListableBeanFactory.doResolveDependency(DefaultListableBeanFactory.java:1417)
        at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveDependency(DefaultListableBeanFactory.java:1337)
        at org.springframework.beans.factory.support.ConstructorResolver.resolveAutowiredArgument(ConstructorResolver.java:910)
        at org.springframework.beans.factory.support.ConstructorResolver.createArgumentArray(ConstructorResolver.java:788)
        ... 61 more
    Caused by: org.springframework.boot.context.properties.bind.BindException: Failed to bind properties under 'spring.datasource.oracleucp.s-s-l-context.provider' to java.security.Provider
        at org.springframework.boot.context.properties.bind.Binder.handleBindError(Binder.java:392)
        at org.springframework.boot.context.properties.bind.Binder.bind(Binder.java:352)
        at org.springframework.boot.context.properties.bind.Binder.lambda$bindDataObject$4(Binder.java:478)
        at org.springframework.boot.context.properties.bind.ValueObjectBinder$ConstructorParameter.bind(ValueObjectBinder.java:318)
        at org.springframework.boot.context.properties.bind.ValueObjectBinder.bind(ValueObjectBinder.java:76)
        at org.springframework.boot.context.properties.bind.Binder.lambda$bindDataObject$5(Binder.java:482)
        at org.springframework.boot.context.properties.bind.Binder$Context.withIncreasedDepth(Binder.java:596)
        at org.springframework.boot.context.properties.bind.Binder$Context.withDataObject(Binder.java:582)
        at org.springframework.boot.context.properties.bind.Binder.bindDataObject(Binder.java:480)
        at org.springframework.boot.context.properties.bind.Binder.bindObject(Binder.java:419)
        at org.springframework.boot.context.properties.bind.Binder.bind(Binder.java:348)
        at org.springframework.boot.context.properties.bind.Binder.lambda$bindDataObject$4(Binder.java:478)
        at org.springframework.boot.context.properties.bind.JavaBeanBinder.bind(JavaBeanBinder.java:99)
        at org.springframework.boot.context.properties.bind.JavaBeanBinder.bind(JavaBeanBinder.java:87)
        at org.springframework.boot.context.properties.bind.JavaBeanBinder.bind(JavaBeanBinder.java:63)
        at org.springframework.boot.context.properties.bind.Binder.lambda$bindDataObject$5(Binder.java:482)
        at org.springframework.boot.context.properties.bind.Binder$Context.withIncreasedDepth(Binder.java:596)
        at org.springframework.boot.context.properties.bind.Binder$Context.withDataObject(Binder.java:582)
        at org.springframework.boot.context.properties.bind.Binder.bindDataObject(Binder.java:480)
        at org.springframework.boot.context.properties.bind.Binder.bindObject(Binder.java:419)
        at org.springframework.boot.context.properties.bind.Binder.bind(Binder.java:348)
        at org.springframework.boot.context.properties.bind.Binder.bind(Binder.java:337)
        at org.springframework.boot.context.properties.bind.Binder.bind(Binder.java:267)
        at org.springframework.boot.context.properties.bind.Binder.bind(Binder.java:254)
        at org.springframework.boot.context.properties.ConfigurationPropertiesBinder.bind(ConfigurationPropertiesBinder.java:94)
        at org.springframework.boot.context.properties.ConfigurationPropertiesBindingPostProcessor.bind(ConfigurationPropertiesBindingPostProcessor.java:96)
        ... 75 more
    Caused by: java.lang.IllegalArgumentException: Could not instantiate Map type: java.security.Provider
        at org.springframework.core.CollectionFactory.createMap(CollectionFactory.java:327)
        at org.springframework.core.CollectionFactory.createMap(CollectionFactory.java:273)
        at org.springframework.boot.context.properties.bind.MapBinder.bindAggregate(MapBinder.java:57)
        at org.springframework.boot.context.properties.bind.AggregateBinder.bind(AggregateBinder.java:56)
        at org.springframework.boot.context.properties.bind.Binder.lambda$bindAggregate$3(Binder.java:443)
        at org.springframework.boot.context.properties.bind.Binder$Context.withIncreasedDepth(Binder.java:596)
        at org.springframework.boot.context.properties.bind.Binder.bindAggregate(Binder.java:443)
        at org.springframework.boot.context.properties.bind.Binder.bindObject(Binder.java:404)
        at org.springframework.boot.context.properties.bind.Binder.bind(Binder.java:348)
        ... 99 more
    Caused by: java.lang.NoSuchMethodException: java.security.Provider.<init>()
        at java.base/java.lang.Class.getConstructor0(Class.java:3761)
        at java.base/java.lang.Class.getDeclaredConstructor(Class.java:2930)
        at org.springframework.util.ReflectionUtils.accessibleConstructor(ReflectionUtils.java:185)
        at org.springframework.core.CollectionFactory.createMap(CollectionFactory.java:324)
        ... 107 more

Comment From: wilkinsona

Thanks for the report.

The problem's occurring because JndiPropertySource is not enumerable. It can be reproduced by adding this test to OracleUcpDataSourceConfigurationTests:

@Test
void testDataSourceExistsWithNonEnumerablePropertySource() {
    this.contextRunner.withInitializer((context) -> {
        context.getEnvironment().getPropertySources().addFirst(new PropertySource<String>("name") {

            @Override
            public Object getProperty(String name) {
                return null;
            }

        });
    }).run((context) -> {
        assertThat(context.getBeansOfType(DataSource.class)).hasSize(1);
        assertThat(context.getBeansOfType(PoolDataSourceImpl.class)).hasSize(1);
        try (Connection connection = context.getBean(DataSource.class).getConnection()) {
            assertThat(connection.isValid(1000)).isTrue();
        }
    });
}

As far as I can tell, we've already addressed this in 3.2.0 through the changes made for https://github.com/spring-projects/spring-boot/issues/38201, although it's something of an unintended side-effect. We may want to back port that change to 3.1.x or consider a different fix.

@fmeheust can you please try 3.2.0 (released earlier today) and let us know if it fixes the problem?

Comment From: fmeheust

@wilkinsona I can't find 3.2.0 in maven central, where can I get it ?

Comment From: wilkinsona

It's in Maven Central. See https://repo.maven.apache.org/maven2/org/springframework/boot/spring-boot/3.2.0/, for example. Note that sites like https://search.maven.org/ take some time to catch up to new releases and as such aren't a great source of up-to-date information.

Comment From: fmeheust

@wilkinsona I confirm that my application starts correctly on spring boot 3.2.0. Thank you

Comment From: wilkinsona

Thanks, @fmeheust.

Comment From: philwebb

We discussed this today and feel that backporting #38201 is a bit risky. We're going to recommend that folks with this issue upgrade to 3.2.x.