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. It's 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, and 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 — for example, 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. This is especially true 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