skip to content

In JDBC, why do applications obtain connections from a DataSource rather than DriverManager?

level: middleimportance: must knowfreq 60%

answer

  1. One is a static method, one is an interface
  2. Who decides what a connection is
  3. Ask what close() means in each case
  4. Two SPI variants exist for pools and XA

basics

~20 s

DriverManager is a static factory that opens a brand-new physical connection per call from a URL hardcoded at the call site. DataSource is an interface whose implementation is configured elsewhere, which is what allows pooled, XA-capable or instrumented connections without changing application code.

solid answer

~50 s

`DriverManager.getConnection(url, user, password)` is the original JDBC 1 entry point: a static method that scans the registered drivers for one that accepts the URL and opens a fresh physical connection every time it is called. That couples every call site to a URL and credentials, and it makes connection acquisition as expensive as a TCP handshake plus authentication. `javax.sql.DataSource` inverts that: it is an interface with a no-argument `getConnection()`, and the implementation — supplied by a pool library, an application server, or the driver itself — decides what a connection is. That indirection is what makes pooling possible at all, and the SPI variants `ConnectionPoolDataSource` and `XADataSource` extend it to pool-managed and distributed-transaction connections. Application code holds the interface, is injected or looks it up, and never mentions a JDBC URL. With a pooled implementation the object handed back is a logical handle whose `close()` returns the physical connection rather than dropping it.

code

java · 11 lines
java
// DriverManager: a new physical connection on every call
try (Connection c1 = DriverManager.getConnection(
        "jdbc:postgresql://db:5432/app", "app", "secret")) {
    // ... close() here ends the database session
}

// DataSource: the implementation decides what getConnection() means
DataSource ds = injectedOrLookedUp();
try (Connection c2 = ds.getConnection()) {
    // ... close() here typically returns it to a pool
}

go deeper

for a junior

Know that production code obtains connections from an injected DataSource rather than calling DriverManager with a URL, and that you still close what you borrow.

for a middle

Explain that DataSource is an interface whose implementation decides what a connection is — that is what makes pooling, proxying and XA possible — and describe logical versus physical connections and what close() does in each case.

for a senior

Be ready to discuss connection state leaking between borrowers, why a handle must never be cached or shared across threads, and when unwrap is a legitimate escape hatch versus a portability leak.

for a principal

Own where the DataSource is defined and how credentials reach it, how many logical data sources a service should have for read/write or tenant separation, and what the migration cost is when the driver or database moves.

## Two ways to get a Connection JDBC has exactly two standard ways to obtain a `java.sql.Connection`. The older one is the static `DriverManager`. You pass a JDBC URL and credentials; `DriverManager` walks its list of registered `java.sql.Driver` implementations, asks each whether it accepts that URL, and returns whatever the first accepting driver produces. Since JDBC 4.0 the registration happens automatically — drivers ship a `META-INF/services/java.sql.Driver` entry and are discovered through `ServiceLoader` — which is why the old `Class.forName("...")` incantation is no longer needed. The newer one is `javax.sql.DataSource`, introduced with JDBC 2.0's optional package and part of Java SE ever since. It is an interface with `getConnection()` and `getConnection(user, password)`. Crucially, it says nothing about how the connection is produced. ## What the indirection buys The first gain is configuration. With `DriverManager`, the URL, username and password appear at the call site, so changing a host means touching code or threading configuration through to every data-access class. With a `DataSource`, all of that lives where the implementation is constructed — a Spring bean, a container resource, a JNDI entry — and the code that runs queries holds only the interface. The second gain, and the reason the interface exists at all, is that an implementation may do something other than open a socket. Every connection pool in the JVM ecosystem is a `DataSource` implementation; so are proxying and instrumenting wrappers that time queries or log SQL, and test doubles that hand out connections to an embedded database. Because the interface is standard, none of that requires the application to know. The third gain is the specialised SPI variants. `javax.sql.ConnectionPoolDataSource` produces `PooledConnection` objects that a pool implementation manages and that fire connection-event callbacks when the logical handle is closed or an error occurs. `javax.sql.XADataSource` produces `XAConnection` objects exposing an `XAResource`, which is how a JDBC connection participates in a two-phase-commit transaction spanning several resource managers. Neither has any equivalent through `DriverManager`. A smaller but real detail: `CommonDataSource`, the parent of all three, defines `setLoginTimeout(int)` and `getParentLogger()`, so acquisition timeouts and logging integration are part of the standard surface. ## Physical versus logical connections The most important behavioural difference for everyday code is what `close()` means. A `Connection` from `DriverManager` is physical: closing it ends the database session. A `Connection` from a pooled `DataSource` is a logical handle wrapping a physical connection that the pool owns. Closing the handle marks it unusable and returns the underlying connection to the pool, typically after the pool resets per-connection state such as auto-commit and any open transaction. This flips the intuition new developers bring from `DriverManager` code, where closing feels wasteful because reopening is slow. With a pool, close early and always: holding a handle is what starves other callers, and it is why leak detection exists in every serious pool implementation. It also means you must not cache a `Connection` in a field or a static — the object you are caching is a handle that the pool believes is in flight. ## Getting the underlying connection Because pooled and proxying implementations wrap things, JDBC defines `java.sql.Wrapper`, which `Connection`, `Statement` and `DataSource` all implement. `isWrapperFor(Class)` and `unwrap(Class)` let you reach a driver-specific type — for example, to call a vendor extension that the standard interface does not expose. Reaching for `unwrap` should be rare and deliberate; code that does it routinely has given up portability without meaning to. ## When DriverManager is still fine It is not deprecated and it is not always wrong. A command-line tool, a migration script, a one-shot admin utility, or a test that opens a single connection and exits has nothing to gain from a pool and nothing to configure elsewhere. The rule of thumb is about lifetime and concurrency: any long-running process that serves concurrent requests wants a `DataSource`; a short program doing one job can call `DriverManager` and close the connection when it finishes.

  • Is Class.forName still required to load a JDBC driver?
    No. Since JDBC 4.0, drivers declare themselves in META-INF/services/java.sql.Driver and DriverManager discovers them through ServiceLoader on first use. The Class.forName call is harmless legacy and appears in old tutorials; the only cases needing it are exotic classloader setups where automatic discovery cannot see the driver jar.
  • What are ConnectionPoolDataSource and XADataSource for?
    They are SPI variants of DataSource meant for infrastructure rather than application code. ConnectionPoolDataSource produces PooledConnection objects that a pool manages and that emit events when a logical handle closes. XADataSource produces XAConnection objects exposing an XAResource, so the connection can join a two-phase-commit transaction alongside other resource managers.
  • Why is caching a Connection in a field a bug when the source is a pooled DataSource?
    The object is a logical handle the pool considers borrowed for as long as you hold it, so it never returns to circulation. It also carries state — auto-commit, transaction, session settings — that the pool would normally reset on return, and it is not safe to share across threads. Borrow per unit of work and close.

saying these in an interview costs you the question

  • Thinks DataSource itself implements pooling
  • Says DriverManager returns a pooled connection
  • Believes Class.forName is still required for drivers
  • Caches one Connection in a static field for reuse
  • Cannot say what close() means on a pooled handle

context