Part 3: Connection Pool Leaks — Built-in Detection in Major Connection Pools
Series Navigation - Part 1: What They Are and Why They Matter - Part 2: How Spring Helps You Avoid Them - Part 3: Built-in Detection in Major Connection Pools (current) - Part 4: Building a Custom Connection Monitor - Part 5: Root Cause Analysis with Heap Dumps
1. Why Built-in Detection Matters
Even when Spring best practices are followed, edge cases can still lead to connection leaks. Major connection pools provide built-in leak detection as a last line of defense:
- Detect leaks: identify connections that have been out of the pool beyond a threshold, and print the call stack at borrow time
- Auto-reclaim (in some pools): forcibly close leaked connections to prevent pool exhaustion
Two main detection models are used across popular pools:
- Background thread periodic scan (Druid, Tomcat JDBC, DBCP2): a background thread periodically iterates all borrowed connections and acts on those exceeding the threshold
- Per-connection delayed task (HikariCP): a timed task is created each time a connection is borrowed; if the connection is not returned within the threshold, the task fires a warning
2. Configuration by Connection Pool
2.1 Druid
| Parameter | Description | Recommended Value |
|---|---|---|
removeAbandoned |
Enable leak detection and reclaim timed-out connections | true |
removeAbandonedTimeout |
Seconds before a borrowed connection is considered leaked | 180–600 s |
logAbandoned |
Log the stack trace recorded at borrow time | true |
spring:
datasource:
druid:
remove-abandoned: true
remove-abandoned-timeout: 300
log-abandoned: true
time-between-eviction-runs-millis: 60000
How it works: the background destroy thread (DestroyTask) periodically calls removeAbandoned():
- Acquires
activeConnectionLockand iterates all active connections - Checks whether the connection has a SQL statement currently executing (
pooledConnection.isRunning()); if so, skips it - Checks whether the elapsed time since borrow exceeds
removeAbandonedTimeoutMillis - Removes timed-out connections from the active set, marks them as
disabled, and forcibly closes them - Prints the borrow-time stack trace when
logAbandonedistrue
2.2 HikariCP
| Parameter | Description | Recommended Value |
|---|---|---|
leakDetectionThreshold |
Milliseconds a connection can be out of the pool before a warning is logged | 5000–30000 ms |
spring:
datasource:
hikari:
leak-detection-threshold: 30000
How it works:
- Default is
0(disabled); automatically reset to0if set below 2 seconds or abovemaxLifetime - HikariCP does not use a polling background scan; instead, each time a connection is borrowed, a
ProxyLeakTaskis scheduled in an internalScheduledExecutorService - If the connection is returned before the threshold, the task is cancelled; otherwise the task fires and logs a warning with the borrow-time stack trace
- Warning only — connections are not forcibly reclaimed
- Leaked connections are eventually closed when
maxLifetimeexpires, but this relies on themaxLifetimemechanism, not the leak detection itself
2.3 Tomcat JDBC
| Parameter | Description | Recommended Value |
|---|---|---|
removeAbandoned |
Enable abandoned connection detection and reclaim | true |
removeAbandonedTimeout |
Seconds before a connection is considered abandoned | 60–300 s |
logAbandoned |
Log the stack trace at borrow time | true |
<Resource name="jdbc/DataSource"
auth="Container"
type="javax.sql.DataSource"
factory="org.apache.tomcat.jdbc.pool.DataSourceFactory"
removeAbandoned="true"
removeAbandonedTimeout="60"
logAbandoned="true" />
How it works: the background PoolSweeper thread periodically checks all active connections. Connections exceeding the timeout are forcibly reclaimed, and the borrow-time stack trace is printed when logAbandoned is true.
2.4 Apache DBCP2
| Parameter | Description | Recommended Value |
|---|---|---|
removeAbandonedOnBorrow |
Check and remove abandoned connections when a connection is borrowed | true |
removeAbandonedOnMaintenance |
Check and remove abandoned connections during maintenance | true |
removeAbandonedTimeout |
Seconds before a connection is considered abandoned | 300 s |
logAbandoned |
Log the stack trace at borrow time | true |
Before using DBCP2 as the Spring Boot connection pool, complete these prerequisites:
<!-- pom.xml -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
<exclusions>
<exclusion>
<groupId>com.zaxxer</groupId>
<artifactId>HikariCP</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-dbcp2</artifactId>
</dependency>
spring.datasource.type=org.apache.commons.dbcp2.BasicDataSource
spring.datasource.dbcp2.remove-abandoned-on-borrow=true
spring.datasource.dbcp2.remove-abandoned-on-maintenance=true
spring.datasource.dbcp2.remove-abandoned-timeout=300
spring.datasource.dbcp2.log-abandoned=true
spring.datasource.dbcp2.time-between-eviction-runs-millis=60000
Java-based configuration:
@Bean
public DataSource dataSource() {
BasicDataSource ds = new BasicDataSource();
ds.setDriverClassName("com.mysql.cj.jdbc.Driver");
ds.setUrl("jdbc:mysql://localhost:3306/test");
ds.setUsername("root");
ds.setPassword("password");
ds.setInitialSize(5);
ds.setMaxTotal(20);
ds.setRemoveAbandonedOnBorrow(true);
ds.setRemoveAbandonedOnMaintenance(true);
ds.setRemoveAbandonedTimeout(300);
ds.setLogAbandoned(true);
ds.setTimeBetweenEvictionRunsMillis(60000);
return ds;
}
How it works:
removeAbandonedOnBorrow=true: triggers a check when a connection is borrowed. Note: the check only runs when the number of active connections exceeds(maxTotal - 2)or whenmaxTotal < 3. This avoids the overhead of scanning when the pool is not under pressure. If the parameter appears to have no effect, verify whether the active connection count is close to the limit.removeAbandonedOnMaintenance=true: triggers during the background maintenance thread (interval controlled bytimeBetweenEvictionRunsMillis). Not subject to the active connection count condition. Recommended to enable alongsideremoveAbandonedOnBorrow.
Version note: As of DBCP2 2.9.0,
setTimeBetweenEvictionRunsMillisis@Deprecated. The recommended replacement issetDurationBetweenEvictionRuns(Duration.ofMillis(60000)).
3. Capability Comparison
| Pool | Detection Parameter | Auto-Reclaim | Stack Trace | Detection Model |
|---|---|---|---|---|
| Druid | removeAbandoned + removeAbandonedTimeout |
✅ | ✅ (logAbandoned) |
Background thread periodic scan |
| HikariCP | leakDetectionThreshold |
❌ Warning only | ✅ | Per-connection delayed task |
| Tomcat JDBC | removeAbandoned + removeAbandonedTimeout |
✅ | ✅ (logAbandoned) |
Background thread periodic scan |
| DBCP2 | removeAbandonedOnBorrow + removeAbandonedOnMaintenance + removeAbandonedTimeout |
✅ | ✅ (logAbandoned) |
Conditional borrow-time check + background maintenance thread |
4. Things to Keep in Mind
Setting removeAbandonedTimeout appropriately
A threshold that is too small risks incorrectly reclaiming legitimate long-running transactions. A threshold that is too large delays detection. A practical guideline:
removeAbandonedTimeout = max(normal longest transaction duration) × 2
Production impact
- Druid / Tomcat JDBC / DBCP2 auto-reclaim will forcibly close leaked connections, which can interrupt an in-progress long transaction. Confirm the threshold is appropriate before enabling in production.
- HikariCP only logs a warning and has no impact on running business logic — safe to enable in production without hesitation.
This series is based on Druid 1.2.x, HikariCP 5.x (Java 11+; use HikariCP 3.x for Java 8), Tomcat JDBC 10.x, and DBCP2 2.9.x. All code examples are for reference only. Adjust them to your specific environment before using in production.