A c3p0 connection pool is a set of open database connections that Hibernate borrows and returns instead of opening a new connection for every transaction. The c3p0 library is a mature open-source JDBC connection pool, and Hibernate uses it through its hibernate-c3p0 module.
We use c3p0 in plain Hibernate applications that run on a server, where a new connection for every request would be slow and Hibernate’s built-in pool is not supported.
The following example adds hibernate-c3p0 to the classpath and configures the pool with hibernate.c3p0.* settings, such as the minimum and maximum number of connections and the time after which c3p0 closes or tests idle ones.
<dependency>
<groupId>org.hibernate.orm</groupId>
<artifactId>hibernate-c3p0</artifactId>
<version>7.4.11.Final</version>
</dependency>
<dependency>
<groupId>com.mchange</groupId>
<artifactId>c3p0</artifactId>
<version>0.14.2</version>
</dependency>
new HibernatePersistenceConfiguration("garage")
.managedClasses(ParkingEntry.class)
.jdbcDriver("org.h2.Driver")
.jdbcUrl("jdbc:h2:mem:garage;DB_CLOSE_DELAY=-1")
.jdbcCredentials("sa", "")
.property("hibernate.c3p0.min_size", 5) // keep at least 5 connections
.property("hibernate.c3p0.max_size", 20) // never open more than 20
.property("hibernate.c3p0.acquire_increment", 5) // grow 5 at a time
.property("hibernate.c3p0.timeout", 300) // close connections idle for 300 s
.property("hibernate.c3p0.idle_test_period", 30) // test idle connections every 30 s
.property("hibernate.c3p0.max_statements", 50) // cache 50 PreparedStatements
.createEntityManagerFactory();
int total = pool.getNumConnectionsDefaultUser(); // 5 after startup
emf.runInTransaction(em -> {
em.persist(new ParkingEntry("ab-123", LocalDateTime.of(2026, 10, 3, 8, 15)));
int busy = pool.getNumBusyConnectionsDefaultUser(); // 1, the transaction holds one connection
});
int busyAfter = pool.getNumBusyConnectionsDefaultUser(); // 0, returned at commit
Notice that Hibernate borrows a connection from c3p0 when a transaction begins and returns it at commit, while the pool keeps its 5 connections open.
Next, we look at how Hibernate picks the pool and how to size and test it. After that, we set up c3p0 in Spring Boot and compare it with HikariCP and Agroal.
1. What Is a Connection Pool?
Opening a JDBC connection is slow compared to running a short query. The driver opens a network connection, and the database checks the user and password before it creates a session for the client. A connection pool opens these connections once and keeps them open, so the application borrows a connection instead of opening one. A borrowed connection is busy, whereas a connection waiting in the pool is idle.
For example, a parking garage app that saves an entry for every car at the gate would otherwise log in to the database once per car.

Hibernate gets every connection from a ConnectionProvider, an interface with one implementation per pool. The hibernate-c3p0 module adds C3P0ConnectionProvider, which wraps a c3p0 pool. Hibernate supports five providers.
| ConnectionProvider | Module | When to use it |
|---|---|---|
| DriverManagerConnectionProvider | hibernate-core (built in) | Demos and tests only |
| C3P0ConnectionProvider | hibernate-c3p0 | Plain Hibernate with c3p0 |
| HikariCP provider | hibernate-hikaricp | Plain Hibernate with HikariCP |
| Agroal provider | hibernate-agroal | Plain Hibernate with Agroal |
| DataSourceConnectionProvider | hibernate-core | A DataSource from Spring Boot, an application server or JNDI |
1.1. Hibernate’s Built-in Pool Is Not for Production
When no pool module is on the classpath and we only set a JDBC URL, Hibernate uses its own small pool and logs a warning at startup.
WARN org.hibernate.orm.connections.pooling - HHH10001002: Using built-in connection pool (not intended for production use)
INFO org.hibernate.orm.connections.pooling - HHH10001005: Database info:
...
Pool: DriverManagerConnectionProvider
Minimum pool size: 1
Maximum pool size: 20
Hibernate does not support this pool in production. The warning is fine in a first Hibernate application or a test, but a server application needs a real pool.
1.2. How Hibernate Picks the Pool
Hibernate 7.4 decides which ConnectionProvider to use when the EntityManagerFactory starts. It checks the rules in a fixed order and stops at the first match.

With hibernate-c3p0 on the classpath and no pool settings at all, Hibernate 7.4 starts C3P0ConnectionProvider with the c3p0 defaults of 3 to 15 connections. The hibernate.c3p0.* settings decide the pool only when several pool modules are on the classpath, so in practice we use them to size and tune the pool.
2. Configuring c3p0 With Hibernate
The following example is a parking garage app that logs every car entering it. The ParkingEntry entity has a plate and an entry time, mapped with @Entity and a LocalDateTime field. We run it with Hibernate 7.4.11.Final, c3p0 0.14.2, Java 25 and an in-memory H2 database.
@Entity
public class ParkingEntry {
@Id
@GeneratedValue
private Long id;
private String plate;
private LocalDateTime enteredAt;
}
2.1. Maven Dependencies
The hibernate-c3p0 module contains the ConnectionProvider, and its version must match hibernate-core. Version 7.4.11.Final brings c3p0 0.12.0, so we declare c3p0 0.14.2 ourselves to get the current release. The c3p0 versions 0.13.0 and 0.14.0 removed more ways to misuse c3p0 as a deserialization gadget.
<dependency>
<groupId>org.hibernate.orm</groupId>
<artifactId>hibernate-core</artifactId>
<version>7.4.11.Final</version>
</dependency>
<dependency>
<groupId>org.hibernate.orm</groupId>
<artifactId>hibernate-c3p0</artifactId>
<version>7.4.11.Final</version>
</dependency>
<dependency>
<groupId>com.mchange</groupId>
<artifactId>c3p0</artifactId>
<version>0.14.2</version>
</dependency>
2.2. The hibernate.c3p0 Settings
Hibernate defines short hibernate.c3p0.* keys for the most used c3p0 properties. Each key sets one c3p0 property, and c3p0 defines the defaults.
| Hibernate setting | c3p0 property | Default | What it does |
|---|---|---|---|
| hibernate.c3p0.min_size | minPoolSize | 3 | Connections the pool keeps open even when nobody uses them |
| hibernate.c3p0.max_size | maxPoolSize | 15 | Upper limit of open connections; extra requests wait |
| hibernate.c3p0.acquire_increment | acquireIncrement | 3 | Connections opened at once when all are busy |
| hibernate.c3p0.timeout | maxIdleTime | 0 (never) | Seconds an idle connection stays open before c3p0 closes it |
| hibernate.c3p0.idle_test_period | idleConnectionTestPeriod | 0 (never) | Seconds between tests of idle connections |
| hibernate.c3p0.max_statements | maxStatements | 0 (off) | Size of c3p0’s PreparedStatement cache for the whole pool |
| hibernate.c3p0.initialPoolSize | initialPoolSize | min_size | Connections opened at startup |
Any other c3p0 property works with the hibernate.c3p0. prefix and its c3p0 name, for example hibernate.c3p0.checkoutTimeout or hibernate.c3p0.testConnectionOnCheckin. Hibernate strips hibernate. and passes the rest to c3p0. We set them in code with HibernatePersistenceConfiguration (as in the intro) or in persistence.xml.
<persistence-unit name="garage">
<class>com.howtodoinjava.hibernate.c3p0.ParkingEntry</class>
<properties>
<property name="jakarta.persistence.jdbc.url" value="jdbc:h2:mem:garage-xml;DB_CLOSE_DELAY=-1"/>
<property name="jakarta.persistence.jdbc.user" value="sa"/>
<property name="jakarta.persistence.jdbc.password" value=""/>
<property name="hibernate.c3p0.min_size" value="5"/>
<property name="hibernate.c3p0.max_size" value="20"/>
<property name="hibernate.c3p0.acquire_increment" value="5"/>
<property name="hibernate.c3p0.timeout" value="300"/>
<property name="hibernate.c3p0.idle_test_period" value="30"/>
<property name="hibernate.c3p0.max_statements" value="50"/>
</properties>
</persistence-unit>
The old hibernate.cfg.xml file accepts the same keys as property elements.
2.3. Verifying That Hibernate Uses c3p0
The startup log is the fastest check, because c3p0 logs its version and all its settings, and Hibernate prints the pool it chose. We get these lines with INFO logging for two loggers (here in simplelogger.properties for slf4j-simple).
org.slf4j.simpleLogger.log.org.hibernate.orm.connections.pooling=info
org.slf4j.simpleLogger.log.com.mchange=info
INFO com.mchange.v2.c3p0.C3P0Registry - Initializing c3p0-0.14.2 [built 2026-09-04T00:05:25.164278Z; debug? true; trace: 10]
INFO com.mchange.v2.c3p0.impl.AbstractPoolBackedDataSource - Initializing c3p0 pool... com.mchange.v2.c3p0.PoolBackedDataSource@3cdf6977 [ ... acquireIncrement -> 5, ... idleConnectionTestPeriod -> 30, initialPoolSize -> 5, ... maxIdleTime -> 300, ... maxPoolSize -> 20, maxStatements -> 50, ... minPoolSize -> 5, ... ]
INFO org.hibernate.orm.connections.pooling - HHH10001005: Database info:
...
Pool: C3P0ConnectionProvider
Minimum pool size: 5
Maximum pool size: 20
In code, we ask Hibernate for its ConnectionProvider and unwrap the c3p0 pool. The PooledDataSource interface of c3p0 reports the connection counts.
ConnectionProvider provider = emf.unwrap(SessionFactoryImplementor.class)
.getServiceRegistry()
.requireService(ConnectionProvider.class); // C3P0ConnectionProvider
PooledDataSource pool = (PooledDataSource) provider.unwrap(DataSource.class);
int total = pool.getNumConnectionsDefaultUser(); // 5
int busy = pool.getNumBusyConnectionsDefaultUser(); // 0
int idle = pool.getNumIdleConnectionsDefaultUser(); // 5
A transaction holds one connection from begin() until commit(). Saving two entries with persist() shows the busy count.
emf.runInTransaction(em -> {
em.persist(new ParkingEntry("ab-123", LocalDateTime.of(2026, 10, 3, 8, 15)));
em.persist(new ParkingEntry("cd-456", LocalDateTime.of(2026, 10, 3, 8, 40)));
em.flush(); // insert into ParkingEntry (enteredAt,plate,id) values (?,?,?) x2
// total=5, busy=1, idle=4
}); // after commit: total=5, busy=0, idle=5
The c3p0 pool returns connections in a background thread, so the busy count can stay at 1 for a few milliseconds after commit(). Tests that read the counts should poll for a short time instead of reading them once.
2.4. Sizing the Pool
When all connections are busy and the pool is below max_size, c3p0 opens acquire_increment new connections at once. With 7 open transactions and our settings, the pool grows from 5 to 10.

After startup: total=5, busy=0, idle=5
7 open transactions: total=10, busy=7, idle=3
After they finish: total=10, busy=0, idle=10
When the pool reaches max_size, the next request waits for a free connection. The c3p0 setting checkoutTimeout defaults to 0, which means the thread waits with no time limit. We set it in milliseconds so that a full pool fails fast with a clear error. With max_size 2, two open transactions and checkoutTimeout 1000, the third transaction fails after about one second with a GenericJDBCException.
org.hibernate.exception.GenericJDBCException: Unable to acquire JDBC Connection [An attempt by a client to checkout a Connection has timed out.] [n/a]
Caused by: com.mchange.v2.resourcepool.TimeoutException: A client timed out while waiting to acquire a resource from com.mchange.v2.resourcepool.BasicResourcePool@... -- timeout at awaitAvailable()
A larger pool is not faster by itself, because the database runs only a limited number of queries in parallel. These rules give a sound starting point.
- The sum of max_size over all application instances must stay below the database limit. PostgreSQL, for example, allows 100 connections by default, so four instances with max_size 20 use 80 of them.
- A min_size that covers normal load lets most requests find an idle connection.
- An acquire_increment between 1 and 5 grows the pool in small steps.
- A checkoutTimeout of a few seconds turns a stuck pool into an error that shows up in the logs.
2.5. Idle Connections and Timeouts
Open connections cost memory on the database server, and firewalls or the database itself can close connections that stay unused for long. The c3p0 pool has three settings that close connections.
| Setting | What c3p0 closes | Typical value |
|---|---|---|
| hibernate.c3p0.timeout (maxIdleTime) | Any connection idle for this many seconds | Shorter than the database or firewall idle limit |
| hibernate.c3p0.maxIdleTimeExcessConnections | Only idle connections above min_size | 60 to 300 seconds, to shrink the pool after a peak |
| hibernate.c3p0.maxConnectionAge | Any connection older than this many seconds, however recently it was used | Hours, to renew connections from time to time |
The setting maxIdleTimeExcessConnections shrinks the pool after a peak and keeps min_size connections open. With min_size 2 and the setting at 2 seconds, six transactions grew the pool, and a few seconds after they finished it was back to 2.
6 open transactions: total=6, busy=6, idle=0
A few seconds later: total=2, busy=0, idle=2
2.6. Testing Connections Before Use
A pooled connection can break while it waits in the pool, for example when the database restarts. The c3p0 pool tests connections only when we ask for it. To simulate a restart, we shut down an H2 database while the pool still holds its connections. Without connection testing, the next transaction gets a dead connection and fails.
org.hibernate.exception.JDBCConnectionException: Unable to acquire JDBC Connection [Database is already closed (to disable automatic closing at VM shutdown, add ";DB_CLOSE_ON_EXIT=FALSE" to the db URL) [90121-252]] [n/a]
With hibernate.c3p0.testConnectionOnCheckout set to true, c3p0 tests each connection before lending it and replaces the dead ones with new connections. With the dead connections replaced, the same query returns the saved entry. The c3p0 pool offers three ways to test connections.
| Setting | When c3p0 tests | Cost |
|---|---|---|
| testConnectionOnCheckout | Before every borrow | One extra check per transaction, inside the request |
| testConnectionOnCheckin | When a connection is returned, in a background thread | No delay for the request |
| idle_test_period (idleConnectionTestPeriod) | Every N seconds for idle connections | Background only |
The c3p0 project recommends testConnectionOnCheckout = true as the simplest setup, and testConnectionOnCheckin = true with idle_test_period = 30 for less overhead. With a JDBC 4 driver, we leave preferredTestQuery unset and c3p0 uses Connection.isValid().
.property("hibernate.c3p0.testConnectionOnCheckin", true)
.property("hibernate.c3p0.idle_test_period", 30)
2.7. Finding Connection Leaks
A connection leak is code that borrows a connection and never returns it, for example an EntityManager with an open transaction that is never closed. Each leak takes one connection out of the pool until max_size is reached and every request waits. Two c3p0 settings find the leak.
.property("hibernate.c3p0.unreturnedConnectionTimeout", 2) // seconds
.property("hibernate.c3p0.debugUnreturnedConnectionStackTraces", true)
The setting unreturnedConnectionTimeout destroys a connection that stays borrowed longer than the given seconds. The second setting, debugUnreturnedConnectionStackTraces, records where each connection was borrowed and logs that stack trace when the timeout hits.
INFO com.mchange.v2.resourcepool.BasicResourcePool - A checked-out resource is overdue, and will be destroyed: com.mchange.v2.c3p0.impl.NewPooledConnection@2ee227d0
INFO com.mchange.v2.resourcepool.BasicResourcePool - Logging the stack trace by which the overdue resource was checked-out.
java.lang.Exception: DEBUG STACK TRACE: Overdue resource check-out stack trace.
at com.mchange.v2.resourcepool.BasicResourcePool.checkoutResource(BasicResourcePool.java:651)
...
at org.hibernate.engine.transaction.internal.TransactionImpl.begin(TransactionImpl.java:75)
at com.howtodoinjava.hibernate.c3p0.C3p0Demo.main(C3p0Demo.java:96)
The last line points to our code that began the transaction. The timeout also kills slow but valid transactions, so we use it to find leaks and set it well above the longest real transaction. Capturing a stack trace on every borrow slows the pool, so debugUnreturnedConnectionStackTraces belongs in test or staging environments.
The complete project on GitHub runs every case from this section, prints the pool statistics and includes 18 JUnit tests (mvn -q compile exec:java, mvn test).
3. Using c3p0 in Spring Boot
Spring Boot 4 uses HikariCP by default, because spring-boot-starter-jdbc and spring-boot-starter-data-jpa bring it in. Spring Boot creates the DataSource (the pool) and passes it to Hibernate, so we switch the pool on the Spring side and need only the c3p0 dependency, not hibernate-c3p0.
<dependency>
<groupId>com.mchange</groupId>
<artifactId>c3p0</artifactId>
<version>0.14.2</version>
</dependency>
Setting spring.datasource.type selects c3p0 with its default settings. Spring Boot maps the connection URL and the credentials to c3p0, but it does not bind pool settings such as the maximum size.
spring.datasource.url=jdbc:h2:mem:garage-boot;DB_CLOSE_DELAY=-1
spring.datasource.username=sa
spring.datasource.type=com.mchange.v2.c3p0.ComboPooledDataSource
To tune the pool, we declare the DataSource bean ourselves. The class DataSourceProperties reads the spring.datasource.* connection settings, and @ConfigurationProperties binds the c3p0 properties of ComboPooledDataSource from our own prefix.
@Configuration(proxyBeanMethods = false)
public class C3p0DataSourceConfig {
@Bean
@ConfigurationProperties("spring.datasource.c3p0")
ComboPooledDataSource dataSource(DataSourceProperties properties) {
return properties.initializeDataSourceBuilder()
.type(ComboPooledDataSource.class)
.build();
}
}
spring.datasource.url=jdbc:h2:mem:garage-boot;DB_CLOSE_DELAY=-1
spring.datasource.username=sa
spring.datasource.c3p0.initial-pool-size=5
spring.datasource.c3p0.min-pool-size=5
spring.datasource.c3p0.max-pool-size=20
spring.datasource.c3p0.acquire-increment=5
spring.datasource.c3p0.max-idle-time=300
spring.datasource.c3p0.idle-connection-test-period=30
spring.datasource.c3p0.test-connection-on-checkin=true
When our application defines its own DataSource bean, Spring Boot does not create the HikariCP pool, so HikariCP can stay on the classpath.
4. c3p0 vs HikariCP vs Agroal
Hibernate has a module for each of the three pools, c3p0, HikariCP and Agroal, and all of them implement javax.sql.DataSource. The pools differ in defaults and in what they check.
| Feature | c3p0 | HikariCP | Agroal |
|---|---|---|---|
| Latest version (October 2026) | 0.14.2 | 7.1.0 | 3.2.1 |
| Hibernate module and prefix | hibernate-c3p0, hibernate.c3p0.* | hibernate-hikaricp, hibernate.hikari.* | hibernate-agroal, hibernate.agroal.* |
| Default pool in | None of the big frameworks | Spring Boot | Quarkus |
| Default maximum size | 15 | 10 | None, maxSize is required |
| Wait for a connection | checkoutTimeout, 0 = no limit | connectionTimeout, 30 seconds | acquisitionTimeout, 0 = no limit |
| Close idle connections | maxIdleTime, off by default | idleTimeout, 10 minutes | reapTimeout, off by default |
| Maximum connection age | maxConnectionAge, off | maxLifetime, 30 minutes | maxLifetime, off |
| Connection testing | On checkout, check-in or a period; all off by default | Keepalive of idle connections every 2 minutes | On borrow, after idle time or a period; off by default |
| Leak detection | unreturnedConnectionTimeout (destroys the connection) | leakDetectionThreshold (logs a warning) | leakTimeout (reports the leak) |
| PreparedStatement cache | Yes, maxStatements | No, leaves it to the JDBC driver | No setting for it |
For a new Spring Boot application, we keep HikariCP, the default. The c3p0 pool remains a good fit for existing applications that already use it, or for plain Hibernate setups that need its statement cache or its testing options.
5. c3p0 FAQs
5.1. Do We Still Need hibernate.connection.provider_class?
No. Rule 3 in section 1.2 selects c3p0 when hibernate-c3p0 is the only pool module on the classpath. If several pool modules are present, any hibernate.c3p0.* setting selects c3p0. When we want to name the provider anyway, the short name works.
hibernate.connection.provider_class=c3p0
Old tutorials use org.hibernate.connection.C3P0ConnectionProvider. Hibernate 7.4 still accepts that name as an alias, but the class has moved to org.hibernate.c3p0.internal.C3P0ConnectionProvider.
5.2. What Replaced hibernate.c3p0.validate?
Nothing in current Hibernate. The setting hibernate.c3p0.validate was a Hibernate 2.x setting for testConnectionOnCheckout, so today we write the c3p0 name directly.
hibernate.c3p0.testConnectionOnCheckout=true
5.3. Why Does c3p0 Warn About the Initial Pool Size?
The warning appears when we configure ComboPooledDataSource ourselves, for example in Spring Boot, and set only min-pool-size.
WARN com.mchange.v2.resourcepool.BasicResourcePool - Bad pool size config, start 3 < min 5. Using 5 as start.
The c3p0 setting initialPoolSize defaults to 3, which is below our minimum of 5. Hibernate’s hibernate.c3p0.min_size sets both values, so plain Hibernate does not show it. In Spring Boot we set initial-pool-size to the same value as min-pool-size.
5.4. Do hibernate.c3p0 Settings Work in Spring Boot’s application.properties?
No. Settings under spring.jpa.properties.hibernate.c3p0.* reach Hibernate, but Spring Boot also passes its DataSource, and rule 2 in section 1.2 then picks DataSourceConnectionProvider. For example, given a ComboPooledDataSource with maxPoolSize 20 and hibernate.c3p0.max_size 99, Hibernate uses the DataSource, and the pool stays at 20. We configure the pool on the DataSource bean instead, as in section 3.
5.5. Does hibernate.connection.pool_size Work With c3p0?
Yes, as a fallback, because when hibernate.c3p0.max_size is missing, C3P0ConnectionProvider uses hibernate.connection.pool_size as the maximum pool size. The setting hibernate.c3p0.max_size wins when both are set.
5.6. Is c3p0 Still Maintained?
Yes, c3p0 0.14.2 was released in September 2026, and the 0.12 to 0.14 releases added security hardening. The project also publishes a c3p0-loom artifact that uses Java 21 virtual threads.
6. Conclusion
The c3p0 pool replaces the built-in pool of Hibernate with a production pool. We add hibernate-c3p0 and c3p0 0.14.2 and size the pool with min_size, max_size and acquire_increment. After that, we close idle connections with timeout and test connections on check-in or checkout. The startup log and PooledDataSource statistics confirm the settings. In Spring Boot, the pool is a DataSource bean, and HikariCP stays the default unless we replace it.
7. References
- Hibernate ORM 7.4 User Guide: Using c3p0
- Hibernate ORM 7.4 User Guide: ConnectionProvider
- c3p0 documentation: configuration properties
- c3p0 on Maven Central
- Spring Boot: Supported Connection Pools
- HikariCP README
- Agroal documentation
Happy Learning !!
This is not entirely correct. At least Hibernate 5, hibernate-c3p0 already contains the c3po library, there is no need to add it as a separate library. I can confirm this in maven in Eclipse.
Second, my company use a c3p0-config.xml but I am not sure if it is being used. I don’t know if I need to move those properties into the hibernate.cfg.xml that we are using.
Hi
I have configured c3p0 with Hibernate. It works fine till connections get exhausted to max limit. Then it stops providing responses. So, practically, my web app does not receive any data. No errors in console.
Tried with “org.hibernate.service.jdbc.connections.internal.C3P0ConnectionProvider” also.
My sessionFactory creation class is as below:
public class DBConfiguration { private static SessionFactory sessionFactory = setSessionFactory(); private static SessionFactory setSessionFactory() { Configuration configuration = null; configuration = new Configuration().configure("hibernate.cfg.xml"); StandardServiceRegistryBuilder builder = new StandardServiceRegistryBuilder().applySettings(configuration.getProperties()); return configuration.buildSessionFactory(builder.build()); } public static SessionFactory getSessionFactory() { if(sessionFactory == null) { sessionFactory = setSessionFactory(); } return sessionFactory; } public static void closeSessionFactory() { sessionFactory.close(); } }I have done
session.flush()andsession.close()after every DB queries.It seems that the connection is not released from the pool for another client to use. Hence after the max number of connections is reached, it is waiting for one to be released, which is not happening.
I tried with 50 as max limit, it stays for longer than stops.
Am I missing something?
Please share your persistence.xml/hibernate-cfg.xml after masking any sensitive data.
Is it possible to modify all properties at runtime? For instance, if I encounter that the value of maximum connections needs to be increased, can I do this without re-starting the CP?
sir if am creating a connection pool in tomcat with c3p0 then how to use it in hibernate
As mentioned in post, all you need to do is define any property in hibernate config file starting with “hibernate.c3p0.”. Then hibernate will take care of everything in backend.