It does not replace JDBC. It rather manages connections. Creating a connection to the database is a time expensive operation. From the application's point of view, individual connections are mostly interchangeble when in a "neutral" state (no open cursors, transactions, custom variables, locale, whatever the database may set for a connection).
Therefore it's better to open several connections and re-use them.
I guess you know this.
Now, C3P0 uses the JDBC API to get the connection when requested by the application. The application typically has some other API to ask the pool for a connection. There are certain requirements for the connection, defined by the API. C3P0, as mentioned in the other answer, implements DataSource
API, but I believe it has adapters for other commonly used interfaces, too.
The returned JDBC connection is typically a wrapper object which delegates the JDBC API calls to the underlying database's JDBC driver.
An stacktrace is worth thousand words:
! at org.postgresql.core.v3.QueryExecutorImpl.readStartupMessages(QueryExecutorImpl.java:2566)
! at org.postgresql.core.v3.QueryExecutorImpl.<init>(QueryExecutorImpl.java:131)
! at org.postgresql.core.v3.ConnectionFactoryImpl.openConnectionImpl(ConnectionFactoryImpl.java:210)
! at org.postgresql.core.ConnectionFactory.openConnection(ConnectionFactory.java:49)
! at org.postgresql.jdbc.PgConnection.<init>(PgConnection.java:195)
! at org.postgresql.Driver.makeConnection(Driver.java:452)
! at org.postgresql.Driver.connect(Driver.java:254)
! at com.mchange.v2.c3p0.DriverManagerDataSource.getConnection(DriverManagerDataSource.java:175)
! at com.mchange.v2.c3p0.WrapperConnectionPoolDataSource.getPooledConnection(WrapperConnectionPoolDataSource.java:220)
! at com.mchange.v2.c3p0.WrapperConnectionPoolDataSource.getPooledConnection(WrapperConnectionPoolDataSource.java:206)
! at com.mchange.v2.c3p0.impl.C3P0PooledConnectionPool$1PooledConnectionResourcePoolManager.acquireResource(C3P0PooledConnectionPool.java:203)
! at com.mchange.v2.resourcepool.BasicResourcePool.doAcquire(BasicResourcePool.java:1138)
! at com.mchange.v2.resourcepool.BasicResourcePool.doAcquireAndDecrementPendingAcquiresWithinLockOnSuccess(BasicResourcePool.java:1125)
! at com.mchange.v2.resourcepool.BasicResourcePool.access$700(BasicResourcePool.java:44)
! at com.mchange.v2.resourcepool.BasicResourcePool$ScatteredAcquireTask.run(BasicResourcePool.java:1870)
! at com.mchange.v2.async.ThreadPoolAsynchronousRunner$PoolThread.run(ThreadPoolAsynchronousRunner.java:696)