Without a pool, every call to DriverManager.getConnection() creates a brand new connection:
| Step | Typical Cost |
| TCP connect | 1–5 ms (local) / 50–200 ms (remote) |
| TLS handshake | 10–50 ms |
| DB authentication | 5–20 ms |
| Total per query (no pool) | 60–270 ms overhead |
| Total per query (with pool) | <1 ms (borrow from pool) |
A pool also caps the number of concurrent connections to the database, preventing connection exhaustion.
<dependency>
<groupId>com.zaxxer</groupId>
<artifactId>HikariCP</artifactId>
<version>5.1.0</version>
</dependency>
<!-- Plus your JDBC driver -->
<dependency>
<groupId>org.postgresql</groupId>
<artifactId>postgresql</artifactId>
<version>42.7.3</version>
</dependency>
Create a HikariConfig, configure it, then build a HikariDataSource. The DataSource is the pool — keep it as a singleton in your application:
import com.zaxxer.hikari.HikariConfig;
import com.zaxxer.hikari.HikariDataSource;
public class DataSourceFactory {
private static final HikariDataSource dataSource;
static {
HikariConfig config = new HikariConfig();
// Connection settings
config.setJdbcUrl("jdbc:postgresql://localhost:5432/mydb");
config.setUsername("myuser");
config.setPassword("secret");
config.setDriverClassName("org.postgresql.Driver"); // optional — auto-detected
// Pool sizing
config.setMaximumPoolSize(10); // max connections in pool
config.setMinimumIdle(2); // keep at least 2 idle connections
// Timeout settings
config.setConnectionTimeout(30_000); // ms to wait for a connection
config.setIdleTimeout(600_000); // ms idle before connection is evicted
config.setMaxLifetime(1_800_000); // ms max connection lifetime
// Pool name (appears in logs)
config.setPoolName("MyAppPool");
dataSource = new HikariDataSource(config);
}
public static HikariDataSource get() { return dataSource; }
}
Use it exactly like any other DataSource:
try (Connection conn = DataSourceFactory.get().getConnection();
PreparedStatement ps = conn.prepareStatement("SELECT count(*) FROM users");
ResultSet rs = ps.executeQuery()) {
rs.next();
System.out.println("Users: " + rs.getLong(1));
}
| Property | Default | Purpose |
maximumPoolSize | 10 | Hard ceiling on total connections |
minimumIdle | = maxPool | Connections kept warm when idle |
connectionTimeout | 30 000 ms | Max wait for a free connection; throws SQLTimeoutException |
idleTimeout | 600 000 ms | Time an idle connection can sit in the pool before eviction |
maxLifetime | 1 800 000 ms | Absolute max connection age — prevents stale TCP connections |
keepaliveTime | 0 (disabled) | Period to ping idle connections to keep them alive through firewalls |
connectionTestQuery | none | SQL to validate connections — use only if driver lacks JDBC 4 isValid() |
leakDetectionThreshold | 0 (disabled) | Log a warning if a connection is held longer than this (ms) |
schema | driver default | Default schema for new connections |
autoCommit | true | Initial auto-commit for borrowed connections |
Bigger is not better. Every extra connection uses server memory and adds contention. The HikariCP author recommends:
# Pool size formula (PostgreSQL team research)
pool_size = (core_count * 2) + effective_spindle_count
# For a 4-core server with SSDs (spindles = 1):
pool_size = (4 * 2) + 1 = 9 → round up to 10
For most web applications, 10–20 connections is more than enough. A pool of 100 connections usually performs worse than a pool of 10 due to lock contention inside the database.
If code borrows a connection but never returns it (missing try-with-resources), the pool eventually starves. Enable leak detection to log a warning with a stack trace pointing to the offending call site:
config.setLeakDetectionThreshold(5_000); // warn if connection held > 5 s
Sample log output:
WARN HikariPool-1 - Connection leak detection triggered for
com.example.OrderService.createOrder(OrderService.java:47),
stack trace follows...
Set leakDetectionThreshold to a value longer than any legitimate long-running query you have. Setting it too low creates false positives for batch jobs.
Instead of code, you can configure HikariCP from a properties file on the classpath:
# src/main/resources/hikari.properties
dataSourceClassName=org.postgresql.ds.PGSimpleDataSource
dataSource.serverName=localhost
dataSource.portNumber=5432
dataSource.databaseName=mydb
dataSource.user=myuser
dataSource.password=secret
maximumPoolSize=10
minimumIdle=2
connectionTimeout=30000
idleTimeout=600000
maxLifetime=1800000
poolName=MyAppPool
HikariConfig config = new HikariConfig("/hikari.properties");
HikariDataSource ds = new HikariDataSource(config);
Close the pool when the application exits to release all connections cleanly:
Runtime.getRuntime().addShutdownHook(new Thread(() -> {
if (!DataSourceFactory.get().isClosed()) {
DataSourceFactory.get().close();
System.out.println("Connection pool closed");
}
}));