Keywords: Spring Boot | Embedded Tomcat | JNDI Configuration
Abstract: This article provides an in-depth exploration of how to enable and configure JNDI context in Spring Boot's embedded Tomcat container to support JNDI lookups for resources such as data sources. Based on the best-practice answer, it analyzes default JNDI disabling issues, enabling methods, resource binding mechanisms, and Spring Bean configuration techniques. Through step-by-step code examples and principle explanations, it helps developers resolve common NameNotFoundException and classloader problems, ensuring reliable access to JNDI resources in embedded environments.
Introduction
In Spring Boot applications using embedded Tomcat containers, JNDI (Java Naming and Directory Interface) is disabled by default. This can lead to NoInitialContextException or NameNotFoundException when attempting to look up resources such as data sources via JNDI. Based on community best practices, this article delves into how to correctly enable JNDI and configure resources, ensuring seamless use of JNDI functionality in embedded environments.
Enabling JNDI Naming
By default, JNDI functionality is not activated in embedded Tomcat. To enable it, the Tomcat.enableNaming() method must be invoked. In Spring Boot, this can be achieved by customizing the TomcatEmbeddedServletContainerFactory. The following code example demonstrates how to override the getTomcatEmbeddedServletContainer method to enable naming:
@Bean
public TomcatEmbeddedServletContainerFactory tomcatFactory() {
return new TomcatEmbeddedServletContainerFactory() {
@Override
protected TomcatEmbeddedServletContainer getTomcatEmbeddedServletContainer(
Tomcat tomcat) {
tomcat.enableNaming();
return super.getTomcatEmbeddedServletContainer(tomcat);
}
};
}This step is fundamental, ensuring the initialization of Tomcat's JNDI system and providing a context environment for subsequent resource binding.
Configuring JNDI Resources
After enabling JNDI, resources such as data sources need to be bound to the JNDI context. This can be accomplished by overriding the postProcessContext method. The following example illustrates how to configure an Oracle data source:
@Bean
public EmbeddedServletContainerCustomizer embeddedServletContainerCustomizer() {
return new EmbeddedServletContainerCustomizer() {
@Override
public void customize(ConfigurableEmbeddedServletContainer container) {
if (container instanceof TomcatEmbeddedServletContainerFactory) {
TomcatEmbeddedServletContainerFactory tomcatEmbeddedServletContainerFactory = (TomcatEmbeddedServletContainerFactory) container;
tomcatEmbeddedServletContainerFactory.addContextCustomizers(new TomcatContextCustomizer() {
@Override
public void customize(Context context) {
ContextResource mydatasource = new ContextResource();
mydatasource.setName("jdbc/mydatasource");
mydatasource.setAuth("Container");
mydatasource.setType("javax.sql.DataSource");
mydatasource.setScope("Sharable");
mydatasource.setProperty("driverClassName", "oracle.jdbc.driver.OracleDriver");
mydatasource.setProperty("url", "jdbc:oracle:thin:@mydomain.com:1522:myid");
mydatasource.setProperty("username", "myusername");
mydatasource.setProperty("password", "mypassword");
context.getNamingResources().addResource(mydatasource);
}
});
}
}
};
}Key point: context.getNamingResources().addResource adds the resource to the java:comp/env context, so the resource name should be set to jdbc/mydatasource, not the full path java:comp/env/jdbc/mydatasource. This avoids common naming errors.
Resolving Classloader Issues
Tomcat uses the thread context classloader to determine the context for JNDI lookups. In embedded environments, resources are bound to the web application's JNDI context, so lookups must be performed when the web application's classloader is the thread context classloader. If Spring Beans attempt JNDI lookups immediately at application startup, failures may occur due to classloader mismatches. The solution is to configure JndiObjectFactoryBean for deferred lookup:
<bean class="org.springframework.jndi.JndiObjectFactoryBean">
<property name="jndiName" value="java:comp/env/jdbc/mydatasource"/>
<property name="expectedType" value="javax.sql.DataSource"/>
<property name="lookupOnStartup" value="false"/>
</bean>Setting lookupOnStartup to false creates a proxy that performs the JNDI lookup on first actual use, rather than during application startup. This ensures the lookup occurs in the correct classloader context, preventing NameNotFoundException (as shown in error messages like "Name [comp/env/jdbc/mydatasource] is not bound in this Context. Unable to find [comp]").
Advanced Configuration and Variants
Based on supplementary answers, in Spring Boot 2.x, class names and methods have changed (e.g., TomcatServletWebServerFactory replaces older versions). Additionally, the choice of data source factory impacts JNDI configuration:
- By default, Tomcat may attempt to use a DBCP 2 factory; if dependencies such as
org.apache.tomcat.dbcp.dbcp2.BasicDataSourceFactoryare missing from the classpath, aClassNotFoundExceptioncan be thrown. - Tomcat JDBC pool can be used by setting
resource.setProperty("factory", "org.apache.tomcat.jdbc.pool.DataSourceFactory"). - For HikariCP, add the dependency and set the factory to
com.zaxxer.hikari.HikariJNDIFactory.
These variants emphasize the importance of ensuring factory class availability and compatibility when configuring JNDI resources.
Practical Examples and Integration
A complete practical example can be found in GitHub repositories such as spring-boot-sample-tomcat-jndi. In Spring Beans, JNDI resources can be exposed as Spring-managed beans using JndiObjectFactoryBean:
@Bean(destroyMethod = "")
public DataSource jndiDataSource() throws IllegalArgumentException, NamingException {
JndiObjectFactoryBean bean = new JndiObjectFactoryBean();
bean.setJndiName("java:comp/env/jdbc/mydatasource");
bean.setProxyInterface(DataSource.class);
bean.setLookupOnStartup(false);
bean.afterPropertiesSet();
return (DataSource) bean.getObject();
}This allows the data source to be injected into other Spring components, for example via @Autowired private DataSource jndiDataSource;. Note that destroyMethod = "" prevents Spring from invoking default close methods during bean destruction, as JNDI resources are managed by Tomcat.
Conclusion
Configuring JNDI context in Spring Boot embedded Tomcat involves three key steps: enabling JNDI naming, correctly binding resources to the java:comp/env context, and handling classloader issues to avoid startup lookup failures. Through deferred lookup and proxy mechanisms, reliable access to JNDI resources at runtime can be ensured. Based on best practices, this article provides a guide from basic to advanced configuration, helping developers overcome common pitfalls and achieve efficient JNDI integration.