☕ Java

DriverManager

java.sql.DriverManager is the original, classic entry point into JDBC: a static factory class that maintains a registry of available Driver implementations and hands back a live Connection when given a JDBC URL and credentials. It predates the javax.sql.DataSource abstraction and connection pooling, and while it remains fully functional and is still commonly used for simple applications, scripts, and tests, production server-side code generally prefers DataSource-based connection pooling for the reasons covered below. This entry covers how DriverManager locates and selects a driver, its core methods, login timeout and logging configuration, and where it fits (and doesn't fit) in modern application architecture.

Role and Core Methods

DriverManager is a final class consisting entirely of static methods — it is never instantiated — and its central responsibility is matching a JDBC URL against the pool of registered Driver implementations to find one that claims it can handle that URL, then delegating to that driver to actually establish the connection. The primary method, getConnection(), is overloaded to accept just a URL (for drivers that encode credentials in the URL itself), a URL plus username and password, or a URL plus a Properties object for drivers needing additional configuration beyond simple credentials. Internally, when getConnection() is called, DriverManager iterates through its registered drivers in registration order and calls acceptsURL() on each one, asking "do you recognize this URL's subprotocol?" The first driver that returns true is asked to actually open the connection via its own connect() method; if none of the registered drivers accept the URL, DriverManager throws a SQLException with the message "No suitable driver found," which is one of the most common early-stage JDBC errors and almost always means either the driver JAR is missing from the classpath or the URL's subprotocol is misspelled.
Java
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;

public class DriverManagerBasics {
    public static void main(String[] args) {
        String url = "jdbc:postgresql://localhost:5432/storedb";
        String user = "app_user";
        String password = "secret";

        // Three overloads of getConnection():
        try (Connection c1 = DriverManager.getConnection(url, user, password)) {
            System.out.println("Connected: " + c1.getCatalog());
        } catch (SQLException e) {
            // Thrown if no registered driver accepts the URL, or auth fails,
            // or the database is unreachable
            System.err.println("Connection failed: " + e.getMessage());
        }

        // URL with embedded credentials (driver-specific support varies)
        String urlWithCreds = "jdbc:postgresql://app_user:secret@localhost:5432/storedb";
        try (Connection c2 = DriverManager.getConnection(urlWithCreds)) {
            System.out.println("Connected via embedded credentials");
        } catch (SQLException e) {
            System.err.println("Connection failed: " + e.getMessage());
        }

        // Properties-based overload, useful for driver-specific extra settings
        java.util.Properties props = new java.util.Properties();
        props.setProperty("user", user);
        props.setProperty("password", password);
        props.setProperty("sslmode", "require");
        try (Connection c3 = DriverManager.getConnection(url, props)) {
            System.out.println("Connected with extra properties");
        } catch (SQLException e) {
            System.err.println("Connection failed: " + e.getMessage());
        }
    }
}

Inspecting and Managing Registered Drivers

Because driver registration via the Service Provider Interface happens automatically, it's sometimes useful to inspect what DriverManager actually has registered at runtime, particularly when debugging "No suitable driver" errors or diagnosing classpath conflicts where multiple versions of the same driver have been accidentally included. DriverManager.getDrivers() returns an Enumeration of every currently registered Driver instance, and getDriver(url) can be used to find specifically which driver would be selected for a given URL without actually opening a connection. Drivers can also be deregistered with deregisterDriver(), which is occasionally necessary in environments like web application servers where a web app is undeployed but its driver's static registration would otherwise leak and keep classes loaded (a classic cause of PermGen/Metaspace leaks in older servlet containers). DriverManager also exposes global configuration that applies across all drivers: setLoginTimeout(seconds) controls how long getConnection() will wait before giving up and throwing a SQLException, and setLogWriter()/getLogStream() (the latter deprecated) allow routing JDBC's internal logging to a custom PrintWriter, which is useful for debugging connection-level issues, though most modern applications get equivalent or better diagnostics from the driver's own logging configuration or from connection pool metrics instead.
Java
import java.sql.Driver;
import java.sql.DriverManager;
import java.util.Enumeration;

public class DriverManagerInspection {
    public static void main(String[] args) throws Exception {
        // List every driver currently registered via SPI auto-discovery
        Enumeration<Driver> drivers = DriverManager.getDrivers();
        while (drivers.hasMoreElements()) {
            Driver d = drivers.nextElement();
            System.out.println(d.getClass().getName()
                    + " v" + d.getMajorVersion() + "." + d.getMinorVersion());
        }

        // Find which driver would handle a specific URL, without connecting
        Driver chosen = DriverManager.getDriver("jdbc:postgresql://localhost:5432/storedb");
        System.out.println("Would use: " + chosen.getClass().getName());

        // Set a global timeout for all subsequent getConnection() calls
        DriverManager.setLoginTimeout(5);  // seconds

        // Deregistering a driver — relevant mainly in app-server hot-redeploy
        // scenarios to avoid classloader leaks when a webapp is undeployed:
        Enumeration<Driver> toClean = DriverManager.getDrivers();
        while (toClean.hasMoreElements()) {
            Driver d = toClean.nextElement();
            if (d.getClass().getClassLoader() == DriverManagerInspection.class.getClassLoader()) {
                DriverManager.deregisterDriver(d);
            }
        }
    }
}

DriverManager vs. DataSource — Where Each Belongs

DriverManager creates a brand-new physical database connection every single time getConnection() is called — there is no caching, reuse, or pooling built in at all. For a short-lived script, a command-line tool, or a unit test that opens one connection and closes it, this is perfectly fine and is in fact the simplest possible way to get a JDBC connection. For a server handling many concurrent requests, however, repeatedly paying the cost of a fresh TCP handshake and database authentication on every request would be prohibitively slow and would not scale, which is exactly the problem javax.sql.DataSource and connection pooling libraries (HikariCP, Apache Commons DBCP2, etc.) solve. In practice, most production frameworks (Spring Boot, Jakarta EE, Micronaut, Quarkus) configure a DataSource-backed connection pool for you based on application configuration, and application code is written against DataSource rather than calling DriverManager directly — though under the hood, the pool implementation itself typically still uses DriverManager (or talks to the driver directly) once per physical connection it creates and then reuses that connection many times before it is ever actually closed. Understanding DriverManager is foundational because it's what every driver fundamentally implements and what every pool is built on top of, but idiomatic modern application code reaches for DataSource and a pool rather than calling DriverManager.getConnection() directly in request-handling paths.
Java
// ── DriverManager: fine for scripts, CLI tools, and tests ──────────────────
import java.sql.Connection;
import java.sql.DriverManager;

public class QuickScript {
    public static void main(String[] args) throws Exception {
        try (Connection conn = DriverManager.getConnection(
                "jdbc:postgresql://localhost:5432/storedb", "app_user", "secret")) {
            // one-off connection, used once, then discarded — acceptable here
        }
    }
}

// ── DataSource + pool: the production pattern for request-handling code ───
import com.zaxxer.hikari.HikariConfig;
import com.zaxxer.hikari.HikariDataSource;
import javax.sql.DataSource;
import java.sql.Connection;

public class ProductionService {
    private final DataSource dataSource;

    public ProductionService() {
        HikariConfig config = new HikariConfig();
        config.setJdbcUrl("jdbc:postgresql://localhost:5432/storedb");
        config.setUsername("app_user");
        config.setPassword("secret");
        config.setMaximumPoolSize(20);
        this.dataSource = new HikariDataSource(config);  // built once
    }

    public void handleRequest() throws Exception {
        // Borrows an already-open connection from the pool instead of
        // paying for a new TCP + auth handshake on every request
        try (Connection conn = dataSource.getConnection()) {
            // ... do work ...
        } // returns the connection to the pool rather than closing the socket
    }
}

Related Topics in Java Database Connectivity (JDBC)

JDBC Architecture
JDBC (Java Database Connectivity) is the standard Java API for connecting to and executing operations against relational databases, defined as a set of interfaces in java.sql and javax.sql that database vendors implement with concrete driver classes. Its architecture is deliberately layered to decouple application code from any specific database product: applications code against interfaces like Connection, Statement, and ResultSet, while a pluggable driver underneath translates those calls into the vendor-specific network protocol the database actually understands. This entry covers the two-layer (API/driver) architecture, the four historical driver types, the role of the JDBC driver manager and connection pooling layers, and how the pieces fit together at runtime.
JDBC Drivers
A JDBC driver is the vendor-specific library that implements the java.sql/javax.sql interfaces for a particular database product, translating standard JDBC calls into that database's native network protocol. Choosing, loading, registering, and configuring the correct driver is a prerequisite for any JDBC code to run, and the mechanics of how a driver gets discovered and instantiated have evolved significantly since JDBC's introduction. This entry covers driver registration via the Service Provider Interface (SPI) versus the older explicit Class.forName() approach, JDBC URL syntax and structure, driver-specific configuration properties, and how to choose and manage driver dependencies in a real project.
Connection
java.sql.Connection represents a single session with a specific database — the live link through which all statements, transactions, and metadata queries flow, and the factory from which Statement, PreparedStatement, and CallableStatement objects are created. A Connection is a relatively expensive, stateful resource that wraps an underlying network socket and server-side session, which is why its lifecycle, transaction settings, and proper closing matter so much in practice. This entry covers obtaining and closing connections, transaction control (auto-commit, commit, rollback, savepoints, isolation levels), creating statements from a connection, and connection metadata and health checks.
Statement
java.sql.Statement is the base JDBC interface for sending SQL to a database and retrieving results, representing the simplest way to execute a static SQL string with no parameters. It is created from a Connection and supports three execution styles depending on whether the SQL returns rows, modifies data, or could be either, plus batch execution for running many statements efficiently in one round trip. This entry covers the executeQuery/executeUpdate/execute distinction, batch processing, and why Statement's lack of parameterization makes it unsuitable for most application code that handles user input, motivating PreparedStatement instead.