Connection Pooling & Resources
Connection Pooling & Resources
Every Connection object you create with DriverManager.getConnection() opens a physical TCP socket to the database server, authenticates, and allocates memory on both ends. That round-trip costs tens to hundreds of milliseconds. In a web application that receives hundreds of concurrent requests, creating a fresh connection for every query is the single fastest way to bring a database server to its knees.
Connection pooling solves this by keeping a cache of already-open connections that are handed out to callers and returned when the caller is done — without ever closing the underlying socket. The result is dramatically lower latency and far fewer resources consumed on the database server.
Why raw DriverManager does not scale
Consider the lifecycle of a connection opened with DriverManager:
- Your code calls
DriverManager.getConnection(url, user, pw). - The JDBC driver opens a TCP socket to the database.
- The database authenticates the user (~10–100 ms).
- Your code runs the query.
- Your code calls
connection.close(), tearing down the socket.
Steps 1–3 and 5 are pure overhead that dwarfs the actual query time for short queries. Under load, the database also runs out of available connection slots (PostgreSQL defaults to 100; many shared hosts allow far fewer).
HikariCP — the de-facto standard pool
HikariCP is the fastest, most widely used JDBC connection pool for the JVM. Spring Boot uses it by default. Adding it to a Maven project is one dependency:
Creating and configuring a pool at application startup:
Using the pool — same API, zero friction
From the caller's perspective, nothing changes. You call dataSource.getConnection() instead of DriverManager.getConnection(), and you call connection.close() when done — but close() now returns the connection to the pool instead of destroying it.
try-with-resources — closing everything correctly
JDBC uses three AutoCloseable resources: Connection, Statement / PreparedStatement, and ResultSet. Failing to close any one of them leaks the underlying OS handle and, for Connection, starves the pool.
The Java 7+ try-with-resources statement guarantees close() is called on every declared resource in reverse declaration order, even if an exception is thrown:
ResultSet alongside Connection in one try header when you need to call setters on the statement first. Split into an outer try for Connection and PreparedStatement, and an inner try for ResultSet.
What happens when the pool is exhausted
If all connections are checked out and a new request arrives, dataSource.getConnection() blocks for up to connectionTimeout milliseconds, then throws a SQLException. This is intentional: it creates back-pressure that prevents your application from silently queuing thousands of requests and crashing the database.
Lifecycle: one pool per application, shared across threads
HikariDataSource is thread-safe. Create it once — in a static field, a singleton bean, or a DI container — and share it everywhere. Creating a new HikariDataSource per request negates all pooling benefits.
DataSource bean (or rely on auto-configuration with spring.datasource.* properties), Spring creates and manages the HikariCP pool for you. You just inject DataSource and call getConnection(). The pool is closed automatically on application shutdown.
Summary
Connection pools reuse physical database connections, cutting connection overhead from milliseconds per query down to microseconds. HikariCP is the standard choice: configure it once at startup, tune maximumPoolSize conservatively, and always use try-with-resources so connections and statements are returned to the pool immediately after use. A leaked connection that is never returned will exhaust the pool under load, causing all subsequent requests to fail — try-with-resources makes that class of bug impossible.