# 10 — Mocking JNDI DataSources, verified against Spring Framework 7.0.9 and simple-jndi 0.25.0 [← Previous: 09 — Testing with in-memory databases](09-testing-in-memory-databases.md) | [Next: 11 — Proxies and lazy initialization →](11-proxies-and-lazy-initialization.md) Backs [ankurm.com: mocking JNDI datasources](https://ankurm.com/testing-hibernate-7-mocking-jndi-datasources-without-the-container/). Companion code: [`src/test/java/com/ankurm/hibernatedemo/jndi/`](../src/test/java/com/ankurm/hibernatedemo/jndi/) (three test classes — [`JndiDataSourceResolutionTest`](../src/test/java/com/ankurm/hibernatedemo/jndi/JndiDataSourceResolutionTest.java), [`HibernateJndiDataSourceTest`](../src/test/java/com/ankurm/hibernatedemo/jndi/HibernateJndiDataSourceTest.java), [`CrossTestPollutionTest`](../src/test/java/com/ankurm/hibernatedemo/jndi/CrossTestPollutionTest.java) — seven tests, all green together in one run -- see [`docs/output/jndi-tests-run.txt`](output/jndi-tests-run.txt) (filtered) and [`docs/output/jndi-full-run.txt`](output/jndi-full-run.txt) (unfiltered Surefire capture of the same run). Environment: Hibernate ORM 7.4.5.Final, Spring Boot 4.1.1, Spring Framework 7.0.9, JDK 25, H2 2.4.240, simple-jndi 0.25.0 (`com.github.h-thurow:simple-jndi`, test scope). ## `SimpleNamingContextBuilder`: gone, and it's not a recent change The article this replaces doesn't use `SimpleNamingContextBuilder`, but it's the most commonly recommended "just use Spring's mock JNDI" answer elsewhere, so it's worth settling with evidence. `unzip -l` against the actual jars ([`docs/output/jndi-simplenamingcontextbuilder-removal.txt`](output/jndi-simplenamingcontextbuilder-removal.txt)): - `spring-test-5.3.31.jar` (last of the 5.x line): `org/springframework/mock/jndi/` present, 8 class files including `SimpleNamingContextBuilder.class`. - `spring-test-6.0.0.jar`: zero matches for `naming` or `jndi` anywhere in the jar. - `spring-test-7.0.9.jar` (what this whole blog batch verifies against): same, zero matches. So it went in Spring Framework 6.0.0 -- the same release that moved the whole framework from `javax.*` to `jakarta.*` for Jakarta EE 9. There is **no direct built-in replacement** in `spring-test` itself; the practical answer for the last several years has been a third-party library, which is exactly why this chapter exists. One nuance worth stating precisely: JNDI (`javax.naming.*`) is a **Java SE API** shipped in the `java.naming` module, not a Jakarta EE API, so it did not get renamed to `jakarta.naming` the way `javax.persistence` and `javax.servlet` did. simple-jndi's `MemoryContextFactory` still `implements javax.naming.spi.InitialContextFactory` in 2026, and always will unless the JDK itself changes it. ## Getting simple-jndi 0.25.0 actually working First correction to the article's own dependency block: the artifact coordinates it used, `simple-jndi:simple-jndi:0.11.4.1`, are an old, essentially abandoned groupId. The maintained fork used throughout this blog batch is `com.github.h-thurow:simple-jndi:0.25.0`. Second: the article's `jndi.properties` sets `java.naming.provider.url=org.osjava.sj.memory .MemoryContextFactory` -- that package does not exist in the 0.25.0 jar at all ([`docs/output/jndi-simplejndi-jar-listing.txt`](output/jndi-simplejndi-jar-listing.txt)). The real class is `org.osjava.sj.MemoryContextFactory`, and the property that should carry it is `java.naming.factory.initial`, not `java.naming.provider.url`. A working bind-then-lookup, from `JndiDataSourceResolutionTest`: ```java System.setProperty(Context.INITIAL_CONTEXT_FACTORY, "org.osjava.sj.MemoryContextFactory"); System.setProperty("org.osjava.sj.jndi.shared", "true"); // see below -- this one is easy to miss Context ctx = new InitialContext(); ctx.createSubcontext("java:"); ctx.createSubcontext("java:comp"); /* ...etc */ ctx.bind(JNDI_NAME, dataSource); DataSource looked = (DataSource) new InitialContext().lookup(JNDI_NAME); ``` `org.osjava.sj.jndi.shared=true` is the detail every abbreviated example skips, and skipping it produces a confusing failure: without it, `javap -c` on `MemoryContextFactory.class` ([`docs/output/proxy-settings-javap.txt`](output/proxy-settings-javap.txt)'s sibling investigation technique, applied here) shows the factory branches on that exact property name and, if it's not `"true"`, hands back a **brand new, empty** `MemoryContext` on every single `new InitialContext()` call instead of consulting its static, JVM-shared cache. A `bind()` through one `InitialContext` instance is then invisible to a `lookup()` through a different one -- even inside the same test method, if the code happens to construct more than one `InitialContext`. This was not a hypothetical: it's exactly the first failure this investigation hit. Also demonstrated, and also driving Hibernate itself (not just a raw JDBC lookup): Hibernate's `hibernate.connection.datasource` setting (constant `DATASOURCE` in `org.hibernate.cfg .JdbcSettings`, confirmed via `javap`, [`docs/output/jndi-hibernate-datasource-setting-javap.txt`](output/jndi-hibernate-datasource-setting-javap.txt)) resolves a JNDI name into a real, working `SessionFactory` -- `HibernateJndiDataSourceTest` builds one and runs `SELECT 1` through it. Verbatim log line proving the resolution actually went through JNDI, not a URL: ``` HHH10001005: Database info: DataSource JNDI name [jdbc/HibernateTestDS] Database JDBC URL [jdbc:h2:mem:hibernate-jndi-test] ... Pool: DataSourceConnectionProvider ``` ## The failure modes, verbatim `NoInitialContextException` when `java.naming.factory.initial` is never set: ``` Need to specify class name in environment or system property, or in an application resource file: java.naming.factory.initial ``` `NameNotFoundException` on an unbound name -- message is just the name itself: ``` java:comp/env/jdbc/DoesNotExist ``` `NameAlreadyBoundException` across two tests sharing a JVM -- reproduced on purpose in `CrossTestPollutionTest` (test A binds and never cleans up; test B tries to bind the same name): ``` Name jdbc/SharedAcrossTests already bound. Use rebind() to override ``` That message's own suggestion (`rebind()` instead of `bind()`) does make the immediate error go away, but it is a band-aid, not the fix -- it papers over test A's leak rather than closing it. `CrossTestPollutionTest`'s third test spells out the real fix: whatever a test binds, that same test unbinds in `@AfterEach`, unconditionally, so nothing survives to the next test class in the same JVM. This surfaced for real, by accident, in this exact investigation: once all three JNDI test classes ran together in one Surefire invocation instead of one at a time, `HibernateJndiDataSourceTest`'s `@BeforeEach` started throwing `NameAlreadyBoundException` on `ctx.createSubcontext("jdbc")` -- a *different* test class in the same run had already created that subcontext and never removed it. The fix applied ([`docs/output/jndi-tests-run.txt`](output/jndi-tests-run.txt) shows the resulting clean run) was to make subcontext creation idempotent (catch `NameAlreadyBoundException`, treat "already there" as success) in addition to unbinding leaf names in `@AfterEach`. Simple-JNDI's shared, static, JVM-wide namespace is not a toy problem confined to a contrived demo -- it is the normal behavior of the library, and it bit this test suite the first time the suite ran as a whole. ## Boot 4.1.1 specifics: `spring.datasource.jndi-name` is alive, relocated `javap` against the actual 4.1.1 jars ([`docs/output/jndi-boot-autoconfig-javap.txt`](output/jndi-boot-autoconfig-javap.txt)) confirms both pieces still exist: - `org.springframework.boot.jdbc.autoconfigure.JndiDataSourceAutoConfiguration` -- a real class, with a `dataSource(DataSourceProperties, ApplicationContext)` factory method. - `DataSourceProperties.jndiName` -- the field backing `spring.datasource.jndi-name`, with its getter/setter intact. The relocation matters for anyone grepping for it in the wrong place: this is in the **`spring-boot-jdbc`** module, not `spring-boot-autoconfigure` -- `spring-boot-autoconfigure-4.1.1.jar` has zero matches for `jndi` at all. This is the same Boot 4 autoconfigure-module split noted elsewhere in this batch (JPA landed in `spring-boot-jpa`; JDBC/datasource landed in `spring-boot-jdbc`). What did **not** get resolved in this sandbox: a full `@SpringBootTest` actually resolving `spring.datasource.jndi-name` end-to-end through Boot's own `JndiDataSourceAutoConfiguration`. Standalone simple-jndi bind/lookup worked perfectly (proven above, repeatedly, including through Spring's own `JndiTemplate` called directly). But inside a real `ApplicationContext` refresh, the `dataSource` bean's JNDI lookup consistently threw `NameNotFoundException` even though: the binding was moved to a static initializer (to run before `SpringExtension`'s `BeforeAllCallback`, which fires before a test class's own `@BeforeAll`); the class loading, `System.identityHashCode`, and classloader of `MemoryContextFactory` were confirmed identical between the successful standalone lookup and the failing in-context one via a diagnostic `BeanFactoryPostProcessor`; the relevant system properties (`java.naming.factory.initial`, `org.osjava.sj.jndi.shared`) were confirmed present and correct at the point of failure; and no `jndi.properties` resource or JNDI `InitialContextFactoryBuilder` registration was found anywhere on the 107-jar test classpath. The root cause was not found. `HibernateJndiDataSourceTest` (Hibernate's own `hibernate.connection.datasource`, no Spring autoconfiguration involved) is the test that carries the "Hibernate/Spring resolves it by name" claim for this chapter -- the Boot-autoconfiguration-specific path is documented as class-and-property-exist-but-live-wiring-unverified, not glossed over as working. ## Is mock JNDI still the right answer in 2026? Being honest about what this investigation actually found: mock JNDI is a legacy technique kept alive for the shrinking set of applications that still get deployed into a real Java EE/Jakarta EE application server (WildFly, Payara) where a container-managed `DataSource` is genuinely the only path to a connection. For anything running as a Spring Boot fat jar -- the overwhelming majority of new work -- there is no container JNDI tree to fake in the first place, so mocking one in tests is solving a problem the production topology doesn't have. The honest recommendation for a Boot application in 2026 is what the rest of this repo already does (see [chapter 09](09-testing-in-memory-databases.md)): an in-memory database or Testcontainers wired through `spring.datasource.url`, not JNDI. Reach for simple-jndi specifically when the application under test really is deployed via JNDI in production and the test needs to mirror that lookup path -- not as a generic "how do I mock a DataSource" answer. [← Previous: 09 — Testing with in-memory databases](09-testing-in-memory-databases.md) | [Next: 11 — Proxies and lazy initialization →](11-proxies-and-lazy-initialization.md)