To reproduce (this was on Java 17.0.10, Tomcat 10.1.19, Spring Boot 3.2.4):

  1. Define any spring.main property (e.g. spring.main.banner_mode=off).
  2. Deploy your application as a WAR file in Tomcat.
  3. Start Tomcat, then exit with ctrl-c.

  4. On startup, from SpringApplication.bindToSpringApplication, the JavaBeanBinder for "spring.main" finds the property defined in hasKnownBindableProperties and does not skip binding.

  5. When it gets to the "spring.main.spring-boot-exception-handler" property, it has found the get method SpringApplication.getSpringBootExceptionHandler but not yet invoked it.
  6. Because we're running in Tomcat, we have the JndiPropertySource, so the test containsNoDescendantOf in Binder.bindObject returns false, causing the property to be bound.
  7. Finally, there was a change in DefaultBindConstructorProvider somewhere between 2.7.18 and 3.0.13 so it now not only calls bindable.getValue() but also bindable.getValue().get(). This invokes getSpringBootExceptionHandler (on the main thread) so the ThreadLocal is always created.

Tomcat's WebappClassLoaderBase.checkThreadLocalsForLeaks will log this as a severe problem when you stop the web application.

I can't really see a good way around this.

Comment From: wilkinsona

Thanks for the report, @stefanodelfalk. Are you making use of JNDI in your application? For example, are you configuring properties in JNDI and then consuming them through Spring's Environment? If not, I believe you could work around this problem by adding a spring.properties file to your app that contains spring.jndi.ignore=true. This will disable JndiPropertySource and should hopefully prevent the unwanted creation of the thread local.

Comment From: wilkinsona

This is another problem that could be solved by https://github.com/spring-projects/spring-boot/issues/34616.

Alternatively, a more specific fix would be to stop binding directly to SpringApplication and use a SpringApplicationProperties class instead and then map those onto the SpringApplication instance. This has the potential to be a breaking change if someone's using a custom SpringApplication sub-class and they're using custom spring.main.* properties to configure it. That feels to me to be quite an unlikely combination. If someone was affected, overriding bindToSpringApplication would allow them to fix the problem.

Comment From: philwebb

We've also just opened #40592. I'm going to close this one as a duplicate and either #40592 or #34616 will ultimately provide a fix.

Comment From: stefanodelfalk

Thanks a lot for looking into this, looking forward to #40592.

FYI: in the application I'm working on customers have the option define data sources in JNDI. The javadoc on JndiLocatorDelegate.IGNORE_JNDI_PROPERTY_NAME seems to indicate that this should work even with ignore=true, so that may not be a problem.

However, a bigger issue is that since the beginning of time we've asked them to set an application-critical property in Tomcat's web application , and it looks like that is ignored when I set spring.jndi.ignore=true, so the workaround wouldn't work for our particular case. (We've listed the log message as a known issue for now.)

Thanks again.