Spring Boot Actuator: Avoid Connection Pool Exhaustion in Production
Learn how to use Spring Boot Actuator to detect and prevent HikariCP connection pool exhaustion in production.
20+ years shipping production Java in banking & fintech. Everything here is grounded in real deployments.
- ✓Spring Boot 3.2+ application with HikariCP (default connection pool)
- ✓Basic understanding of JDBC connection pooling concepts
- ✓Spring Actuator dependency in your build file
• Use /actuator/health with db component to see pool status in real time
• Expose hikaricp metrics via Micrometer to track active, idle, and pending connections
• Set up alerts when hikaricp.connections.active exceeds 80% of maximum-pool-size
• Leverage custom health indicators to fail fast when pool is near exhaustion
• Use metrics endpoint to correlate pool usage with request throughput and latency
Think of a connection pool like a taxi fleet for a busy airport. If you don't monitor how many taxis are in use (active connections) vs. waiting (idle), you'll run out of taxis during rush hour. Actuator is your dashboard showing every taxi's status so you can dispatch more (increase pool size) or fix a traffic jam (slow queries).
You've deployed your Spring Boot 3.2 payment-processing service to production. Everything's humming along — until Black Friday hits. Suddenly, customers can't complete transactions, support tickets explode, and you're staring at a stack trace that reads HikariPool-1 - Connection is not available, request timed out after 30000ms. Welcome to connection pool exhaustion, the silent killer of production Spring Boot applications.
I've debugged this exact scenario at 3 AM after a major SaaS billing platform went down. The root cause wasn't a database crash — it was a slow query that held connections hostage, starving every other request. The worst part? Standard monitoring showed CPU and memory were fine. The pool just silently drained.
Spring Boot Actuator is your first line of defense. Combined with Micrometer metrics and custom health indicators, you can detect pool pressure before it causes an outage. In this article, I'll show you exactly which endpoints to expose, which metrics to watch, and how to build a production-grade monitoring strategy that would have caught that Black Friday incident.
We'll cover real code for exposing HikariCP metrics, setting up threshold alerts, and building a custom health indicator that fails fast when your pool is at 85% capacity. No fluff — just the patterns I've used to keep 99.99% uptime on systems handling 10K+ requests per second.
Why Your Connection Pool is a Ticking Time Bomb
Let's get one thing straight: your default HikariCP configuration is not production-ready. Spring Boot gives you a pool of 10 connections by default, which is fine for development but a disaster waiting to happen under load. I've seen teams deploy with these defaults and wonder why their app dies during the first traffic spike.
The fundamental problem is that connection pools are finite. Every HTTP request that hits your database needs a connection from the pool. If a request takes 5 seconds to run a query, that connection is occupied for 5 seconds. Under 100 RPS with 10 connections, you're mathematically guaranteed to exhaust the pool if your average query time exceeds 100ms (Little's Law: L = λW, where L is connections needed, λ is arrival rate, W is service time).
Here's the production reality: you need to know three numbers at all times — the pool's maximum size, the current active connections, and the number of pending requests waiting for a connection. Actuator exposes all of this via the /actuator/metrics endpoint, but only if you've configured it correctly.
Let's start by ensuring your Actuator exposes the right metrics. You need to add the Micrometer HikariCP meter binder, which Spring Boot auto-configures when it detects HikariCP on the classpath. But you also need to enable the metrics endpoint and expose the hikaricp metric group.
management.endpoints.web.base-path=/internal and IP whitelisting for on-prem deployments.What the Official Docs Won't Tell You
The Spring Boot reference documentation tells you how to expose endpoints and configure metrics. It doesn't tell you that the default health indicator for a database is useless for detecting pool exhaustion. The DataSourceHealthIndicator just checks if it can get a connection — it doesn't check how many are in use. I've seen applications return UP with 9 out of 10 connections already active. That's not healthy.
What the docs won't tell you: you need to build a custom health indicator that calculates pool utilization as a percentage and returns DOWN when it exceeds a threshold. This is the only way to make your load balancer stop sending traffic to a dying instance before it completely locks up.
Here's the pattern I use in every production deployment. It reads HikariCP's HikariPoolMXBean directly to get active, idle, and pending threads. This is more reliable than parsing metrics strings and gives you real-time data without the overhead of Micrometer's metric collection pipeline.
Also, the official docs don't emphasize that you should monitor hikaricp.connections.timeout (a counter that increments every time a connection request times out). If this metric goes from 0 to 1, you're already in trouble. Set up a Prometheus alert on this metric with a threshold of 0 — any timeout is unacceptable.
Exposing HikariCP Metrics with Micrometer
Spring Boot 3.2 uses Micrometer 1.12+ for metrics. When HikariCP is on the classpath, Micrometer automatically binds a HikariCPMetrics gauge set. But here's the catch: these metrics are only registered if you have a DataSource bean and the pool is actively used. If your application starts but no queries run yet, the metrics won't appear in /actuator/metrics.
To verify your metrics are exposed, hit GET /actuator/metrics/hikaricp.connections.active. If you get a 404, the metrics haven't been registered yet. Run a single query against your database and try again. This is a common source of confusion for teams setting up monitoring for the first time.
hikaricp.connections.active: Currently in-use connectionshikaricp.connections.idle: Available connectionshikaricp.connections.pending: Threads waiting for a connectionhikaricp.connections.max: Configured maximum pool sizehikaricp.connections.timeout: Total connection timeout count (cumulative)hikaricp.connections.creation: Connection creation rate
For Prometheus, these become hikaricp_connections_active, etc. I recommend setting up a Grafana dashboard that shows active vs. max as a percentage, with a threshold line at 80%. When active crosses that line, your pager should go off.
Let's look at how to access these metrics programmatically for a custom actuator endpoint that gives you a pool health summary in one call.
/actuator/pool-metrics and fails if utilization exceeds 90%. This prevents the pod from receiving traffic when it's about to exhaust connections.MeterRegistry to build custom actuator endpoints that aggregate HikariCP metrics into a single, easy-to-consume response.Setting Up Prometheus Alerts for Pool Exhaustion
Monitoring without alerting is just data hoarding. You need alerts that wake you up before your users do. Here's the Prometheus alerting rules I use in every production Spring Boot deployment. These rules assume you've exposed the /actuator/prometheus endpoint and Prometheus is scraping it every 15 seconds.
The most important alert is HikariPoolHighUtilization. It fires when active connections exceed 80% of max for more than 1 minute. Why 1 minute? Because brief spikes are normal — a cache miss or a slow query can cause a temporary jump. Sustained high utilization indicates a real problem.
Second alert: HikariPoolConnectionTimeouts. This fires when the hikaricp_connections_timeout_total counter increases. Any connection timeout is a production incident. I set this to fire immediately with a P1 priority.
Third alert: HikariPoolPendingRequests. When threads are waiting for connections (pending > 0), it means the pool is saturated. I alert at pending > 5 for more than 30 seconds.
Here's the PrometheusRule YAML configuration. Save this in your monitoring stack (e.g., as a PrometheusRule custom resource in Kubernetes).
KubePodCrashLooping alert combined with pool metrics. If a pod crashes due to OOM and pool metrics show high utilization before the crash, it's a strong signal that the pool size is too small for the workload.Configuring HikariCP for Production Resilience
Your pool configuration is the foundation. Get this wrong and no amount of monitoring will save you. Here's what I use in production for a payment-processing service handling 5K TPS:
maximum-pool-size: Calculate this using Little's Law. For 5K TPS with average query time of 50ms, you need 250 connections (5000 * 0.05). But don't go above 300 — too many connections overwhelm the database.minimum-idle: Set this equal tomaximum-pool-size. Why? Because you want connections ready at all times. Lazy initialization causes latency spikes under load. The memory cost is negligible.connection-timeout: 5000ms. If a connection isn't available in 5 seconds, fail fast rather than hanging. Users prefer a 503 to a 30-second timeout.max-lifetime: 1800000ms (30 minutes). Forces connection refresh to avoid stale connections from database-side timeouts.idle-timeout: 600000ms (10 minutes). Only relevant if you setminimum-idlelower than max. With equal values, idle connections are never evicted.leak-detection-threshold: 60000ms (60 seconds). Logs a stack trace if a connection is held longer than 60 seconds. This is your early warning system for connection leaks.
Let's see this in a production configuration file. Note that I use environment variables for pool size so it can be tuned per environment without rebuilding.
leak-detection-threshold caught a developer who forgot to close a Statement in a batch job. The stack trace pointed directly to the offending code. Without it, we would have debugged for days thinking it was a database issue.minimum-idle equal to maximum-pool-size to avoid initialization latency. Enable leak-detection-threshold at 60 seconds. Always set a query timeout in Hibernate.Building a Real-Time Pool Dashboard with Actuator and Grafana
Metrics are useless if you can't visualize them. I build a Grafana dashboard for every Spring Boot service that shows pool health in real time. The dashboard has four panels: pool utilization percentage, active vs. idle connections, pending request count, and connection timeout rate.
First, ensure Prometheus is scraping your Actuator metrics. Add this to your Prometheus configuration. The key is to scrape the /actuator/prometheus endpoint. Spring Boot exposes this when you have micrometer-registry-prometheus on the classpath.
Once Prometheus is collecting data, import the following Grafana dashboard JSON. This dashboard uses PromQL queries against the HikariCP metrics. The utilization panel uses a gauge visualization with thresholds: green below 60%, yellow 60-80%, red above 80%.
- Pool utilization:
(hikaricp_connections_active / hikaricp_connections_max) * 100 - Active connections:
hikaricp_connections_active - Idle connections:
hikaricp_connections_idle - Pending requests:
hikaricp_connections_pending - Timeout rate:
rate(hikaricp_connections_timeout_total[5m])
I also add a panel showing the ratio of active to max over time. A flat line at 80% means your pool is perfectly sized for your load. A line trending upward means you're growing and need to increase pool size before it hits 100%.
Debugging Pool Exhaustion in Real Time
When the pager goes off at 3 AM because pool utilization is at 100%, you need a debug plan. Here's my step-by-step playbook:
- Check the health endpoint:
curl http://localhost:8080/actuator/health | jq .components.poolHealth. This tells you active, idle, pending, and utilization. If pending > 0, you're in trouble. - Get thread dump:
jstack. Look for threads blocked on> threaddump.txt HikariCPconnection acquisition. Search forWAITINGonjava.util.concurrent.CompletableFutureorHikariPool.getConnection. These are threads waiting for a connection. - Find the connection hogs: Use
jdbc:mysqlorpg_stat_activityto find queries running longer than expected. In PostgreSQL:SELECT pid,.now()- pg_stat_activity.query_start AS duration, query, state FROM pg_stat_activity WHERE state != 'idle' ORDER BY duration DESC LIMIT 10; - Kill the bad query: If you find a runaway query, kill it immediately. In PostgreSQL:
SELECT pg_terminate_backend(pid);. This will release its connection back to the pool. - Reset the pool if needed: If connections are stuck in a bad state, you can reset the pool via JMX. Use
jconsoleorcurlwith the Actuatorrestartendpoint (if enabled) to bounce the connections.
Here's a script I use to automate the initial diagnosis. It collects health, metrics, and thread dump in one command.
Preventing Pool Exhaustion with Circuit Breakers
Even with perfect monitoring, you can't prevent every exhaustion scenario. That's where circuit breakers come in. When your pool hits 80% utilization, you should start rejecting non-critical requests before the pool is completely exhausted. This is called graceful degradation.
Spring Boot 3.2 has built-in support for Resilience4j, which integrates perfectly with Actuator. You can create a circuit breaker that monitors pool utilization via a custom HealthService and opens the circuit when utilization is high. Once the circuit is open, requests fail fast with a 503 instead of hanging indefinitely waiting for a connection.
Here's the pattern: create a PoolHealthService that exposes the current utilization. Then configure a Resilience4j circuit breaker that uses this service as a failure rate evaluator. When utilization exceeds 80%, the circuit opens and all requests to the database-dependent endpoints fail immediately.
This buys you time. Instead of a cascading failure where every thread blocks on the pool, you have a controlled degradation. Users see a 503 with a clear message instead of a timeout. Your load balancer can then route traffic to healthy instances.
Let's implement this with Resilience4j and expose the circuit breaker state via Actuator.
The 3 AM Black Friday Meltdown
- A health check returning UP does not mean your pool is healthy
- Always monitor
hikaricp.connections.activeas a percentage ofmaximum-pool-size - Slow queries are the #1 cause of pool exhaustion — use query timeouts
- Expose all HikariCP metrics via Actuator before going to production
HikariPoolMXBean.softReset(). If that fails, restart the application. Investigate connection leak using leak-detection-threshold logs.curl -s http://localhost:8080/actuator/health | jq '.components.poolHealth'curl -s http://localhost:8080/actuator/metrics/hikaricp.connections.active | jq .now() - interval '30 seconds'| File | Command / Code | Purpose |
|---|---|---|
| application.yml | management:\n endpoints:\n web:\n exposure:\n include: health,me... | Why Your Connection Pool is a Ticking Time Bomb |
| prometheus-rules.yml | groups:\n - name: hikaricp-alerts\n interval: 15s\n rules:\n - alert... | Setting Up Prometheus Alerts for Pool Exhaustion |
| application-prod.yml | spring:\n datasource:\n hikari:\n maximum-pool-size: ${DB_POOL_MAX:50}\... | Configuring HikariCP for Production Resilience |
| prometheus-scrape-config.yml | scrape_configs:\n - job_name: 'spring-boot-apps'\n metrics_path: '/actuator/... | Building a Real-Time Pool Dashboard with Actuator and Grafan |
Key takeaways
minimum-idle equal to maximum-pool-size, leak-detection-threshold at 60 seconds, and always set Hibernate query timeouts. Use circuit breakers with Resilience4j for graceful degradation.Interview Questions on This Topic
What is the default HikariCP maximum pool size in Spring Boot 3.2 and why is it problematic in production?
Frequently Asked Questions
20+ years shipping production Java in banking & fintech. Everything here is grounded in real deployments.
That's Spring Boot. Mark it forged?
7 min read · try the examples if you haven't