Spring Boot + HikariCP: 20 vs 2 idle PostgreSQL connections

I've been experimenting with HikariCP connection pool sizing in Spring Boot, particularly what happens to PostgreSQL connections after traffic stops.

One detail that's easy to miss: with Hikari's default sizing, minimumIdle and maximumPoolSize both resolve to 10. Since the idle target equals the maximum, idleTimeout has nothing to retire.

I ran two Spring Boot instances against PostgreSQL 16 on a 1 GiB VM, comparing three configurations:

  • Default sizing: Both pools retained 10 connections each after traffic stopped.
  • minimumIdle=1, retirement disabled: Pools still retained all 20 connections after growing under load.
  • minimumIdle=1, retirement enabled: Connections dropped from 20 to 2, with approximately 29 MiB lower PostgreSQL cgroup memory.

https://preview.redd.it/rk5qd3ts69vh1.png?width=1834&format=png&auto=webp&s=123895181e6830168d96e36903ccc3c2742a6073

There was a tradeoff. When traffic returned, median connection acquisition increased from under 3 ms to roughly 200 ms as the pools rebuilt capacity.

The experiment used a deliberately short 10-second idle timeout to make the behavior measurable, not as a production tuning recommendation.

Two repetitions, 2,280 successful workload requests, with recorded data and reproduction instructions.

Full write-up: https://pvrlabs.xyz/articles/spring-boot-jdbc-pool-reclaim.html

Curious how others configure HikariCP when multiple Spring Boot instances share a small PostgreSQL server. Do you generally keep the default fixed-size pools or allow idle connections to retire?

6 comments · 100% upvoted · other discussions

comments

free-as-in-freedom

It’d be interesting to know why Hikari comes with such defaults. 10 is rather low as a max, and rather high for a random app if connections are never retired.

fykup [OP]

Good question. HikariCP actually has a detailed explanation of its pool-sizing philosophy:

https://github.com/brettwooldridge/HikariCP/wiki/About-Pool-Sizing

I think 10 is ultimately a convenient round number backed by reasonable theory, rather than a universally optimal default. The actual pool size depends heavily on the workload, hardware, and deployment architecture.

I'm more skeptical of keeping all 10 connections open indefinitely by default. That makes sense for latency-sensitive workloads, but can be unnecessarily expensive when many application instances or rarely used secondary pools share the same database.

free-as-in-freedom

What a great read, thank you very much

[deleted]

[removed]

fykup [OP]

Great points, especially about managed PostgreSQL and TLS overhead.

I kept maximumPoolSize=10 across all three configurations to isolate the memory savings and reconnection cost of retiring idle connections.

I think the tradeoff is different when PostgreSQL is self-hosted or co-located on the same VPS. Reconnecting is generally cheaper, and reclaiming database memory directly benefits the same memory-constrained server.

I also wonder whether a more adaptive approach could work well: keeping a few warm connections at different utilization levels rather than shrinking all the way to minimumIdle. Something worth exploring, though I haven't tested it.

Agreed that right-sizing maximumPoolSize is the first step.

ducki666

Since your db most likely cannot scale on load you always need the max amount of mem your app every needs. If it really makes sense to release connections really depends on use cases and max pool sizes. Imho makes sense when your db is always low on mem and you have many app instances with potentially a lot idle connections.