How does connection pooling work in Spring Boot (HikariCP), and why does it matter?

8 minintermediateconnection-poolinghikaricpdatasource

Quick Answer

Opening a new database connection is relatively expensive (TCP handshake, authentication, session setup), so a connection pool keeps a set of already-open connections ready to reuse across requests instead of opening/closing one per query. Spring Boot uses HikariCP as its default connection pool (auto-configured whenever a DataSource is needed), tunable via spring.datasource.hikari.* properties like maximum-pool-size and connection-timeout — sizing the pool correctly (not too small, causing threads to wait for a connection; not needlessly large, overwhelming the database) is a common, genuinely impactful production tuning concern.

Detailed Answer

Establishing a brand-new database connection is a relatively expensive operation — a TCP handshake, authentication, session/charset negotiation — expensive enough that opening and closing a fresh connection for every single query would be a serious performance bottleneck under any real load.

A connection pool solves this by maintaining a set of already-established, ready-to-use connections that application code borrows, uses briefly, and returns — instead of tearing down and recreating a connection every time.

Spring Boot uses HikariCP as its default connection pool implementation whenever a DataSource is auto-configured (it's on the classpath transitively via spring-boot-starter-data-jpa/spring-boot-starter-jdbc, and Spring Boot's auto-configuration prefers it over alternatives like Apache DBCP2 or Tomcat's pool when multiple are present) — chosen for its low overhead and strong benchmarked performance relative to older pool implementations.

Key tunable properties (spring.datasource.hikari.*):

spring.datasource.hikari.maximum-pool-size=10
spring.datasource.hikari.minimum-idle=5
spring.datasource.hikari.connection-timeout=30000
spring.datasource.hikari.idle-timeout=600000
spring.datasource.hikari.max-lifetime=1800000
  • maximum-pool-size: the hard cap on concurrently open connections. Too small, and under load, request-handling threads queue up waiting for a free connection (visible as connection-timeout errors or generally increased latency under load); too large, and you risk overwhelming the database server itself (each connection consumes real memory/resources on the database side too — a pool sized for "every app instance times a generous max" across a fleet of instances can easily exceed what the database can actually sustain).
  • connection-timeout: how long a thread will wait for an available connection before giving up with an exception, rather than blocking indefinitely.
  • max-lifetime: connections are proactively retired and replaced after this long, to avoid issues with connections that have silently gone stale (e.g., due to a network device or database-side timeout closing them without the pool's immediate knowledge).

Why this is a genuinely impactful production concern (not just a minor tuning knob): a pool sized too small is a very common, very real cause of production latency/timeout incidents under load — especially in a horizontally-scaled deployment (many application instances, each with its own pool) where the sum of every instance's maximum-pool-size needs to stay within what the actual database can handle, a detail that's easy to overlook when each individual instance's pool size looks reasonable in isolation.

Related Resources