skip to content

How do you expose a container-managed JDBC DataSource to a Tomcat web application using a <Resource> element, how does application code look it up, and where must the JDBC driver jar be placed?

level: middleimportance: should knowfreq 50%

answer

  1. container builds the pool, not your code
  2. private naming tree per application
  3. one prefix before every lookup name
  4. the driver must be visible to Tomcat itself

basics

~20 s

Declare a <Resource> of type javax.sql.DataSource in the application's Context descriptor; Tomcat builds a pool and binds it in that application's private JNDI tree. Code looks it up at java:comp/env/<name>. The driver jar must sit in $CATALINA_BASE/lib, not in WEB-INF/lib.

solid answer

~40 s

You add a `<Resource name="jdbc/orders" auth="Container" type="javax.sql.DataSource" driverClassName=... url=... username=... password=.../>` to the application's `<Context>` — normally `conf/<engine>/<host>/<appname>.xml` so the credentials are not baked into the artifact. Tomcat instantiates a connection pool from those attributes and binds it into that webapp's private naming context, so the code does `new InitialContext().lookup("java:comp/env/jdbc/orders")` and casts to `javax.sql.DataSource`. The `java:comp/env` prefix matters — the entry is scoped to one application, not global. If several applications must share one pool, declare it once under `<GlobalNamingResources>` in `server.xml` and add a `<ResourceLink>` in each context. The driver has to be in `$CATALINA_BASE/lib` because Tomcat's own code — loaded by the common class loader, which never looks inside a WAR — is what creates the pool.

code

xml · 12 lines
xml
<Context>
  <Resource name="jdbc/orders"
            auth="Container"
            type="javax.sql.DataSource"
            driverClassName="org.postgresql.Driver"
            url="jdbc:postgresql://db.internal:5432/orders"
            username="orders_app"
            password="s3cret"
            maxTotal="50"
            maxIdle="10"
            maxWaitMillis="5000"/>
</Context>

go deeper

for a junior

Be able to say that Tomcat can own the connection pool, that it is declared as a <Resource> and looked up by name through JNDI rather than constructed in code.

for a middle

Write the declaration and the lookup correctly, explain what java:comp/env scopes, and know that the driver must be in Tomcat's shared lib directory because the container creates the pool.

for a senior

Reason about the operational side: per-application versus global pool and the connection budget against the database, where credentials live so the artifact stays environment-independent, and how driver placement interacts with redeploy leaks.

for a principal

Decide whether datasource ownership belongs to the container at all — container-managed JNDI versus application-owned pools versus an external proxy — and what that choice implies for credential rotation and connection limits across the estate.

## What "container-managed" means here With a container-managed datasource, the *container* owns the connection pool: it reads the configuration, instantiates the pool at context start, and hands the application a `javax.sql.DataSource` through JNDI. The application never names a driver class or a URL in its own code. The upside is that connection details and credentials live in server configuration rather than in the artifact, and the pool can be shared or replaced without rebuilding the application. ## Declaring it ```xml <Context> <Resource name="jdbc/orders" auth="Container" type="javax.sql.DataSource" driverClassName="org.postgresql.Driver" url="jdbc:postgresql://db.internal:5432/orders" username="orders_app" password="..." maxTotal="50" maxIdle="10" maxWaitMillis="5000"/> </Context> ``` Put this in `conf/<engine>/<host>/<appname>.xml` when the values are environment-specific — that file is not part of the WAR, survives redeployment and keeps the password out of the build artifact. Putting it in the application's `META-INF/context.xml` works too, but then every environment needs its own build. Tomcat's default pool implementation is Commons DBCP 2, repackaged inside Tomcat, which is why the sizing attributes are spelled `maxTotal`, `maxIdle` and `maxWaitMillis`. Tomcat also ships its own alternative pool, selected with `factory="org.apache.tomcat.jdbc.pool.DataSourceFactory"`, and you can point `factory` at any other pool's object factory. Attribute names follow whichever pool you chose — a frequent source of "my setting is ignored", because an unknown attribute is simply not applied. ## Looking it up ```java Context initial = new InitialContext(); DataSource ds = (DataSource) initial.lookup("java:comp/env/jdbc/orders"); ``` The `java:comp/env` prefix is the application component's environment naming context — a private tree per web application, which is why two applications can each have a `jdbc/orders` meaning different databases. A common variant narrows to the subcontext first (`lookup("java:comp/env")` then `lookup("jdbc/orders")`); looking up the bare name `jdbc/orders` against a fresh `InitialContext` fails. In modern Tomcat a matching `<resource-ref>` in `web.xml` is optional, though older documentation and other containers still show it; the annotation form is `@Resource(name = "jdbc/orders")` on a field, which lets the container inject the datasource directly. ## Sharing one pool between applications Declare it once in `server.xml`: ```xml <GlobalNamingResources> <Resource name="jdbc/orders" auth="Container" type="javax.sql.DataSource" .../> </GlobalNamingResources> ``` and link it into each application's context: ```xml <Context> <ResourceLink name="jdbc/orders" global="jdbc/orders" type="javax.sql.DataSource"/> </Context> ``` One pool, one set of connections against the database — which is the point when several applications share a database and you care about the total connection count. The trade is coupling: a change to the global resource affects everyone, and one application exhausting the pool starves the rest. ## Where the driver jar goes, and why This is the question that actually fails in practice. The pool is constructed by Tomcat's own code, running under the **common class loader**, which sees `$CATALINA_BASE/lib` and `$CATALINA_HOME/lib` but never looks inside `WEB-INF/lib`. Put the driver in the WAR and context start-up fails with a message about being unable to create a JDBC driver of the given class for the connect URL, wrapping a `ClassNotFoundException`. Copy the driver to `$CATALINA_BASE/lib` and it works. The corollary is that a driver in the shared directory is loaded once for the whole JVM, which is also what keeps it from being a redeploy leak — an application-loaded driver registers itself with the JVM-wide `DriverManager` and pins its class loader after undeployment. ## Other resource types The same `<Resource>` mechanism binds mail sessions, custom object factories and simple constants (via `<Environment>` entries for `java:comp/env` values such as feature flags). The pattern is identical: the container creates the object, the application looks it up by name and never learns where it came from. ## Note on Jakarta From Tomcat 10 onwards the Servlet API packages are `jakarta.servlet.*`, but the datasource `type` remains `javax.sql.DataSource` — JDBC is a Java SE API and did not move. Writing `jakarta.sql.DataSource` in a `<Resource>` is a real and frequent mistake after a Tomcat 9 to 10 upgrade.

  • When would you prefer a <ResourceLink> to a global resource over a per-application <Resource>?
    When several applications on the same instance talk to the same database and you need to bound the total connection count — one pool instead of one per application. It also centralises credential rotation. The cost is coupling: every linked application shares the pool's limits, so one application's slow queries can exhaust connections for all of them, and a configuration change affects everyone at once.
  • Context start-up fails with a message about being unable to create a JDBC driver for the connect URL. What is your first check?
    Where the driver jar lives. The pool is created by Tomcat's own code under the common class loader, which cannot see `WEB-INF/lib`, so the driver must be in `$CATALINA_BASE/lib`. Second check is the `type` attribute — `javax.sql.DataSource`, not the jakarta spelling — and third is whether the attribute names match the pool implementation you selected.
  • Why does a JDBC driver bundled inside a WAR contribute to redeploy leaks?
    Drivers register themselves with `java.sql.DriverManager`, which is loaded by the JVM's own loader and outlives every web application. A driver class loaded from `WEB-INF/lib` therefore leaves a JVM-level reference to the webapp's class loader after undeployment, pinning every class it loaded. Tomcat logs a warning about the unregistered driver at context stop; putting the driver in the shared directory avoids the situation entirely.

saying these in an interview costs you the question

  • Looking up the bare name without the java:comp/env prefix
  • Putting the JDBC driver in WEB-INF/lib
  • Assuming a per-app Resource is visible to other applications
  • Using jakarta.sql.DataSource as the resource type
  • Baking production credentials into the WAR's context.xml

context