Connection Pooling: HikariCP Standalone Configuration

Creating a new database connection for every query is slow: a TCP handshake and TLS negotiation can take 50–200 ms. A connection pool keeps a set of ready connections that threads borrow and return. HikariCP is the fastest and most widely used JDBC pool for Java, and it is what Spring Boot uses by default. This article configures it from scratch, without Spring Boot auto-configuration, so you understand every important property.

Share

Why You Need a Connection Pool

Without a pool, every call to DriverManager.getConnection() creates a brand new connection:

StepTypical Cost
TCP connect1–5 ms (local) / 50–200 ms (remote)
TLS handshake10–50 ms
DB authentication5–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.

Maven Dependency

XML
<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>

Basic HikariCP Setup

Create a HikariConfig, configure it, then build a HikariDataSource. The DataSource is the pool: keep it as a singleton in your application:

Java
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:

Java
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));
}

Key Configuration Properties

PropertyDefaultPurpose
maximumPoolSize10Hard ceiling on total connections
minimumIdle= maxPoolConnections kept warm when idle
connectionTimeout30 000 msMax wait for a free connection; throws SQLTimeoutException
idleTimeout600 000 msTime an idle connection can sit in the pool before eviction
maxLifetime1 800 000 msAbsolute max connection age: prevents stale TCP connections
keepaliveTime0 (disabled)Period to ping idle connections to keep them alive through firewalls
connectionTestQuerynoneSQL to validate connections: use only if driver lacks JDBC 4 isValid()
leakDetectionThreshold0 (disabled)Log a warning if a connection is held longer than this (ms)
schemadriver defaultDefault schema for new connections
autoCommittrueInitial auto-commit for borrowed connections

Pool Sizing: The Right Formula

Bigger is not better. Every extra connection uses server memory and adds contention. The HikariCP author recommends:

Shell
# 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.

Connection Leak Detection

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:

Java
config.setLeakDetectionThreshold(5_000); // warn if connection held > 5 s

Sample log output:

Text
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.

Configuration via hikari.properties File

Instead of code, you can configure HikariCP from a properties file on the classpath:

Properties
# 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
Java
HikariConfig config = new HikariConfig("/hikari.properties");
HikariDataSource ds  = new HikariDataSource(config);

Graceful Shutdown

Close the pool when the application exits to release all connections cleanly:

Java
Runtime.getRuntime().addShutdownHook(new Thread(() -> {
    if (!DataSourceFactory.get().isClosed()) {
        DataSourceFactory.get().close();
        System.out.println("Connection pool closed");
    }
}));