Java Flight Recorder (JFR) provides continuous, low-overhead profiling by capturing runtime execution samples directly within the JVM. In this blog, we will examine DB Connection Leaks, a performance issue commonly caused by an application repeatedly opening database connections without closing them, eventually exhausting the database’s connection limit. We will simulate the problem, capture the relevant JFR data, and analyze the recording to identify the underlying issue. Let’s take a closer look.
What are DB Connection Leaks?

Fig: Connection leaks exhaust the pool, causing new requests to be rejected.
A DB connection leak occurs when an application opens a connection to a database but fails to close or return it once the work is done. Each open connection consumes resources on both the application and database sides, and since most databases only allow a limited number of concurrent connections, repeatedly opening new ones without releasing old ones eventually exhausts that limit. Once the limit is reached, the database rejects any further connection attempts, and the application starts failing with connection errors.
What causes ‘DB Connection Leaks’?
Let’s look at the list of causes for this DB Connection Leaks issue:
- Failure to Close Connections: If a connection is opened but the code never calls close() on it, whether due to an oversight or an exception skipping the cleanup step, that connection stays open indefinitely, consuming a slot in the database’s connection limit.
- Failing to Return a Connection to the Pool: When using a connection pool, a connection that’s checked out but never returned is unavailable to the rest of the application, even though the pool believes it’s still in circulation. Over time, this shrinks the pool’s effective capacity.
- Exceptions Bypassing Cleanup Code: If an error occurs between opening a connection and closing it, and the closing logic isn’t in a finally block or a try-with-resources statement, the connection leaks silently every time that exception path is hit.
Simulating DB Connection Leaks Performance Issue
To understand how DB Connection Leaks appears in JFR data, let’s reproduce the issue using a sample Java application. The following program deliberately opens a new database connection on every iteration of a loop without ever closing it, exhausting the database’s connection limit over time.
public class DBLeakDemo { private static DBConnectionLeak dbConnectionLeak = null; public static void start(String jdbcUrl, String username, String password, String tableName) { dbConnectionLeak = new DBConnectionLeak(jdbcUrl, username, password, tableName); dbConnectionLeak.leakConnections(); } public static void stop() { if (dbConnectionLeak != null) { dbConnectionLeak.setLeakConnections(false); } System.out.println("DB Connection problem leak terminated!"); }}
public class DBConnectionLeak { private Boolean leakConnections = true; private String jdbcUrl; private String username; private String password; private String tableName; public DBConnectionLeak(String jdbcUrl, String username, String password, String tableName) { this.jdbcUrl = jdbcUrl; this.username = username; this.password = password; this.tableName = tableName; } public Connection getConnection() throws SQLException { try { Class.forName("com.mysql.jdbc.Driver"); } catch (ClassNotFoundException e) { // TODO Auto-generated catch block e.printStackTrace(); } return DriverManager.getConnection(jdbcUrl, username, password); } public void closeConnection(Connection connection) throws SQLException { if (connection != null) { connection.close(); } } /** * Opens a SQL connection and never closes it */ public void leakConnection() { Connection connection = null; try { connection = getConnection(); System.out.println("Leaking DB connection"); // Perform database operations using the connection PreparedStatement statement = connection.prepareStatement("SELECT * FROM " + tableName); ResultSet resultSet = statement.executeQuery(); resultSet.close(); statement.close(); } catch (SQLException e) { e.printStackTrace(); } finally { try { //Connection not closed // closeConnection(connection); } catch (Exception e) { e.printStackTrace(); } } } /** * Introduces a continuous connection leak sql connection leak problem */ public void leakConnections() { while (true && leakConnections) { leakConnection(); } } public Boolean getLeakConnections() { return leakConnections; } public void setLeakConnections(Boolean leakConnections) { this.leakConnections = leakConnections; }}
In this program, start() constructs a DBConnectionLeak instance with the target database’s connection details, then calls leakConnections(). This method opens a new database connection on every pass through a loop, performs a query, and never closes the connection, so it stays open indefinitely. As the loop continues, each iteration adds one more open connection without releasing any of the previous ones, so the number of active connections climbs steadily until the database reaches its connection limit and starts rejecting new attempts, typically surfacing as a “Too many connections” error.
Capturing JFR Data for Troubleshooting DB Connection Leaks
To capture JFR data for troubleshooting the DB Connection Leaks issue, we recommend that you follow the steps below:
If you are interested in learning about the other methods available to capture JFR recordings, we recommend that you read ‘How to Capture Java Flight Recorder (JFR)?’ blog.
Step 1: Start a JFR recording against the running JVM:
jcmd {PID} JFR.start \ name=loadTestCapture \ settings=profile \ filename=/tmp/tomcat.jfr
Note: Replace <PID> with the Process ID (PID) of your Java application running inside the container. If you don’t know how to find it, refer to our guide on finding the Java application Process ID (PID) for step-by-step instructions.
Step 2: Let it run while the DB Connection Leaks is occurring, then stop it manually:
jcmd {PID} JFR.stop \ name=loadTestCapture
Step 3: Alternatively, for Fixed-Duration Capture, you can start a recording that automatically exits after a predefined duration (for example, 15 minutes) in a single step:
jcmd {PID} JFR.start \ name=loadTestCapture \ settings=profile \ duration=15m \ filename=/tmp/tomcat.jfr
Analyzing DB Connection Leaks Using the JFR Data
You can analyze JFR recording using yCrash JFRPlayer by following the steps mentioned below.
Step 1: Install yCrash JFRPlayer, which is available in both cloud and on-premises versions. Use one of the options below to get started:
- Cloud service: Register and upload your JFR file online.
- On-Premises: Install and run yCrash JFRPlayer on your local machine or within your organization’s environment.
Step 2: Upload the jfr file to your yCrash JFRPlayer. Once JFR file is uploaded, JFRPlayer parses the JFR file and generates an incident report instantly.

Fig: Uploading a standalone JFR file to yCrash JFRPlayer
Step 3: The AI Overview traces the issue to com.buggyapp.dbconnectionleak.DBConnectionLeak.leakConnection(), backed by method profiling data showing significant CPU time in java.sql.DriverManager.getConnection and com.mysql.jdbc.ConnectionImpl constructors, with the call tree tracing back through DBLeakDemo.leakConnections() to main.

Fig: AI Overview identifying the DB connection leak source
Step 4: The Bottom Up Call Stack Tree confirms the application call path, LaunchPad.main() at line 147 calling DBLeakDemo.start() at line 9, down through DBConnectionLeak.leakConnections() at line 76.

Fig: Call stack tree confirming the leak’s path from main to DBConnectionLeak.leakConnections()
Step 5: Clicking “click here” expands the complete tree, revealing leakConnections() at line 76 calling leakConnection() at line 59, which lands in Throwable.printStackTrace(), confirming the leak method as the direct source of the exception-driven CPU overhead.

Fig: Complete call stack tree confirming leakConnection() terminating in the exception stack trace print
Simple, right? Now that we have analyzed the data, we are equipped with all the information to fix the problem.
If you face any challenges while analyzing a JFR file using yCrash JFRPlayer, check out our FAQ for answers to common questions and troubleshooting guidance.
How to fix DB Connection Leaks
The following are potential solutions to fix this issue:
- Use Try-With-Resources: Wrap the connection, statement, and result set in a try-with-resources statement so they’re automatically closed when the block exits, regardless of whether an exception occurs.
- Close Connections in a Finally Block: If try-with-resources isn’t an option, explicitly close the connection inside a finally block to guarantee it runs even when an exception is thrown.
- Use a Connection Pool: Rely on a connection pooling library rather than managing raw connections manually. Pools handle acquisition and release automatically and can also cap and monitor how many connections are in use.
- Monitor Connection Counts: A JFR recording or database-side monitoring showing a steadily climbing connection count, rather than one that rises and falls with normal traffic, is a clear signal to investigate for a leak.
Conclusion
Diagnosing and identifying the root cause of DB Connection Leaks can be challenging, especially in complex production environments where multiple symptoms often overlap. In this example, the JFR recording was analyzed using yCrash JFRPlayer, which automatically identified the key performance bottlenecks and correlated JVM events to pinpoint the underlying problem. The analysis traced the issue to DBConnectionLeak.leakConnections() repeatedly opening connections that were never closed. By examining the relevant JFR insights, including socket and connection activity, thread activity, and object allocation patterns, we were able to identify the root cause and determine the appropriate fix.

Share your Thoughts!