You are viewing a plain text version of this content. The canonical link for it is here.
Posted to dev@tomcat.apache.org by ma...@apache.org on 2021/09/02 15:11:19 UTC

[tomcat] 02/02: Update internal fork of Commons DBCP to 2.9.0 (2021-08-03)

This is an automated email from the ASF dual-hosted git repository.

markt pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/tomcat.git

commit d2e8a3b4c4046ef84fdc18475a652e3027854232
Author: Mark Thomas <ma...@apache.org>
AuthorDate: Thu Sep 2 16:00:10 2021 +0100

    Update internal fork of Commons DBCP to 2.9.0 (2021-08-03)
---
 MERGE.txt                                          |    2 +-
 .../apache/tomcat/dbcp/dbcp2/AbandonedTrace.java   |   33 +-
 .../apache/tomcat/dbcp/dbcp2/BasicDataSource.java  |  643 +++++++----
 .../tomcat/dbcp/dbcp2/BasicDataSourceFactory.java  |  521 +++------
 .../tomcat/dbcp/dbcp2/BasicDataSourceMXBean.java   |  330 +-----
 .../dbcp/dbcp2/ConnectionFactoryFactory.java       |    2 +-
 java/org/apache/tomcat/dbcp/dbcp2/Constants.java   |   16 +-
 .../dbcp/dbcp2/DataSourceConnectionFactory.java    |    2 +-
 ...DataSourceMXBean.java => DataSourceMXBean.java} |  284 +++--
 .../tomcat/dbcp/dbcp2/DelegatingConnection.java    |  866 +++++++-------
 .../dbcp/dbcp2/DelegatingDatabaseMetaData.java     |   19 +-
 .../tomcat/dbcp/dbcp2/DelegatingResultSet.java     |   24 +-
 .../tomcat/dbcp/dbcp2/DelegatingStatement.java     |   19 +-
 .../org/apache/tomcat/dbcp/dbcp2/Jdbc41Bridge.java |  136 +--
 java/org/apache/tomcat/dbcp/dbcp2/PStmtKey.java    |    4 +-
 .../dbcp/dbcp2/PoolableCallableStatement.java      |   30 +-
 .../tomcat/dbcp/dbcp2/PoolableConnection.java      |  283 +++--
 .../dbcp/dbcp2/PoolableConnectionFactory.java      |  171 ++-
 .../dbcp/dbcp2/PoolableConnectionMXBean.java       |   44 +-
 .../dbcp/dbcp2/PoolablePreparedStatement.java      |   20 +-
 .../tomcat/dbcp/dbcp2/PoolingConnection.java       |   68 +-
 .../tomcat/dbcp/dbcp2/PoolingDataSource.java       |  183 +--
 .../apache/tomcat/dbcp/dbcp2/PoolingDriver.java    |  225 ++--
 java/org/apache/tomcat/dbcp/dbcp2/Utils.java       |   52 +-
 .../dbcp/dbcp2/cpdsadapter/ConnectionImpl.java     |  106 +-
 .../dbcp/dbcp2/cpdsadapter/DriverAdapterCPDS.java  |  193 +++-
 .../dbcp2/cpdsadapter/PooledConnectionImpl.java    |  110 +-
 .../dbcp2/datasources/CPDSConnectionFactory.java   |  426 ++++---
 .../tomcat/dbcp/dbcp2/datasources/CharArray.java   |   88 ++
 .../dbcp2/datasources/InstanceKeyDataSource.java   | 1220 ++++++++++++--------
 .../datasources/InstanceKeyDataSourceFactory.java  |  226 ++--
 .../datasources/KeyedCPDSConnectionFactory.java    |  335 +++---
 .../dbcp2/datasources/PerUserPoolDataSource.java   |  343 ++++--
 .../datasources/PerUserPoolDataSourceFactory.java  |   65 +-
 .../tomcat/dbcp/dbcp2/datasources/PoolKey.java     |    2 +-
 .../dbcp2/datasources/PooledConnectionAndInfo.java |   55 +-
 .../dbcp2/datasources/PooledConnectionManager.java |   30 +-
 .../dbcp2/datasources/SharedPoolDataSource.java    |  104 +-
 .../datasources/SharedPoolDataSourceFactory.java   |   10 +-
 .../tomcat/dbcp/dbcp2/datasources/UserPassKey.java |   53 +-
 .../dbcp/dbcp2/managed/BasicManagedDataSource.java |  227 ++--
 .../managed/DataSourceXAConnectionFactory.java     |   45 +-
 .../dbcp2/managed/LocalXAConnectionFactory.java    |   39 +-
 .../dbcp/dbcp2/managed/ManagedConnection.java      |   36 +-
 .../dbcp/dbcp2/managed/ManagedDataSource.java      |   29 +-
 .../managed/PoolableManagedConnectionFactory.java  |    3 +-
 .../dbcp/dbcp2/managed/TransactionContext.java     |  181 ++-
 .../dbcp2/managed/TransactionContextListener.java  |   29 +-
 .../dbcp/dbcp2/managed/TransactionRegistry.java    |  134 ++-
 .../dbcp/dbcp2/managed/XAConnectionFactory.java    |   45 +-
 webapps/docs/changelog.xml                         |    4 +
 51 files changed, 4376 insertions(+), 3739 deletions(-)

diff --git a/MERGE.txt b/MERGE.txt
index 4682268..e1403d4 100644
--- a/MERGE.txt
+++ b/MERGE.txt
@@ -69,4 +69,4 @@ Sub-tree
 src/main/java/org/apache/commons/dbcp2
 src/main/resources/org/apache/commons/dbcp2
 The SHA1 ID / tag for the most recent commit to be merged to Tomcat is:
-e24196a95bbbc531eb3c5f1b19e1dc42fd78a783 (2021-01-15)
+rel/commons-dbcp-2.9.0 (2021-08-03)
diff --git a/java/org/apache/tomcat/dbcp/dbcp2/AbandonedTrace.java b/java/org/apache/tomcat/dbcp/dbcp2/AbandonedTrace.java
index 671e3e6..c107580 100644
--- a/java/org/apache/tomcat/dbcp/dbcp2/AbandonedTrace.java
+++ b/java/org/apache/tomcat/dbcp/dbcp2/AbandonedTrace.java
@@ -17,6 +17,7 @@
 package org.apache.tomcat.dbcp.dbcp2;
 
 import java.lang.ref.WeakReference;
+import java.time.Instant;
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.Iterator;
@@ -25,7 +26,7 @@ import java.util.List;
 import org.apache.tomcat.dbcp.pool2.TrackedUse;
 
 /**
- * Tracks db connection usage for recovering and reporting abandoned db connections.
+ * Tracks connection usage for recovering and reporting abandoned connections.
  * <p>
  * The JDBC Connection, Statement, and ResultSet classes extend this class.
  * </p>
@@ -38,7 +39,7 @@ public class AbandonedTrace implements TrackedUse {
     private final List<WeakReference<AbandonedTrace>> traceList = new ArrayList<>();
 
     /** Last time this connection was used. */
-    private volatile long lastUsedMillis = 0;
+    private volatile Instant lastUsedInstant = Instant.EPOCH;
 
     /**
      * Creates a new AbandonedTrace without config and without doing abandoned tracing.
@@ -85,8 +86,14 @@ public class AbandonedTrace implements TrackedUse {
      * @return long time in milliseconds.
      */
     @Override
+    @Deprecated
     public long getLastUsed() {
-        return lastUsedMillis;
+        return lastUsedInstant.toEpochMilli();
+    }
+
+    @Override
+    public Instant getLastUsedInstant() {
+        return lastUsedInstant;
     }
 
     /**
@@ -153,7 +160,8 @@ public class AbandonedTrace implements TrackedUse {
                 if (trace != null && trace.equals(traceInList)) {
                     iter.remove();
                     break;
-                } else if (traceInList == null) {
+                }
+                if (traceInList == null) {
                     // Clean-up since we are here anyway
                     iter.remove();
                 }
@@ -165,7 +173,18 @@ public class AbandonedTrace implements TrackedUse {
      * Sets the time this object was last used to the current time in milliseconds.
      */
     protected void setLastUsed() {
-        lastUsedMillis = System.currentTimeMillis();
+        lastUsedInstant = Instant.now();
+    }
+
+    /**
+     * Sets the instant this object was last used.
+     *
+     * @param lastUsedInstant
+     *            instant.
+     * @since 2.10.0
+     */
+    protected void setLastUsed(final Instant lastUsedInstant) {
+        this.lastUsedInstant = lastUsedInstant;
     }
 
     /**
@@ -173,8 +192,10 @@ public class AbandonedTrace implements TrackedUse {
      *
      * @param lastUsedMillis
      *            time in milliseconds.
+     * @deprecated Use {@link #setLastUsed(Instant)}
      */
+    @Deprecated
     protected void setLastUsed(final long lastUsedMillis) {
-        this.lastUsedMillis = lastUsedMillis;
+        this.lastUsedInstant = Instant.ofEpochMilli(lastUsedMillis);
     }
 }
diff --git a/java/org/apache/tomcat/dbcp/dbcp2/BasicDataSource.java b/java/org/apache/tomcat/dbcp/dbcp2/BasicDataSource.java
index 08762f0..f260a53 100644
--- a/java/org/apache/tomcat/dbcp/dbcp2/BasicDataSource.java
+++ b/java/org/apache/tomcat/dbcp/dbcp2/BasicDataSource.java
@@ -27,6 +27,7 @@ import java.sql.Driver;
 import java.sql.DriverManager;
 import java.sql.SQLException;
 import java.sql.SQLFeatureNotSupportedException;
+import java.time.Duration;
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.Collections;
@@ -40,7 +41,9 @@ import java.util.logging.Logger;
 import javax.management.MBeanRegistration;
 import javax.management.MBeanServer;
 import javax.management.MalformedObjectNameException;
+import javax.management.NotCompliantMBeanException;
 import javax.management.ObjectName;
+import javax.management.StandardMBean;
 import javax.sql.DataSource;
 
 import org.apache.juli.logging.Log;
@@ -53,10 +56,11 @@ import org.apache.tomcat.dbcp.pool2.impl.GenericObjectPool;
 import org.apache.tomcat.dbcp.pool2.impl.GenericObjectPoolConfig;
 
 /**
+ * Basic implementation of <code>javax.sql.DataSource</code> that is configured via JavaBeans properties.
+ *
  * <p>
- * Basic implementation of <code>javax.sql.DataSource</code> that is configured via JavaBeans properties. This is not
- * the only way to combine the <em>commons-dbcp2</em> and <em>commons-pool2</em> packages, but provides a "one stop
- * shopping" solution for basic requirements.
+ * This is not the only way to combine the <em>commons-dbcp2</em> and <em>commons-pool2</em> packages, but provides a
+ * one-stop solution for basic requirements.
  * </p>
  *
  * @since 2.0
@@ -73,7 +77,7 @@ public class BasicDataSource implements DataSource, BasicDataSourceMXBean, MBean
             // A number of classes are loaded when getConnection() is called
             // but the following classes are not loaded and therefore require
             // explicit loading.
-            if (Utils.IS_SECURITY_ENABLED) {
+            if (Utils.isSecurityEnabled()) {
                 final ClassLoader loader = BasicDataSource.class.getClassLoader();
                 final String dbcpPackageName = BasicDataSource.class.getPackage().getName();
                 loader.loadClass(dbcpPackageName + ".DelegatingCallableStatement");
@@ -94,8 +98,13 @@ public class BasicDataSource implements DataSource, BasicDataSourceMXBean, MBean
         }
     }
 
-    protected static void validateConnectionFactory(final PoolableConnectionFactory connectionFactory)
-            throws Exception {
+    /**
+     * Validates the given factory.
+     *
+     * @param connectionFactory the factory
+     * @throws Exception Thrown by one of the factory methods while managing a temporary pooled object.
+     */
+    protected static void validateConnectionFactory(final PoolableConnectionFactory connectionFactory) throws Exception {
         PoolableConnection conn = null;
         PooledObject<PoolableConnection> p = null;
         try {
@@ -126,7 +135,7 @@ public class BasicDataSource implements DataSource, BasicDataSourceMXBean, MBean
      */
     private volatile int defaultTransactionIsolation = PoolableConnectionFactory.UNKNOWN_TRANSACTION_ISOLATION;
 
-    private Integer defaultQueryTimeoutSeconds;
+    private Duration defaultQueryTimeoutDuration;
 
     /**
      * The default "catalog" of connections created by this pool.
@@ -187,28 +196,28 @@ public class BasicDataSource implements DataSource, BasicDataSourceMXBean, MBean
      * The minimum number of active connections that can remain idle in the pool, without extra ones being created when
      * the evictor runs, or 0 to create none. The pool attempts to ensure that minIdle connections are available when
      * the idle object evictor runs. The value of this property has no effect unless
-     * {@link #timeBetweenEvictionRunsMillis} has a positive value.
+     * {@link #durationBetweenEvictionRuns} has a positive value.
      */
     private int minIdle = GenericObjectPoolConfig.DEFAULT_MIN_IDLE;
 
     /**
      * The initial number of connections that are created when the pool is started.
      */
-    private int initialSize = 0;
+    private int initialSize;
 
     /**
-     * The maximum number of milliseconds that the pool will wait (when there are no available connections) for a
+     * The maximum Duration that the pool will wait (when there are no available connections) for a
      * connection to be returned before throwing an exception, or <= 0 to wait indefinitely.
      */
-    private long maxWaitMillis = BaseObjectPoolConfig.DEFAULT_MAX_WAIT_MILLIS;
+    private Duration maxWaitDuration = BaseObjectPoolConfig.DEFAULT_MAX_WAIT;
 
     /**
      * Prepared statement pooling for this pool. When this property is set to <code>true</code> both PreparedStatements
      * and CallableStatements are pooled.
      */
-    private boolean poolPreparedStatements = false;
+    private boolean poolPreparedStatements;
 
-    private boolean clearStatementPoolOnReturn = false;
+    private boolean clearStatementPoolOnReturn;
 
     /**
      * <p>
@@ -229,7 +238,7 @@ public class BasicDataSource implements DataSource, BasicDataSourceMXBean, MBean
      * The indication of whether objects will be validated as soon as they have been created by the pool. If the object
      * fails to validate, the borrow operation that triggered the creation will fail.
      */
-    private boolean testOnCreate = false;
+    private boolean testOnCreate;
 
     /**
      * The indication of whether objects will be validated before being borrowed from the pool. If the object fails to
@@ -240,13 +249,13 @@ public class BasicDataSource implements DataSource, BasicDataSourceMXBean, MBean
     /**
      * The indication of whether objects will be validated before being returned to the pool.
      */
-    private boolean testOnReturn = false;
+    private boolean testOnReturn;
 
     /**
      * The number of milliseconds to sleep between runs of the idle object evictor thread. When non-positive, no idle
      * object evictor thread will be run.
      */
-    private long timeBetweenEvictionRunsMillis = BaseObjectPoolConfig.DEFAULT_TIME_BETWEEN_EVICTION_RUNS_MILLIS;
+    private Duration durationBetweenEvictionRuns = BaseObjectPoolConfig.DEFAULT_TIME_BETWEEN_EVICTION_RUNS;
 
     /**
      * The number of objects to examine during each run of the idle object evictor thread (if any).
@@ -257,15 +266,15 @@ public class BasicDataSource implements DataSource, BasicDataSourceMXBean, MBean
      * The minimum amount of time an object may sit idle in the pool before it is eligible for eviction by the idle
      * object evictor (if any).
      */
-    private long minEvictableIdleTimeMillis = BaseObjectPoolConfig.DEFAULT_MIN_EVICTABLE_IDLE_TIME_MILLIS;
+    private Duration minEvictableIdleDuration = BaseObjectPoolConfig.DEFAULT_MIN_EVICTABLE_IDLE_DURATION;
 
     /**
      * The minimum amount of time a connection may sit idle in the pool before it is eligible for eviction by the idle
      * object evictor, with the extra condition that at least "minIdle" connections remain in the pool. Note that
      * {@code minEvictableIdleTimeMillis} takes precedence over this parameter. See
-     * {@link #getSoftMinEvictableIdleTimeMillis()}.
+     * {@link #getSoftMinEvictableIdleDuration()}.
      */
-    private long softMinEvictableIdleTimeMillis = BaseObjectPoolConfig.DEFAULT_SOFT_MIN_EVICTABLE_IDLE_TIME_MILLIS;
+    private Duration softMinEvictableIdleDuration = BaseObjectPoolConfig.DEFAULT_SOFT_MIN_EVICTABLE_IDLE_DURATION;
 
     private String evictionPolicyClassName = BaseObjectPoolConfig.DEFAULT_EVICTION_POLICY_CLASS_NAME;
 
@@ -273,7 +282,7 @@ public class BasicDataSource implements DataSource, BasicDataSourceMXBean, MBean
      * The indication of whether objects will be validated by the idle object evictor (if any). If an object fails to
      * validate, it will be dropped from the pool.
      */
-    private boolean testWhileIdle = false;
+    private boolean testWhileIdle;
 
     /**
      * The connection password to be passed to our JDBC driver to establish a connection.
@@ -300,7 +309,7 @@ public class BasicDataSource implements DataSource, BasicDataSourceMXBean, MBean
     /**
      * Timeout in seconds before connection validation queries fail.
      */
-    private volatile int validationQueryTimeoutSeconds = -1;
+    private volatile Duration validationQueryTimeoutDuration = Duration.ofSeconds(-1);
 
     /**
      * The fully qualified Java class name of a {@link ConnectionFactory} implementation.
@@ -319,9 +328,9 @@ public class BasicDataSource implements DataSource, BasicDataSourceMXBean, MBean
     /**
      * Controls access to the underlying connection.
      */
-    private boolean accessToUnderlyingConnectionAllowed = false;
+    private boolean accessToUnderlyingConnectionAllowed;
 
-    private long maxConnLifetimeMillis = -1;
+    private Duration maxConnDuration = Duration.ofMillis(-1);
 
     private boolean logExpiredConnections = true;
 
@@ -381,10 +390,8 @@ public class BasicDataSource implements DataSource, BasicDataSourceMXBean, MBean
     }
 
     /**
-     * <p>
      * Closes and releases all idle connections that are currently stored in the connection pool associated with this
      * data source.
-     * </p>
      * <p>
      * Connections that are checked out to clients when this method is invoked are not affected. When client
      * applications subsequently invoke {@link Connection#close()} to return these connections to the pool, the
@@ -428,25 +435,19 @@ public class BasicDataSource implements DataSource, BasicDataSourceMXBean, MBean
     private void closeConnectionPool() {
         final GenericObjectPool<?> oldPool = connectionPool;
         connectionPool = null;
-        try {
-            if (oldPool != null) {
-                oldPool.close();
-            }
-        } catch (final Exception e) {
-            /* Ignore */
-        }
+        Utils.closeQuietly(oldPool);
     }
 
     /**
      * Creates a JDBC connection factory for this data source. The JDBC driver is loaded using the following algorithm:
      * <ol>
      * <li>If a Driver instance has been specified via {@link #setDriver(Driver)} use it</li>
-     * <li>If no Driver instance was specified and {@link #driverClassName} is specified that class is loaded using the
-     * {@link ClassLoader} of this class or, if {@link #driverClassLoader} is set, {@link #driverClassName} is loaded
+     * <li>If no Driver instance was specified and {code driverClassName} is specified that class is loaded using the
+     * {@link ClassLoader} of this class or, if {code driverClassLoader} is set, {code driverClassName} is loaded
      * with the specified {@link ClassLoader}.</li>
-     * <li>If {@link #driverClassName} is specified and the previous attempt fails, the class is loaded using the
+     * <li>If {code driverClassName} is specified and the previous attempt fails, the class is loaded using the
      * context class loader of the current thread.</li>
-     * <li>If a driver still isn't loaded one is loaded via the {@link DriverManager} using the specified {@link #url}.
+     * <li>If a driver still isn't loaded one is loaded via the {@link DriverManager} using the specified {code url}.
      * </ol>
      * <p>
      * This method exists so subclasses can replace the implementation class.
@@ -483,13 +484,13 @@ public class BasicDataSource implements DataSource, BasicDataSourceMXBean, MBean
         gop.setMaxTotal(maxTotal);
         gop.setMaxIdle(maxIdle);
         gop.setMinIdle(minIdle);
-        gop.setMaxWaitMillis(maxWaitMillis);
+        gop.setMaxWait(maxWaitDuration);
         gop.setTestOnCreate(testOnCreate);
         gop.setTestOnBorrow(testOnBorrow);
         gop.setTestOnReturn(testOnReturn);
         gop.setNumTestsPerEvictionRun(numTestsPerEvictionRun);
-        gop.setMinEvictableIdleTimeMillis(minEvictableIdleTimeMillis);
-        gop.setSoftMinEvictableIdleTimeMillis(softMinEvictableIdleTimeMillis);
+        gop.setMinEvictableIdle(minEvictableIdleDuration);
+        gop.setSoftMinEvictableIdle(softMinEvictableIdleDuration);
         gop.setTestWhileIdle(testWhileIdle);
         gop.setLifo(lifo);
         gop.setSwallowedExceptionListener(new SwallowedExceptionLogger(log, logExpiredConnections));
@@ -499,9 +500,7 @@ public class BasicDataSource implements DataSource, BasicDataSourceMXBean, MBean
     }
 
     /**
-     * <p>
      * Creates (if necessary) and return the internal data source we are using to manage our connections.
-     * </p>
      *
      * @return The current internal DataSource or a newly created instance if it has not yet been created.
      * @throws SQLException if the object pool cannot be created.
@@ -628,10 +627,9 @@ public class BasicDataSource implements DataSource, BasicDataSourceMXBean, MBean
             throws SQLException {
         PoolableConnectionFactory connectionFactory = null;
         try {
-            connectionFactory = new PoolableConnectionFactory(driverConnectionFactory,
-                    ObjectNameWrapper.unwrap(registeredJmxObjectName));
+            connectionFactory = new PoolableConnectionFactory(driverConnectionFactory, ObjectNameWrapper.unwrap(registeredJmxObjectName));
             connectionFactory.setValidationQuery(validationQuery);
-            connectionFactory.setValidationQueryTimeout(validationQueryTimeoutSeconds);
+            connectionFactory.setValidationQueryTimeout(validationQueryTimeoutDuration);
             connectionFactory.setConnectionInitSql(connectionInitSqls);
             connectionFactory.setDefaultReadOnly(defaultReadOnly);
             connectionFactory.setDefaultAutoCommit(defaultAutoCommit);
@@ -642,10 +640,10 @@ public class BasicDataSource implements DataSource, BasicDataSourceMXBean, MBean
             connectionFactory.setPoolStatements(poolPreparedStatements);
             connectionFactory.setClearStatementPoolOnReturn(clearStatementPoolOnReturn);
             connectionFactory.setMaxOpenPreparedStatements(maxOpenPreparedStatements);
-            connectionFactory.setMaxConnLifetimeMillis(maxConnLifetimeMillis);
+            connectionFactory.setMaxConn(maxConnDuration);
             connectionFactory.setRollbackOnReturn(getRollbackOnReturn());
             connectionFactory.setAutoCommitOnReturn(getAutoCommitOnReturn());
-            connectionFactory.setDefaultQueryTimeout(getDefaultQueryTimeout());
+            connectionFactory.setDefaultQueryTimeout(getDefaultQueryTimeoutDuration());
             connectionFactory.setFastFailValidation(fastFailValidation);
             connectionFactory.setDisconnectionSqlCodes(disconnectionSqlCodes);
             validateConnectionFactory(connectionFactory);
@@ -690,7 +688,7 @@ public class BasicDataSource implements DataSource, BasicDataSourceMXBean, MBean
     }
 
     /**
-     * Returns the value of the flag that controls whether or not connections being returned to the pool will be checked
+     * Gets the value of the flag that controls whether or not connections being returned to the pool will be checked
      * and configured with {@link Connection#setAutoCommit(boolean) Connection.setAutoCommit(true)} if the auto commit
      * setting is {@code false} when the connection is returned. It is <code>true</code> by default.
      *
@@ -701,7 +699,7 @@ public class BasicDataSource implements DataSource, BasicDataSourceMXBean, MBean
     }
 
     /**
-     * Returns the state caching flag.
+     * Gets the state caching flag.
      *
      * @return the state caching flag
      */
@@ -718,7 +716,7 @@ public class BasicDataSource implements DataSource, BasicDataSourceMXBean, MBean
      */
     @Override
     public Connection getConnection() throws SQLException {
-        if (Utils.IS_SECURITY_ENABLED) {
+        if (Utils.isSecurityEnabled()) {
             final PrivilegedExceptionAction<Connection> action = () -> createDataSource().getConnection();
             try {
                 return AccessController.doPrivileged(action);
@@ -751,7 +749,7 @@ public class BasicDataSource implements DataSource, BasicDataSourceMXBean, MBean
     }
 
     /**
-     * Returns the ConnectionFactoryClassName that has been configured for use by this pool.
+     * Gets the ConnectionFactoryClassName that has been configured for use by this pool.
      * <p>
      * Note: This getter only returns the last value set by a call to {@link #setConnectionFactoryClassName(String)}.
      * </p>
@@ -764,7 +762,7 @@ public class BasicDataSource implements DataSource, BasicDataSourceMXBean, MBean
     }
 
     /**
-     * Returns the list of SQL statements executed when a physical connection is first created. Returns an empty list if
+     * Gets the list of SQL statements executed when a physical connection is first created. Returns an empty list if
      * there are no initialization statements configured.
      *
      * @return initialization SQL statements
@@ -782,6 +780,11 @@ public class BasicDataSource implements DataSource, BasicDataSourceMXBean, MBean
         return getConnectionInitSqls().toArray(Utils.EMPTY_STRING_ARRAY);
     }
 
+    /**
+     * Gets the underlying connection pool.
+     *
+     * @return the underlying connection pool.
+     */
     protected GenericObjectPool<PoolableConnection> getConnectionPool() {
         return connectionPool;
     }
@@ -791,7 +794,7 @@ public class BasicDataSource implements DataSource, BasicDataSourceMXBean, MBean
     }
 
     /**
-     * Returns the default auto-commit property.
+     * Gets the default auto-commit property.
      *
      * @return true if default auto-commit is enabled
      */
@@ -801,7 +804,7 @@ public class BasicDataSource implements DataSource, BasicDataSourceMXBean, MBean
     }
 
     /**
-     * Returns the default catalog.
+     * Gets the default catalog.
      *
      * @return the default catalog
      */
@@ -815,13 +818,26 @@ public class BasicDataSource implements DataSource, BasicDataSourceMXBean, MBean
      * connection. <code>null</code> means that the driver default will be used.
      *
      * @return The default query timeout in seconds.
+     * @deprecated Use {@link #getDefaultQueryTimeoutDuration()}.
      */
+    @Deprecated
     public Integer getDefaultQueryTimeout() {
-        return defaultQueryTimeoutSeconds;
+        return defaultQueryTimeoutDuration == null ? null : Integer.valueOf((int) defaultQueryTimeoutDuration.getSeconds());
     }
 
     /**
-     * Returns the default readOnly property.
+     * Gets the default query timeout that will be used for {@link java.sql.Statement Statement}s created from this
+     * connection. <code>null</code> means that the driver default will be used.
+     *
+     * @return The default query timeout Duration.
+     * @since 2.10.0
+     */
+    public Duration getDefaultQueryTimeoutDuration() {
+        return defaultQueryTimeoutDuration;
+    }
+
+    /**
+     * Gets the default readOnly property.
      *
      * @return true if connections are readOnly by default
      */
@@ -831,7 +847,7 @@ public class BasicDataSource implements DataSource, BasicDataSourceMXBean, MBean
     }
 
     /**
-     * Returns the default schema.
+     * Gets the default schema.
      *
      * @return the default schema.
      * @since 2.5.0
@@ -842,7 +858,7 @@ public class BasicDataSource implements DataSource, BasicDataSourceMXBean, MBean
     }
 
     /**
-     * Returns the default transaction isolation state of returned connections.
+     * Gets the default transaction isolation state of returned connections.
      *
      * @return the default value for transaction isolation state
      * @see Connection#getTransactionIsolation
@@ -853,7 +869,7 @@ public class BasicDataSource implements DataSource, BasicDataSourceMXBean, MBean
     }
 
     /**
-     * Returns the set of SQL_STATE codes considered to signal fatal conditions.
+     * Gets the set of SQL_STATE codes considered to signal fatal conditions.
      *
      * @return fatal disconnection state codes
      * @see #setDisconnectionSqlCodes(Collection)
@@ -875,7 +891,7 @@ public class BasicDataSource implements DataSource, BasicDataSourceMXBean, MBean
     }
 
     /**
-     * Returns the JDBC Driver that has been configured for use by this pool.
+     * Gets the JDBC Driver that has been configured for use by this pool.
      * <p>
      * Note: This getter only returns the last value set by a call to {@link #setDriver(Driver)}. It does not return any
      * driver instance that may have been created from the value set via {@link #setDriverClassName(String)}.
@@ -888,7 +904,7 @@ public class BasicDataSource implements DataSource, BasicDataSourceMXBean, MBean
     }
 
     /**
-     * Returns the class loader specified for loading the JDBC driver. Returns <code>null</code> if no class loader has
+     * Gets the class loader specified for loading the JDBC driver. Returns <code>null</code> if no class loader has
      * been explicitly specified.
      * <p>
      * Note: This getter only returns the last value set by a call to {@link #setDriverClassLoader(ClassLoader)}. It
@@ -902,7 +918,7 @@ public class BasicDataSource implements DataSource, BasicDataSourceMXBean, MBean
     }
 
     /**
-     * Returns the JDBC driver class name.
+     * Gets the JDBC driver class name.
      * <p>
      * Note: This getter only returns the last value set by a call to {@link #setDriverClassName(String)}. It does not
      * return the class name of any driver that may have been set via {@link #setDriver(Driver)}.
@@ -916,7 +932,7 @@ public class BasicDataSource implements DataSource, BasicDataSourceMXBean, MBean
     }
 
     /**
-     * Returns the value of the flag that controls whether or not connections being returned to the pool will be checked
+     * Gets the value of the flag that controls whether or not connections being returned to the pool will be checked
      * and configured with {@link Connection#setAutoCommit(boolean) Connection.setAutoCommit(true)} if the auto commit
      * setting is {@code false} when the connection is returned. It is <code>true</code> by default.
      *
@@ -951,7 +967,7 @@ public class BasicDataSource implements DataSource, BasicDataSourceMXBean, MBean
     }
 
     /**
-     * Returns the initial size of the connection pool.
+     * Gets the initial size of the connection pool.
      *
      * @return the number of connections created when the pool is initialized
      */
@@ -961,7 +977,7 @@ public class BasicDataSource implements DataSource, BasicDataSourceMXBean, MBean
     }
 
     /**
-     * Returns the JMX name that has been requested for this DataSource. If the requested name is not valid, an
+     * Gets the JMX name that has been requested for this DataSource. If the requested name is not valid, an
      * alternative may be chosen.
      *
      * @return The JMX name that has been requested for this DataSource.
@@ -971,7 +987,7 @@ public class BasicDataSource implements DataSource, BasicDataSourceMXBean, MBean
     }
 
     /**
-     * Returns the LIFO property.
+     * Gets the LIFO property.
      *
      * @return true if connection pool behaves as a LIFO queue.
      */
@@ -981,9 +997,7 @@ public class BasicDataSource implements DataSource, BasicDataSourceMXBean, MBean
     }
 
     /**
-     * <p>
      * Flag to log stack traces for application code which abandoned a Statement or Connection.
-     * </p>
      * <p>
      * Defaults to false.
      * </p>
@@ -998,7 +1012,7 @@ public class BasicDataSource implements DataSource, BasicDataSourceMXBean, MBean
     }
 
     /**
-     * When {@link #getMaxConnLifetimeMillis()} is set to limit connection lifetime, this property determines whether or
+     * When {@link #getMaxConnDuration()} is set to limit connection lifetime, this property determines whether or
      * not log messages are generated when the pool closes connections due to maximum lifetime exceeded.
      *
      * @since 2.1
@@ -1012,7 +1026,7 @@ public class BasicDataSource implements DataSource, BasicDataSourceMXBean, MBean
      * <strong>BasicDataSource does NOT support this method.</strong>
      *
      * <p>
-     * Returns the login timeout (in seconds) for connecting to the database.
+     * Gets the login timeout (in seconds) for connecting to the database.
      * </p>
      * <p>
      * Calls {@link #createDataSource()}, so has the side effect of initializing the connection pool.
@@ -1030,9 +1044,7 @@ public class BasicDataSource implements DataSource, BasicDataSourceMXBean, MBean
     }
 
     /**
-     * <p>
-     * Returns the log writer being used by this data source.
-     * </p>
+     * Gets the log writer being used by this data source.
      * <p>
      * Calls {@link #createDataSource()}, so has the side effect of initializing the connection pool.
      * </p>
@@ -1046,19 +1058,29 @@ public class BasicDataSource implements DataSource, BasicDataSourceMXBean, MBean
     }
 
     /**
-     * Returns the maximum permitted lifetime of a connection in milliseconds. A value of zero or less indicates an
+     * Gets the maximum permitted duration of a connection. A value of zero or less indicates an
+     * infinite lifetime.
+     * @return the maximum permitted duration of a connection.
+     * @since 2.10.0
+     */
+    public Duration getMaxConnDuration() {
+        return maxConnDuration;
+    }
+
+    /**
+     * Gets the maximum permitted lifetime of a connection in milliseconds. A value of zero or less indicates an
      * infinite lifetime.
+     * @deprecated Use {@link #getMaxConnDuration()}.
      */
     @Override
+    @Deprecated
     public long getMaxConnLifetimeMillis() {
-        return maxConnLifetimeMillis;
+        return maxConnDuration.toMillis();
     }
 
     /**
-     * <p>
-     * Returns the maximum number of connections that can remain idle in the pool. Excess idle connections are destroyed
+     * Gets the maximum number of connections that can remain idle in the pool. Excess idle connections are destroyed
      * on return to the pool.
-     * </p>
      * <p>
      * A negative value indicates that there is no limit
      * </p>
@@ -1081,9 +1103,7 @@ public class BasicDataSource implements DataSource, BasicDataSourceMXBean, MBean
     }
 
     /**
-     * <p>
-     * Returns the maximum number of active connections that can be allocated at the same time.
-     * </p>
+     * Gets the maximum number of active connections that can be allocated at the same time.
      * <p>
      * A negative number means that there is no limit.
      * </p>
@@ -1096,31 +1116,57 @@ public class BasicDataSource implements DataSource, BasicDataSourceMXBean, MBean
     }
 
     /**
-     * Returns the maximum number of milliseconds that the pool will wait for a connection to be returned before
+     * Gets the maximum Duration that the pool will wait for a connection to be returned before throwing an exception. A
+     * value less than or equal to zero means the pool is set to wait indefinitely.
+     *
+     * @return the maxWaitDuration property value.
+     * @since 2.10.0
+     */
+    public synchronized Duration getMaxWaitDuration() {
+        return this.maxWaitDuration;
+    }
+
+    /**
+     * Gets the maximum number of milliseconds that the pool will wait for a connection to be returned before
      * throwing an exception. A value less than or equal to zero means the pool is set to wait indefinitely.
      *
-     * @return the maxWaitMillis property value
+     * @return the maxWaitMillis property value.
+     * @deprecated Use {@link #getMaxWaitDuration()}.
      */
+    @Deprecated
     @Override
     public synchronized long getMaxWaitMillis() {
-        return this.maxWaitMillis;
+        return this.maxWaitDuration.toMillis();
     }
 
     /**
-     * Returns the {@link #minEvictableIdleTimeMillis} property.
+     * Gets the {code minEvictableIdleDuration} property.
      *
-     * @return the value of the {@link #minEvictableIdleTimeMillis} property
-     * @see #minEvictableIdleTimeMillis
+     * @return the value of the {code minEvictableIdleDuration} property
+     * @see #setMinEvictableIdle(Duration)
+     * @since 2.10.0
      */
+    public synchronized Duration getMinEvictableIdleDuration() {
+        return this.minEvictableIdleDuration;
+    }
+
+    /**
+     * Gets the {code minEvictableIdleDuration} property.
+     *
+     * @return the value of the {code minEvictableIdleDuration} property
+     * @see #setMinEvictableIdle(Duration)
+     * @deprecated Use {@link #getMinEvictableIdleDuration()}.
+     */
+    @Deprecated
     @Override
     public synchronized long getMinEvictableIdleTimeMillis() {
-        return this.minEvictableIdleTimeMillis;
+        return this.minEvictableIdleDuration.toMillis();
     }
 
     /**
-     * Returns the minimum number of idle connections in the pool. The pool attempts to ensure that minIdle connections
+     * Gets the minimum number of idle connections in the pool. The pool attempts to ensure that minIdle connections
      * are available when the idle object evictor runs. The value of this property has no effect unless
-     * {@link #timeBetweenEvictionRunsMillis} has a positive value.
+     * {code durationBetweenEvictionRuns} has a positive value.
      *
      * @return the minimum number of idle connections
      * @see GenericObjectPool#getMinIdle()
@@ -1155,10 +1201,10 @@ public class BasicDataSource implements DataSource, BasicDataSourceMXBean, MBean
     }
 
     /**
-     * Returns the value of the {@link #numTestsPerEvictionRun} property.
+     * Gets the value of the {code numTestsPerEvictionRun} property.
      *
      * @return the number of objects to examine during idle object evictor runs
-     * @see #numTestsPerEvictionRun
+     * @see #setNumTestsPerEvictionRun(int)
      */
     @Override
     public synchronized int getNumTestsPerEvictionRun() {
@@ -1171,7 +1217,7 @@ public class BasicDataSource implements DataSource, BasicDataSourceMXBean, MBean
     }
 
     /**
-     * Returns the password passed to the JDBC driver to establish connections.
+     * Gets the password passed to the JDBC driver to establish connections.
      *
      * @return the connection password
      */
@@ -1180,20 +1226,23 @@ public class BasicDataSource implements DataSource, BasicDataSourceMXBean, MBean
         return this.password;
     }
 
+    /**
+     * Gets the registered JMX ObjectName.
+     *
+     * @return the registered JMX ObjectName.
+     */
     protected ObjectName getRegisteredJmxName() {
         return ObjectNameWrapper.unwrap(registeredJmxObjectName);
     }
 
     /**
-     * <p>
      * Flag to remove abandoned connections if they exceed the removeAbandonedTimeout when borrowObject is invoked.
-     * </p>
      * <p>
      * The default value is false.
      * </p>
      * <p>
      * If set to true a connection is considered abandoned and eligible for removal if it has not been used for more
-     * than {@link #getRemoveAbandonedTimeout() removeAbandonedTimeout} seconds.
+     * than {@link #getRemoveAbandonedTimeoutDuration() removeAbandonedTimeout} seconds.
      * </p>
      * <p>
      * Abandoned connections are identified and removed when {@link #getConnection()} is invoked and all of the
@@ -1205,7 +1254,7 @@ public class BasicDataSource implements DataSource, BasicDataSourceMXBean, MBean
      * <li>{@link #getNumIdle()} &lt; 2</li>
      * </ul>
      *
-     * @see #getRemoveAbandonedTimeout()
+     * @see #getRemoveAbandonedTimeoutDuration()
      */
     @Override
     public boolean getRemoveAbandonedOnBorrow() {
@@ -1213,20 +1262,16 @@ public class BasicDataSource implements DataSource, BasicDataSourceMXBean, MBean
     }
 
     /**
-     * <p>
      * Flag to remove abandoned connections if they exceed the removeAbandonedTimeout during pool maintenance.
-     * </p>
-     *
      * <p>
      * The default value is false.
      * </p>
-     *
      * <p>
      * If set to true a connection is considered abandoned and eligible for removal if it has not been used for more
-     * than {@link #getRemoveAbandonedTimeout() removeAbandonedTimeout} seconds.
+     * than {@link #getRemoveAbandonedTimeoutDuration() removeAbandonedTimeout} seconds.
      * </p>
      *
-     * @see #getRemoveAbandonedTimeout()
+     * @see #getRemoveAbandonedTimeoutDuration()
      */
     @Override
     public boolean getRemoveAbandonedOnMaintenance() {
@@ -1234,9 +1279,7 @@ public class BasicDataSource implements DataSource, BasicDataSourceMXBean, MBean
     }
 
     /**
-     * <p>
-     * Timeout in seconds before an abandoned connection can be removed.
-     * </p>
+     * Gets the timeout in seconds before an abandoned connection can be removed.
      * <p>
      * Creating a Statement, PreparedStatement or CallableStatement or using one of these to execute a query (using one
      * of the execute methods) resets the lastUsed property of the parent connection.
@@ -1252,10 +1295,36 @@ public class BasicDataSource implements DataSource, BasicDataSourceMXBean, MBean
      * <p>
      * The default value is 300 seconds.
      * </p>
+     * @deprecated Use {@link #getRemoveAbandonedTimeoutDuration()}.
      */
+    @Deprecated
     @Override
     public int getRemoveAbandonedTimeout() {
-        return abandonedConfig == null ? 300 : abandonedConfig.getRemoveAbandonedTimeout();
+        return (int) getRemoveAbandonedTimeoutDuration().getSeconds();
+    }
+
+    /**
+     * Gets the timeout before an abandoned connection can be removed.
+     * <p>
+     * Creating a Statement, PreparedStatement or CallableStatement or using one of these to execute a query (using one
+     * of the execute methods) resets the lastUsed property of the parent connection.
+     * </p>
+     * <p>
+     * Abandoned connection cleanup happens when:
+     * </p>
+     * <ul>
+     * <li>{@link #getRemoveAbandonedOnBorrow()} or {@link #getRemoveAbandonedOnMaintenance()} = true</li>
+     * <li>{@link #getNumIdle() numIdle} &lt; 2</li>
+     * <li>{@link #getNumActive() numActive} &gt; {@link #getMaxTotal() maxTotal} - 3</li>
+     * </ul>
+     * <p>
+     * The default value is 300 seconds.
+     * </p>
+     * @return Timeout before an abandoned connection can be removed.
+     * @since 2.10.0
+     */
+    public Duration getRemoveAbandonedTimeoutDuration() {
+        return abandonedConfig == null ? Duration.ofSeconds(300) : abandonedConfig.getRemoveAbandonedTimeoutDuration();
     }
 
     /**
@@ -1269,11 +1338,27 @@ public class BasicDataSource implements DataSource, BasicDataSourceMXBean, MBean
     }
 
     /**
-     * <p>
-     * Returns the minimum amount of time a connection may sit idle in the pool before it is eligible for eviction by
+     * Gets the minimum amount of time a connection may sit idle in the pool before it is eligible for eviction by
      * the idle object evictor, with the extra condition that at least "minIdle" connections remain in the pool.
+     * <p>
+     * When {@link #getMinEvictableIdleTimeMillis() minEvictableIdleTimeMillis} is set to a positive value,
+     * minEvictableIdleTimeMillis is examined first by the idle connection evictor - i.e. when idle connections are
+     * visited by the evictor, idle time is first compared against {@code minEvictableIdleTimeMillis} (without
+     * considering the number of idle connections in the pool) and then against {@code softMinEvictableIdleTimeMillis},
+     * including the {@code minIdle}, constraint.
      * </p>
      *
+     * @return minimum amount of time a connection may sit idle in the pool before it is eligible for eviction, assuming
+     *         there are minIdle idle connections in the pool
+     * @since 2.10.0
+     */
+    public synchronized Duration getSoftMinEvictableIdleDuration() {
+        return softMinEvictableIdleDuration;
+    }
+
+    /**
+     * Gets the minimum amount of time a connection may sit idle in the pool before it is eligible for eviction by
+     * the idle object evictor, with the extra condition that at least "minIdle" connections remain in the pool.
      * <p>
      * When {@link #getMinEvictableIdleTimeMillis() minEvictableIdleTimeMillis} is set to a positive value,
      * minEvictableIdleTimeMillis is examined first by the idle connection evictor - i.e. when idle connections are
@@ -1284,18 +1369,20 @@ public class BasicDataSource implements DataSource, BasicDataSourceMXBean, MBean
      *
      * @return minimum amount of time a connection may sit idle in the pool before it is eligible for eviction, assuming
      *         there are minIdle idle connections in the pool
+     * @deprecated Use {@link #getSoftMinEvictableIdleDuration()}.
      */
+    @Deprecated
     @Override
     public synchronized long getSoftMinEvictableIdleTimeMillis() {
-        return softMinEvictableIdleTimeMillis;
+        return softMinEvictableIdleDuration.toMillis();
     }
 
     /**
-     * Returns the {@link #testOnBorrow} property.
+     * Gets the {code testOnBorrow} property.
      *
      * @return true if objects are validated before being borrowed from the pool
      *
-     * @see #testOnBorrow
+     * @see #setTestOnBorrow(boolean)
      */
     @Override
     public synchronized boolean getTestOnBorrow() {
@@ -1303,10 +1390,10 @@ public class BasicDataSource implements DataSource, BasicDataSourceMXBean, MBean
     }
 
     /**
-     * Returns the {@link #testOnCreate} property.
+     * Gets the {code testOnCreate} property.
      *
      * @return true if objects are validated immediately after they are created by the pool
-     * @see #testOnCreate
+     * @see #setTestOnCreate(boolean)
      */
     @Override
     public synchronized boolean getTestOnCreate() {
@@ -1314,20 +1401,20 @@ public class BasicDataSource implements DataSource, BasicDataSourceMXBean, MBean
     }
 
     /**
-     * Returns the value of the {@link #testOnReturn} property.
+     * Gets the value of the {code testOnReturn} property.
      *
      * @return true if objects are validated before being returned to the pool
-     * @see #testOnReturn
+     * @see #setTestOnReturn(boolean)
      */
     public synchronized boolean getTestOnReturn() {
         return this.testOnReturn;
     }
 
     /**
-     * Returns the value of the {@link #testWhileIdle} property.
+     * Gets the value of the {code testWhileIdle} property.
      *
      * @return true if objects examined by the idle object evictor are validated
-     * @see #testWhileIdle
+     * @see #setTestWhileIdle(boolean)
      */
     @Override
     public synchronized boolean getTestWhileIdle() {
@@ -1335,20 +1422,33 @@ public class BasicDataSource implements DataSource, BasicDataSourceMXBean, MBean
     }
 
     /**
-     * Returns the value of the {@link #timeBetweenEvictionRunsMillis} property.
+     * Gets the value of the {code durationBetweenEvictionRuns} property.
      *
      * @return the time (in milliseconds) between evictor runs
-     * @see #timeBetweenEvictionRunsMillis
+     * @see #setDurationBetweenEvictionRuns(Duration)
+     * @since 2.10.0
      */
+    public synchronized Duration getDurationBetweenEvictionRuns() {
+        return this.durationBetweenEvictionRuns;
+    }
+
+    /**
+     * Gets the value of the {code durationBetweenEvictionRuns} property.
+     *
+     * @return the time (in milliseconds) between evictor runs
+     * @see #setDurationBetweenEvictionRuns(Duration)
+     * @deprecated Use {@link #getDurationBetweenEvictionRuns()}.
+     */
+    @Deprecated
     @Override
     public synchronized long getTimeBetweenEvictionRunsMillis() {
-        return this.timeBetweenEvictionRunsMillis;
+        return this.durationBetweenEvictionRuns.toMillis();
     }
 
     /**
-     * Returns the JDBC connection {@link #url} property.
+     * Gets the JDBC connection {code url} property.
      *
-     * @return the {@link #url} passed to the JDBC driver to establish connections
+     * @return the {code url} passed to the JDBC driver to establish connections
      */
     @Override
     public synchronized String getUrl() {
@@ -1356,9 +1456,9 @@ public class BasicDataSource implements DataSource, BasicDataSourceMXBean, MBean
     }
 
     /**
-     * Returns the JDBC connection {@link #userName} property.
+     * Gets the JDBC connection {code userName} property.
      *
-     * @return the {@link #userName} passed to the JDBC driver to establish connections
+     * @return the {code userName} passed to the JDBC driver to establish connections
      */
     @Override
     public String getUsername() {
@@ -1366,10 +1466,10 @@ public class BasicDataSource implements DataSource, BasicDataSourceMXBean, MBean
     }
 
     /**
-     * Returns the validation query used to validate connections before returning them.
+     * Gets the validation query used to validate connections before returning them.
      *
      * @return the SQL validation query
-     * @see #validationQuery
+     * @see #setValidationQuery(String)
      */
     @Override
     public String getValidationQuery() {
@@ -1377,13 +1477,24 @@ public class BasicDataSource implements DataSource, BasicDataSourceMXBean, MBean
     }
 
     /**
-     * Returns the validation query timeout.
+     * Gets the validation query timeout.
+     *
+     * @return the timeout in seconds before connection validation queries fail.
+     */
+    public Duration getValidationQueryTimeoutDuration() {
+        return validationQueryTimeoutDuration;
+    }
+
+    /**
+     * Gets the validation query timeout.
      *
      * @return the timeout in seconds before connection validation queries fail.
+     * @deprecated Use {@link #getValidationQueryTimeoutDuration()}.
      */
+    @Deprecated
     @Override
     public int getValidationQueryTimeout() {
-        return validationQueryTimeoutSeconds;
+        return (int) validationQueryTimeoutDuration.getSeconds();
     }
 
     /**
@@ -1422,7 +1533,7 @@ public class BasicDataSource implements DataSource, BasicDataSourceMXBean, MBean
     }
 
     /**
-     * Returns the value of the accessToUnderlyingConnectionAllowed property.
+     * Gets the value of the accessToUnderlyingConnectionAllowed property.
      *
      * @return true if access to the underlying connection is allowed, false otherwise.
      */
@@ -1474,7 +1585,7 @@ public class BasicDataSource implements DataSource, BasicDataSourceMXBean, MBean
 
     @Override
     public boolean isWrapperFor(final Class<?> iface) throws SQLException {
-        return false;
+        return iface != null && iface.isInstance(this);
     }
 
     private void jmxRegister() {
@@ -1487,13 +1598,20 @@ public class BasicDataSource implements DataSource, BasicDataSourceMXBean, MBean
         if (requestedName == null) {
             return;
         }
+        registeredJmxObjectName = registerJmxObjectName(requestedName, null);
         try {
-            ObjectNameWrapper.wrap(requestedName).registerMBean(this);
-        } catch (final MalformedObjectNameException e) {
+            final StandardMBean standardMBean = new StandardMBean(this, DataSourceMXBean.class);
+            registeredJmxObjectName.registerMBean(standardMBean);
+        } catch (final NotCompliantMBeanException e) {
             log.warn("The requested JMX name [" + requestedName + "] was not valid and will be ignored.");
         }
     }
 
+    /**
+     * Logs the given message.
+     *
+     * @param message the message to log.
+     */
     protected void log(final String message) {
         if (logWriter != null) {
             logWriter.println(message);
@@ -1531,18 +1649,23 @@ public class BasicDataSource implements DataSource, BasicDataSourceMXBean, MBean
 
     @Override
     public ObjectName preRegister(final MBeanServer server, final ObjectName objectName) {
-        final String requestedName = getJmxName();
+        registeredJmxObjectName = registerJmxObjectName(getJmxName(), objectName);
+        return ObjectNameWrapper.unwrap(registeredJmxObjectName);
+    }
+
+    private ObjectNameWrapper registerJmxObjectName(final String requestedName, final ObjectName objectName) {
+        ObjectNameWrapper objectNameWrapper = null;
         if (requestedName != null) {
             try {
-                registeredJmxObjectName = ObjectNameWrapper.wrap(requestedName);
+                objectNameWrapper = ObjectNameWrapper.wrap(requestedName);
             } catch (final MalformedObjectNameException e) {
-                log.warn("The requested JMX name [" + requestedName + "] was not valid and will be ignored.");
+                log.warn("The requested JMX name '" + requestedName + "' was not valid and will be ignored.");
             }
         }
-        if (registeredJmxObjectName == null) {
-            registeredJmxObjectName = ObjectNameWrapper.wrap(objectName);
+        if (objectNameWrapper == null) {
+            objectNameWrapper = ObjectNameWrapper.wrap(objectName);
         }
-        return ObjectNameWrapper.unwrap(registeredJmxObjectName);
+        return objectNameWrapper;
     }
 
     /**
@@ -1616,10 +1739,8 @@ public class BasicDataSource implements DataSource, BasicDataSourceMXBean, MBean
     }
 
     /**
-     * <p>
      * Sets the value of the accessToUnderlyingConnectionAllowed property. It controls if the PoolGuard allows access to
      * the underlying connection. (Default: false)
-     * </p>
      * <p>
      * Note: this method currently has no effect once the pool has been initialized. The pool is initialized the first
      * time one of the following methods is invoked: <code>getConnection, setLogwriter,
@@ -1686,7 +1807,7 @@ public class BasicDataSource implements DataSource, BasicDataSourceMXBean, MBean
      * @param connectionInitSqls Collection of SQL statements to execute on connection creation
      */
     public void setConnectionInitSqls(final Collection<String> connectionInitSqls) {
-        if (connectionInitSqls != null && connectionInitSqls.size() > 0) {
+        if (connectionInitSqls != null && !connectionInitSqls.isEmpty()) {
             ArrayList<String> newVal = null;
             for (final String s : connectionInitSqls) {
                 if (!isEmpty(s)) {
@@ -1718,7 +1839,7 @@ public class BasicDataSource implements DataSource, BasicDataSourceMXBean, MBean
         final String[] entries = connectionProperties.split(";");
         final Properties properties = new Properties();
         for (final String entry : entries) {
-            if (entry.length() > 0) {
+            if (!entry.isEmpty()) {
                 final int index = entry.indexOf('=');
                 if (index > 0) {
                     final String name = entry.substring(0, index);
@@ -1735,9 +1856,7 @@ public class BasicDataSource implements DataSource, BasicDataSourceMXBean, MBean
     }
 
     /**
-     * <p>
      * Sets default auto-commit state of connections returned by this datasource.
-     * </p>
      * <p>
      * Note: this method currently has no effect once the pool has been initialized. The pool is initialized the first
      * time one of the following methods is invoked: <code>getConnection, setLogwriter,
@@ -1751,9 +1870,7 @@ public class BasicDataSource implements DataSource, BasicDataSourceMXBean, MBean
     }
 
     /**
-     * <p>
      * Sets the default catalog.
-     * </p>
      * <p>
      * Note: this method currently has no effect once the pool has been initialized. The pool is initialized the first
      * time one of the following methods is invoked: <code>getConnection, setLogwriter,
@@ -1770,16 +1887,27 @@ public class BasicDataSource implements DataSource, BasicDataSourceMXBean, MBean
      * Sets the default query timeout that will be used for {@link java.sql.Statement Statement}s created from this
      * connection. <code>null</code> means that the driver default will be used.
      *
+     * @param defaultQueryTimeoutDuration The default query timeout Duration.
+     * @since 2.10.0
+     */
+    public void setDefaultQueryTimeout(final Duration defaultQueryTimeoutDuration) {
+        this.defaultQueryTimeoutDuration = defaultQueryTimeoutDuration;
+    }
+
+    /**
+     * Sets the default query timeout that will be used for {@link java.sql.Statement Statement}s created from this
+     * connection. <code>null</code> means that the driver default will be used.
+     *
      * @param defaultQueryTimeoutSeconds The default query timeout in seconds.
+     * @deprecated Use {@link #setDefaultQueryTimeout(Duration)}.
      */
+    @Deprecated
     public void setDefaultQueryTimeout(final Integer defaultQueryTimeoutSeconds) {
-        this.defaultQueryTimeoutSeconds = defaultQueryTimeoutSeconds;
+        this.defaultQueryTimeoutDuration = defaultQueryTimeoutSeconds == null ? null : Duration.ofSeconds(defaultQueryTimeoutSeconds.longValue());
     }
 
     /**
-     * <p>
      * Sets defaultReadonly property.
-     * </p>
      * <p>
      * Note: this method currently has no effect once the pool has been initialized. The pool is initialized the first
      * time one of the following methods is invoked: <code>getConnection, setLogwriter,
@@ -1793,9 +1921,7 @@ public class BasicDataSource implements DataSource, BasicDataSourceMXBean, MBean
     }
 
     /**
-     * <p>
      * Sets the default schema.
-     * </p>
      * <p>
      * Note: this method currently has no effect once the pool has been initialized. The pool is initialized the first
      * time one of the following methods is invoked: <code>getConnection, setLogwriter,
@@ -1810,9 +1936,7 @@ public class BasicDataSource implements DataSource, BasicDataSourceMXBean, MBean
     }
 
     /**
-     * <p>
      * Sets the default transaction isolation state for returned connections.
-     * </p>
      * <p>
      * Note: this method currently has no effect once the pool has been initialized. The pool is initialized the first
      * time one of the following methods is invoked: <code>getConnection, setLogwriter,
@@ -1848,7 +1972,7 @@ public class BasicDataSource implements DataSource, BasicDataSourceMXBean, MBean
      * @since 2.1
      */
     public void setDisconnectionSqlCodes(final Collection<String> disconnectionSqlCodes) {
-        if (disconnectionSqlCodes != null && disconnectionSqlCodes.size() > 0) {
+        if (disconnectionSqlCodes != null && !disconnectionSqlCodes.isEmpty()) {
             HashSet<String> newVal = null;
             for (final String s : disconnectionSqlCodes) {
                 if (!isEmpty(s)) {
@@ -1879,9 +2003,7 @@ public class BasicDataSource implements DataSource, BasicDataSourceMXBean, MBean
     }
 
     /**
-     * <p>
      * Sets the class loader to be used to load the JDBC driver.
-     * </p>
      * <p>
      * Note: this method currently has no effect once the pool has been initialized. The pool is initialized the first
      * time one of the following methods is invoked: <code>getConnection, setLogwriter,
@@ -1895,9 +2017,7 @@ public class BasicDataSource implements DataSource, BasicDataSourceMXBean, MBean
     }
 
     /**
-     * <p>
      * Sets the JDBC driver class name.
-     * </p>
      * <p>
      * Note: this method currently has no effect once the pool has been initialized. The pool is initialized the first
      * time one of the following methods is invoked: <code>getConnection, setLogwriter,
@@ -1946,9 +2066,7 @@ public class BasicDataSource implements DataSource, BasicDataSourceMXBean, MBean
     }
 
     /**
-     * <p>
      * Sets the initial size of the connection pool.
-     * </p>
      * <p>
      * Note: this method currently has no effect once the pool has been initialized. The pool is initialized the first
      * time one of the following methods is invoked: <code>getConnection, setLogwriter,
@@ -2000,7 +2118,7 @@ public class BasicDataSource implements DataSource, BasicDataSourceMXBean, MBean
     }
 
     /**
-     * When {@link #getMaxConnLifetimeMillis()} is set to limit connection lifetime, this property determines whether or
+     * When {@link #getMaxConnDuration()} is set to limit connection lifetime, this property determines whether or
      * not log messages are generated when the pool closes connections due to maximum lifetime exceeded. Set this
      * property to false to suppress log messages when connections expire.
      *
@@ -2034,9 +2152,7 @@ public class BasicDataSource implements DataSource, BasicDataSourceMXBean, MBean
     }
 
     /**
-     * <p>
      * Sets the log writer being used by this data source.
-     * </p>
      * <p>
      * Calls {@link #createDataSource()}, so has the side effect of initializing the connection pool.
      * </p>
@@ -2051,10 +2167,24 @@ public class BasicDataSource implements DataSource, BasicDataSourceMXBean, MBean
     }
 
     /**
+     * Sets the maximum permitted lifetime of a connection. A value of zero or less indicates an
+     * infinite lifetime.
      * <p>
+     * Note: this method currently has no effect once the pool has been initialized. The pool is initialized the first
+     * time one of the following methods is invoked: <code>getConnection, setLogwriter,
+     * setLoginTimeout, getLoginTimeout, getLogWriter.</code>
+     * </p>
+     *
+     * @param maxConnDuration The maximum permitted lifetime of a connection.
+     * @since 2.10.0
+     */
+    public void setMaxConn(final Duration maxConnDuration) {
+        this.maxConnDuration = maxConnDuration;
+    }
+
+    /**
      * Sets the maximum permitted lifetime of a connection in milliseconds. A value of zero or less indicates an
      * infinite lifetime.
-     * </p>
      * <p>
      * Note: this method currently has no effect once the pool has been initialized. The pool is initialized the first
      * time one of the following methods is invoked: <code>getConnection, setLogwriter,
@@ -2062,9 +2192,11 @@ public class BasicDataSource implements DataSource, BasicDataSourceMXBean, MBean
      * </p>
      *
      * @param maxConnLifetimeMillis The maximum permitted lifetime of a connection in milliseconds.
+     * @deprecated Use {@link #setMaxConn(Duration)}.
      */
+    @Deprecated
     public void setMaxConnLifetimeMillis(final long maxConnLifetimeMillis) {
-        this.maxConnLifetimeMillis = maxConnLifetimeMillis;
+        this.maxConnDuration = Duration.ofMillis(maxConnLifetimeMillis);
     }
 
     /**
@@ -2082,9 +2214,7 @@ public class BasicDataSource implements DataSource, BasicDataSourceMXBean, MBean
     }
 
     /**
-     * <p>
      * Sets the value of the <code>maxOpenPreparedStatements</code> property.
-     * </p>
      * <p>
      * Note: this method currently has no effect once the pool has been initialized. The pool is initialized the first
      * time one of the following methods is invoked: <code>getConnection, setLogwriter,
@@ -2114,35 +2244,59 @@ public class BasicDataSource implements DataSource, BasicDataSourceMXBean, MBean
     /**
      * Sets the MaxWaitMillis property. Use -1 to make the pool wait indefinitely.
      *
+     * @param maxWaitDuration the new value for MaxWaitMillis
+     * @see #getMaxWaitDuration()
+     * @since 2.10.0
+     */
+    public synchronized void setMaxWait(final Duration maxWaitDuration) {
+        this.maxWaitDuration = maxWaitDuration;
+        if (connectionPool != null) {
+            connectionPool.setMaxWait(maxWaitDuration);
+        }
+    }
+
+    /**
+     * Sets the MaxWaitMillis property. Use -1 to make the pool wait indefinitely.
+     *
      * @param maxWaitMillis the new value for MaxWaitMillis
-     * @see #getMaxWaitMillis()
+     * @see #getMaxWaitDuration()
+     * @deprecated {@link #setMaxWait(Duration)}.
      */
+    @Deprecated
     public synchronized void setMaxWaitMillis(final long maxWaitMillis) {
-        this.maxWaitMillis = maxWaitMillis;
+        setMaxWait(Duration.ofMillis(maxWaitMillis));
+    }
+
+    /**
+     * Sets the {code minEvictableIdleDuration} property.
+     *
+     * @param minEvictableIdleDuration the minimum amount of time an object may sit idle in the pool
+     * @see #setMinEvictableIdle(Duration)
+     * @since 2.10.0
+     */
+    public synchronized void setMinEvictableIdle(final Duration minEvictableIdleDuration) {
+        this.minEvictableIdleDuration = minEvictableIdleDuration;
         if (connectionPool != null) {
-            connectionPool.setMaxWaitMillis(maxWaitMillis);
+            connectionPool.setMinEvictableIdle(minEvictableIdleDuration);
         }
     }
 
     /**
-     * Sets the {@link #minEvictableIdleTimeMillis} property.
+     * Sets the {code minEvictableIdleDuration} property.
      *
      * @param minEvictableIdleTimeMillis the minimum amount of time an object may sit idle in the pool
-     * @see #minEvictableIdleTimeMillis
+     * @see #setMinEvictableIdle(Duration)
+     * @deprecated Use {@link #setMinEvictableIdle(Duration)}.
      */
+    @Deprecated
     public synchronized void setMinEvictableIdleTimeMillis(final long minEvictableIdleTimeMillis) {
-        this.minEvictableIdleTimeMillis = minEvictableIdleTimeMillis;
-        if (connectionPool != null) {
-            connectionPool.setMinEvictableIdleTimeMillis(minEvictableIdleTimeMillis);
-        }
+        setMinEvictableIdle(Duration.ofMillis(minEvictableIdleTimeMillis));
     }
 
-    // ------------------------------------------------------ Protected Methods
-
     /**
      * Sets the minimum number of idle connections in the pool. The pool attempts to ensure that minIdle connections are
      * available when the idle object evictor runs. The value of this property has no effect unless
-     * {@link #timeBetweenEvictionRunsMillis} has a positive value.
+     * {code durationBetweenEvictionRuns} has a positive value.
      *
      * @param minIdle the new value for minIdle
      * @see GenericObjectPool#setMinIdle(int)
@@ -2155,10 +2309,10 @@ public class BasicDataSource implements DataSource, BasicDataSourceMXBean, MBean
     }
 
     /**
-     * Sets the value of the {@link #numTestsPerEvictionRun} property.
+     * Sets the value of the {code numTestsPerEvictionRun} property.
      *
-     * @param numTestsPerEvictionRun the new {@link #numTestsPerEvictionRun} value
-     * @see #numTestsPerEvictionRun
+     * @param numTestsPerEvictionRun the new {code numTestsPerEvictionRun} value
+     * @see #setNumTestsPerEvictionRun(int)
      */
     public synchronized void setNumTestsPerEvictionRun(final int numTestsPerEvictionRun) {
         this.numTestsPerEvictionRun = numTestsPerEvictionRun;
@@ -2168,9 +2322,7 @@ public class BasicDataSource implements DataSource, BasicDataSourceMXBean, MBean
     }
 
     /**
-     * <p>
-     * Sets the {@link #password}.
-     * </p>
+     * Sets the {code password}.
      * <p>
      * Note: this method currently has no effect once the pool has been initialized. The pool is initialized the first
      * time one of the following methods is invoked: <code>getConnection, setLogwriter,
@@ -2184,9 +2336,7 @@ public class BasicDataSource implements DataSource, BasicDataSourceMXBean, MBean
     }
 
     /**
-     * <p>
      * Sets whether to pool statements or not.
-     * </p>
      * <p>
      * Note: this method currently has no effect once the pool has been initialized. The pool is initialized the first
      * time one of the following methods is invoked: <code>getConnection, setLogwriter,
@@ -2231,25 +2381,48 @@ public class BasicDataSource implements DataSource, BasicDataSourceMXBean, MBean
     }
 
     /**
+     * Sets the timeout before an abandoned connection can be removed.
      * <p>
-     * Sets the timeout in seconds before an abandoned connection can be removed.
+     * Setting this property has no effect if {@link #getRemoveAbandonedOnBorrow()} and
+     * {code getRemoveAbandonedOnMaintenance()} are false.
      * </p>
      *
+     * @param removeAbandonedTimeout new abandoned timeout
+     * @see #getRemoveAbandonedTimeoutDuration()
+     * @see #getRemoveAbandonedOnBorrow()
+     * @see #getRemoveAbandonedOnMaintenance()
+     * @since 2.10.0
+     */
+    public void setRemoveAbandonedTimeout(final Duration removeAbandonedTimeout) {
+        if (abandonedConfig == null) {
+            abandonedConfig = new AbandonedConfig();
+        }
+        abandonedConfig.setRemoveAbandonedTimeout(removeAbandonedTimeout);
+        final GenericObjectPool<?> gop = this.connectionPool;
+        if (gop != null) {
+            gop.setAbandonedConfig(abandonedConfig);
+        }
+    }
+
+    /**
+     * Sets the timeout in seconds before an abandoned connection can be removed.
      * <p>
      * Setting this property has no effect if {@link #getRemoveAbandonedOnBorrow()} and
      * {@link #getRemoveAbandonedOnMaintenance()} are false.
      * </p>
      *
      * @param removeAbandonedTimeout new abandoned timeout in seconds
-     * @see #getRemoveAbandonedTimeout()
+     * @see #getRemoveAbandonedTimeoutDuration()
      * @see #getRemoveAbandonedOnBorrow()
      * @see #getRemoveAbandonedOnMaintenance()
+     * @deprecated Use {@link #setRemoveAbandonedTimeout(Duration)}.
      */
+    @Deprecated
     public void setRemoveAbandonedTimeout(final int removeAbandonedTimeout) {
         if (abandonedConfig == null) {
             abandonedConfig = new AbandonedConfig();
         }
-        abandonedConfig.setRemoveAbandonedTimeout(removeAbandonedTimeout);
+        abandonedConfig.setRemoveAbandonedTimeout(Duration.ofSeconds(removeAbandonedTimeout));
         final GenericObjectPool<?> gop = this.connectionPool;
         if (gop != null) {
             gop.setAbandonedConfig(abandonedConfig);
@@ -2274,16 +2447,32 @@ public class BasicDataSource implements DataSource, BasicDataSourceMXBean, MBean
      *                                       eligible for eviction, assuming there are minIdle idle connections in the
      *                                       pool.
      * @see #getSoftMinEvictableIdleTimeMillis
+     * @since 2.10.0
      */
-    public synchronized void setSoftMinEvictableIdleTimeMillis(final long softMinEvictableIdleTimeMillis) {
-        this.softMinEvictableIdleTimeMillis = softMinEvictableIdleTimeMillis;
+    public synchronized void setSoftMinEvictableIdle(final Duration softMinEvictableIdleTimeMillis) {
+        this.softMinEvictableIdleDuration = softMinEvictableIdleTimeMillis;
         if (connectionPool != null) {
-            connectionPool.setSoftMinEvictableIdleTimeMillis(softMinEvictableIdleTimeMillis);
+            connectionPool.setSoftMinEvictableIdle(softMinEvictableIdleTimeMillis);
         }
     }
 
     /**
-     * Sets the {@link #testOnBorrow} property. This property determines whether or not the pool will validate objects
+     * Sets the minimum amount of time a connection may sit idle in the pool before it is eligible for eviction by the
+     * idle object evictor, with the extra condition that at least "minIdle" connections remain in the pool.
+     *
+     * @param softMinEvictableIdleTimeMillis minimum amount of time a connection may sit idle in the pool before it is
+     *                                       eligible for eviction, assuming there are minIdle idle connections in the
+     *                                       pool.
+     * @see #getSoftMinEvictableIdleTimeMillis
+     * @deprecated Use {@link #setSoftMinEvictableIdle(Duration)}.
+     */
+    @Deprecated
+    public synchronized void setSoftMinEvictableIdleTimeMillis(final long softMinEvictableIdleTimeMillis) {
+        setSoftMinEvictableIdle(Duration.ofMillis(softMinEvictableIdleTimeMillis));
+    }
+
+    /**
+     * Sets the {code testOnBorrow} property. This property determines whether or not the pool will validate objects
      * before they are borrowed from the pool.
      *
      * @param testOnBorrow new value for testOnBorrow property
@@ -2296,7 +2485,7 @@ public class BasicDataSource implements DataSource, BasicDataSourceMXBean, MBean
     }
 
     /**
-     * Sets the {@link #testOnCreate} property. This property determines whether or not the pool will validate objects
+     * Sets the {code testOnCreate} property. This property determines whether or not the pool will validate objects
      * immediately after they are created by the pool
      *
      * @param testOnCreate new value for testOnCreate property
@@ -2335,22 +2524,33 @@ public class BasicDataSource implements DataSource, BasicDataSourceMXBean, MBean
     }
 
     /**
-     * Sets the {@link #timeBetweenEvictionRunsMillis} property.
+     * Sets the {code durationBetweenEvictionRuns} property.
      *
      * @param timeBetweenEvictionRunsMillis the new time between evictor runs
-     * @see #timeBetweenEvictionRunsMillis
+     * @see #setDurationBetweenEvictionRuns(Duration)
+     * @since 2.10.0
      */
-    public synchronized void setTimeBetweenEvictionRunsMillis(final long timeBetweenEvictionRunsMillis) {
-        this.timeBetweenEvictionRunsMillis = timeBetweenEvictionRunsMillis;
+    public synchronized void setDurationBetweenEvictionRuns(final Duration timeBetweenEvictionRunsMillis) {
+        this.durationBetweenEvictionRuns = timeBetweenEvictionRunsMillis;
         if (connectionPool != null) {
-            connectionPool.setTimeBetweenEvictionRunsMillis(timeBetweenEvictionRunsMillis);
+            connectionPool.setTimeBetweenEvictionRuns(timeBetweenEvictionRunsMillis);
         }
     }
 
     /**
-     * <p>
-     * Sets the {@link #url}.
-     * </p>
+     * Sets the {code durationBetweenEvictionRuns} property.
+     *
+     * @param timeBetweenEvictionRunsMillis the new time between evictor runs
+     * @see #setDurationBetweenEvictionRuns(Duration)
+     * @deprecated Use {@link #setDurationBetweenEvictionRuns(Duration)}.
+     */
+    @Deprecated
+    public synchronized void setTimeBetweenEvictionRunsMillis(final long timeBetweenEvictionRunsMillis) {
+        setDurationBetweenEvictionRuns(Duration.ofMillis(timeBetweenEvictionRunsMillis));
+    }
+
+    /**
+     * Sets the {code url}.
      * <p>
      * Note: this method currently has no effect once the pool has been initialized. The pool is initialized the first
      * time one of the following methods is invoked: <code>getConnection, setLogwriter,
@@ -2364,9 +2564,7 @@ public class BasicDataSource implements DataSource, BasicDataSourceMXBean, MBean
     }
 
     /**
-     * <p>
-     * Sets the {@link #userName}.
-     * </p>
+     * Sets the {code userName}.
      * <p>
      * Note: this method currently has no effect once the pool has been initialized. The pool is initialized the first
      * time one of the following methods is invoked: <code>getConnection, setLogwriter,
@@ -2380,9 +2578,7 @@ public class BasicDataSource implements DataSource, BasicDataSourceMXBean, MBean
     }
 
     /**
-     * <p>
-     * Sets the {@link #validationQuery}.
-     * </p>
+     * Sets the {code validationQuery}.
      * <p>
      * Note: this method currently has no effect once the pool has been initialized. The pool is initialized the first
      * time one of the following methods is invoked: <code>getConnection, setLogwriter,
@@ -2404,10 +2600,28 @@ public class BasicDataSource implements DataSource, BasicDataSourceMXBean, MBean
      * setLoginTimeout, getLoginTimeout, getLogWriter.</code>
      * </p>
      *
+     * @param validationQueryTimeoutDuration new validation query timeout value in seconds
+     * @since 2.10.0
+     */
+    public void setValidationQueryTimeout(final Duration validationQueryTimeoutDuration) {
+        this.validationQueryTimeoutDuration = validationQueryTimeoutDuration;
+    }
+
+    /**
+     * Sets the validation query timeout, the amount of time, in seconds, that connection validation will wait for a
+     * response from the database when executing a validation query. Use a value less than or equal to 0 for no timeout.
+     * <p>
+     * Note: this method currently has no effect once the pool has been initialized. The pool is initialized the first
+     * time one of the following methods is invoked: <code>getConnection, setLogwriter,
+     * setLoginTimeout, getLoginTimeout, getLogWriter.</code>
+     * </p>
+     *
      * @param validationQueryTimeoutSeconds new validation query timeout value in seconds
+     * @deprecated Use {@link #setValidationQueryTimeout(Duration)}.
      */
+    @Deprecated
     public void setValidationQueryTimeout(final int validationQueryTimeoutSeconds) {
-        this.validationQueryTimeoutSeconds = validationQueryTimeoutSeconds;
+        this.validationQueryTimeoutDuration = Duration.ofSeconds(validationQueryTimeoutSeconds);
     }
 
     /**
@@ -2437,14 +2651,17 @@ public class BasicDataSource implements DataSource, BasicDataSourceMXBean, MBean
      * Starts the connection pool maintenance task, if configured.
      */
     protected void startPoolMaintenance() {
-        if (connectionPool != null && timeBetweenEvictionRunsMillis > 0) {
-            connectionPool.setTimeBetweenEvictionRunsMillis(timeBetweenEvictionRunsMillis);
+        if (connectionPool != null && durationBetweenEvictionRuns.compareTo(Duration.ZERO) > 0) {
+            connectionPool.setTimeBetweenEvictionRuns(durationBetweenEvictionRuns);
         }
     }
 
     @Override
     public <T> T unwrap(final Class<T> iface) throws SQLException {
-        throw new SQLException("BasicDataSource is not a wrapper.");
+        if (isWrapperFor(iface)) {
+            return iface.cast(this);
+        }
+        throw new SQLException(this + " is not a wrapper for " + iface);
     }
 
     private void updateJmxName(final GenericObjectPoolConfig<?> config) {
diff --git a/java/org/apache/tomcat/dbcp/dbcp2/BasicDataSourceFactory.java b/java/org/apache/tomcat/dbcp/dbcp2/BasicDataSourceFactory.java
index 7f63be4..a71f8ff 100644
--- a/java/org/apache/tomcat/dbcp/dbcp2/BasicDataSourceFactory.java
+++ b/java/org/apache/tomcat/dbcp/dbcp2/BasicDataSourceFactory.java
@@ -17,8 +17,10 @@
 package org.apache.tomcat.dbcp.dbcp2;
 
 import java.io.ByteArrayInputStream;
+import java.io.IOException;
 import java.nio.charset.StandardCharsets;
 import java.sql.Connection;
+import java.time.Duration;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collection;
@@ -26,9 +28,14 @@ import java.util.Enumeration;
 import java.util.Hashtable;
 import java.util.LinkedHashMap;
 import java.util.List;
+import java.util.Locale;
 import java.util.Map;
+import java.util.Objects;
+import java.util.Optional;
 import java.util.Properties;
 import java.util.StringTokenizer;
+import java.util.function.Consumer;
+import java.util.function.Function;
 
 import javax.naming.Context;
 import javax.naming.Name;
@@ -42,11 +49,9 @@ import org.apache.tomcat.dbcp.pool2.impl.BaseObjectPoolConfig;
 import org.apache.tomcat.dbcp.pool2.impl.GenericObjectPoolConfig;
 
 /**
- * <p>
  * JNDI object factory that creates an instance of <code>BasicDataSource</code> that has been configured based on the
  * <code>RefAddr</code> values of the specified <code>Reference</code>, which must match the names and data types of the
  * <code>BasicDataSource</code> bean properties with the following exceptions:
- * </p>
  * <ul>
  * <li><code>connectionInitSqls</code> must be passed to this factory as a single String using semicolon to delimit the
  * statements whereas <code>BasicDataSource</code> requires a collection of Strings.</li>
@@ -162,7 +167,7 @@ public class BasicDataSourceFactory implements ObjectFactory {
                         + "Both have default value set to false.");
         NUPROP_WARNTEXT.put(NUPROP_MAXWAIT,
                 "Property " + NUPROP_MAXWAIT + " is not used in DBCP2" + " , use " + PROP_MAX_WAIT_MILLIS + " instead. "
-                        + PROP_MAX_WAIT_MILLIS + " default value is " + BaseObjectPoolConfig.DEFAULT_MAX_WAIT_MILLIS
+                        + PROP_MAX_WAIT_MILLIS + " default value is " + BaseObjectPoolConfig.DEFAULT_MAX_WAIT
                         + ".");
     }
 
@@ -180,13 +185,173 @@ public class BasicDataSourceFactory implements ObjectFactory {
 
     }
 
-    // -------------------------------------------------- ObjectFactory Methods
+    private static <V> void accept(final Properties properties, final String name, final Function<String, V> parser, final Consumer<V> consumer) {
+        getOptional(properties, name).ifPresent(v -> consumer.accept(parser.apply(v)));
+    }
+
+    private static void acceptBoolean(final Properties properties, final String name, final Consumer<Boolean> consumer) {
+        accept(properties, name, Boolean::parseBoolean, consumer);
+    }
+
+    private static void acceptDurationOfMillis(final Properties properties, final String name, final Consumer<Duration> consumer) {
+        accept(properties, name, s -> Duration.ofMillis(Long.parseLong(s)), consumer);
+    }
+
+    private static void acceptDurationOfSeconds(final Properties properties, final String name, final Consumer<Duration> consumer) {
+        accept(properties, name, s -> Duration.ofSeconds(Long.parseLong(s)), consumer);
+    }
+
+    private static void acceptInt(final Properties properties, final String name, final Consumer<Integer> consumer) {
+        accept(properties, name, Integer::parseInt, consumer);
+    }
+
+    private static void acceptString(final Properties properties, final String name, final Consumer<String> consumer) {
+        accept(properties, name, Function.identity(), consumer);
+    }
+
+    /**
+     * Creates and configures a {@link BasicDataSource} instance based on the given properties.
+     *
+     * @param properties
+     *            The data source configuration properties.
+     * @return A new a {@link BasicDataSource} instance based on the given properties.
+     * @throws Exception
+     *             Thrown when an error occurs creating the data source.
+     */
+    public static BasicDataSource createDataSource(final Properties properties) throws Exception {
+        final BasicDataSource dataSource = new BasicDataSource();
+        acceptBoolean(properties, PROP_DEFAULT_AUTO_COMMIT, dataSource::setDefaultAutoCommit);
+        acceptBoolean(properties, PROP_DEFAULT_READ_ONLY, dataSource::setDefaultReadOnly);
+
+        getOptional(properties, PROP_DEFAULT_TRANSACTION_ISOLATION).ifPresent(value -> {
+            value = value.toUpperCase(Locale.ROOT);
+            int level = PoolableConnectionFactory.UNKNOWN_TRANSACTION_ISOLATION;
+            if ("NONE".equals(value)) {
+                level = Connection.TRANSACTION_NONE;
+            } else if ("READ_COMMITTED".equals(value)) {
+                level = Connection.TRANSACTION_READ_COMMITTED;
+            } else if ("READ_UNCOMMITTED".equals(value)) {
+                level = Connection.TRANSACTION_READ_UNCOMMITTED;
+            } else if ("REPEATABLE_READ".equals(value)) {
+                level = Connection.TRANSACTION_REPEATABLE_READ;
+            } else if ("SERIALIZABLE".equals(value)) {
+                level = Connection.TRANSACTION_SERIALIZABLE;
+            } else {
+                try {
+                    level = Integer.parseInt(value);
+                } catch (final NumberFormatException e) {
+                    System.err.println("Could not parse defaultTransactionIsolation: " + value);
+                    System.err.println("WARNING: defaultTransactionIsolation not set");
+                    System.err.println("using default value of database driver");
+                    level = PoolableConnectionFactory.UNKNOWN_TRANSACTION_ISOLATION;
+                }
+            }
+            dataSource.setDefaultTransactionIsolation(level);
+        });
+
+        acceptString(properties, PROP_DEFAULT_SCHEMA, dataSource::setDefaultSchema);
+        acceptString(properties, PROP_DEFAULT_CATALOG, dataSource::setDefaultCatalog);
+        acceptBoolean(properties, PROP_CACHE_STATE, dataSource::setCacheState);
+        acceptString(properties, PROP_DRIVER_CLASS_NAME, dataSource::setDriverClassName);
+        acceptBoolean(properties, PROP_LIFO, dataSource::setLifo);
+        acceptInt(properties, PROP_MAX_TOTAL, dataSource::setMaxTotal);
+        acceptInt(properties, PROP_MAX_IDLE, dataSource::setMaxIdle);
+        acceptInt(properties, PROP_MIN_IDLE, dataSource::setMinIdle);
+        acceptInt(properties, PROP_INITIAL_SIZE, dataSource::setInitialSize);
+        acceptDurationOfMillis(properties, PROP_MAX_WAIT_MILLIS, dataSource::setMaxWait);
+        acceptBoolean(properties, PROP_TEST_ON_CREATE, dataSource::setTestOnCreate);
+        acceptBoolean(properties, PROP_TEST_ON_BORROW, dataSource::setTestOnBorrow);
+        acceptBoolean(properties, PROP_TEST_ON_RETURN, dataSource::setTestOnReturn);
+        acceptDurationOfMillis(properties, PROP_TIME_BETWEEN_EVICTION_RUNS_MILLIS, dataSource::setDurationBetweenEvictionRuns);
+        acceptInt(properties, PROP_NUM_TESTS_PER_EVICTION_RUN, dataSource::setNumTestsPerEvictionRun);
+        acceptDurationOfMillis(properties, PROP_MIN_EVICTABLE_IDLE_TIME_MILLIS, dataSource::setMinEvictableIdle);
+        acceptDurationOfMillis(properties, PROP_SOFT_MIN_EVICTABLE_IDLE_TIME_MILLIS, dataSource::setSoftMinEvictableIdle);
+        acceptString(properties, PROP_EVICTION_POLICY_CLASS_NAME, dataSource::setEvictionPolicyClassName);
+        acceptBoolean(properties, PROP_TEST_WHILE_IDLE, dataSource::setTestWhileIdle);
+        acceptString(properties, PROP_PASSWORD, dataSource::setPassword);
+        acceptString(properties, PROP_URL, dataSource::setUrl);
+        acceptString(properties, PROP_USER_NAME, dataSource::setUsername);
+        acceptString(properties, PROP_VALIDATION_QUERY, dataSource::setValidationQuery);
+        acceptDurationOfSeconds(properties, PROP_VALIDATION_QUERY_TIMEOUT, dataSource::setValidationQueryTimeout);
+        acceptBoolean(properties, PROP_ACCESS_TO_UNDERLYING_CONNECTION_ALLOWED, dataSource::setAccessToUnderlyingConnectionAllowed);
+        acceptBoolean(properties, PROP_REMOVE_ABANDONED_ON_BORROW, dataSource::setRemoveAbandonedOnBorrow);
+        acceptBoolean(properties, PROP_REMOVE_ABANDONED_ON_MAINTENANCE, dataSource::setRemoveAbandonedOnMaintenance);
+        acceptDurationOfSeconds(properties, PROP_REMOVE_ABANDONED_TIMEOUT, dataSource::setRemoveAbandonedTimeout);
+        acceptBoolean(properties, PROP_LOG_ABANDONED, dataSource::setLogAbandoned);
+        acceptBoolean(properties, PROP_ABANDONED_USAGE_TRACKING, dataSource::setAbandonedUsageTracking);
+        acceptBoolean(properties, PROP_POOL_PREPARED_STATEMENTS, dataSource::setPoolPreparedStatements);
+        acceptBoolean(properties, PROP_CLEAR_STATEMENT_POOL_ON_RETURN, dataSource::setClearStatementPoolOnReturn);
+        acceptInt(properties, PROP_MAX_OPEN_PREPARED_STATEMENTS, dataSource::setMaxOpenPreparedStatements);
+        getOptional(properties, PROP_CONNECTION_INIT_SQLS).ifPresent(v -> dataSource.setConnectionInitSqls(parseList(v, ';')));
+
+        final String value = properties.getProperty(PROP_CONNECTION_PROPERTIES);
+        if (value != null) {
+            for (final Object key : getProperties(value).keySet()) {
+                final String propertyName = Objects.toString(key, null);
+                dataSource.addConnectionProperty(propertyName, getProperties(value).getProperty(propertyName));
+            }
+        }
+
+        acceptDurationOfMillis(properties, PROP_MAX_CONN_LIFETIME_MILLIS, dataSource::setMaxConn);
+        acceptBoolean(properties, PROP_LOG_EXPIRED_CONNECTIONS, dataSource::setLogExpiredConnections);
+        acceptString(properties, PROP_JMX_NAME, dataSource::setJmxName);
+        acceptBoolean(properties, PROP_ENABLE_AUTO_COMMIT_ON_RETURN, dataSource::setAutoCommitOnReturn);
+        acceptBoolean(properties, PROP_ROLLBACK_ON_RETURN, dataSource::setRollbackOnReturn);
+        acceptDurationOfSeconds(properties, PROP_DEFAULT_QUERY_TIMEOUT, dataSource::setDefaultQueryTimeout);
+        acceptBoolean(properties, PROP_FAST_FAIL_VALIDATION, dataSource::setFastFailValidation);
+        getOptional(properties, PROP_DISCONNECTION_SQL_CODES).ifPresent(v -> dataSource.setDisconnectionSqlCodes(parseList(v, ',')));
+        acceptString(properties, PROP_CONNECTION_FACTORY_CLASS_NAME, dataSource::setConnectionFactoryClassName);
+
+        // DBCP-215
+        // Trick to make sure that initialSize connections are created
+        if (dataSource.getInitialSize() > 0) {
+            dataSource.getLogWriter();
+        }
+
+        // Return the configured DataSource instance
+        return dataSource;
+    }
+
+    private static Optional<String> getOptional(final Properties properties, final String name) {
+        return Optional.ofNullable(properties.getProperty(name));
+    }
+
+    /**
+     * Parse properties from the string. Format of the string must be [propertyName=property;]*
+     *
+     * @param propText
+     * @return Properties
+     * @throws IOException
+     */
+    private static Properties getProperties(final String propText) throws IOException {
+        final Properties p = new Properties();
+        if (propText != null) {
+            p.load(new ByteArrayInputStream(propText.replace(';', '\n').getBytes(StandardCharsets.ISO_8859_1)));
+        }
+        return p;
+    }
 
     /**
-     * <p>
-     * Create and return a new <code>BasicDataSource</code> instance. If no instance can be created, return
+     * Parse list of property values from a delimited string
+     *
+     * @param value
+     *            delimited list of values
+     * @param delimiter
+     *            character used to separate values in the list
+     * @return String Collection of values
+     */
+    private static Collection<String> parseList(final String value, final char delimiter) {
+        final StringTokenizer tokenizer = new StringTokenizer(value, Character.toString(delimiter));
+        final Collection<String> tokens = new ArrayList<>(tokenizer.countTokens());
+        while (tokenizer.hasMoreTokens()) {
+            tokens.add(tokenizer.nextToken());
+        }
+        return tokens;
+    }
+
+    /**
+     * Creates and return a new <code>BasicDataSource</code> instance. If no instance can be created, return
      * <code>null</code> instead.
-     * </p>
      *
      * @param obj
      *            The possibly null object containing location or reference information that can be used in creating an
@@ -217,22 +382,17 @@ public class BasicDataSourceFactory implements ObjectFactory {
         }
 
         // Check property names and log warnings about obsolete and / or unknown properties
-        final List<String> warnings = new ArrayList<>();
+        final List<String> warnMessages = new ArrayList<>();
         final List<String> infoMessages = new ArrayList<>();
-        validatePropertyNames(ref, name, warnings, infoMessages);
-        for (final String warning : warnings) {
-            log.warn(warning);
-        }
-        for (final String infoMessage : infoMessages) {
-            log.info(infoMessage);
-        }
+        validatePropertyNames(ref, name, warnMessages, infoMessages);
+        warnMessages.forEach(log::warn);
+        infoMessages.forEach(log::info);
 
         final Properties properties = new Properties();
         for (final String propertyName : ALL_PROPERTIES) {
             final RefAddr ra = ref.get(propertyName);
             if (ra != null) {
-                final String propertyValue = ra.getContent().toString();
-                properties.setProperty(propertyName, propertyValue);
+                properties.setProperty(propertyName, Objects.toString(ra.getContent(), null));
             }
         }
 
@@ -247,12 +407,12 @@ public class BasicDataSourceFactory implements ObjectFactory {
      *            Reference to check properties of
      * @param name
      *            Name provided to getObject
-     * @param warnings
+     * @param warnMessages
      *            container for warning messages
      * @param infoMessages
      *            container for info messages
      */
-    private void validatePropertyNames(final Reference ref, final Name name, final List<String> warnings,
+    private void validatePropertyNames(final Reference ref, final Name name, final List<String> warnMessages,
             final List<String> infoMessages) {
         final List<String> allPropsAsList = Arrays.asList(ALL_PROPERTIES);
         final String nameString = name != null ? "Name = " + name.toString() + " " : "";
@@ -261,11 +421,11 @@ public class BasicDataSourceFactory implements ObjectFactory {
                 final RefAddr ra = ref.get(propertyName);
                 if (ra != null && !allPropsAsList.contains(ra.getType())) {
                     final StringBuilder stringBuilder = new StringBuilder(nameString);
-                    final String propertyValue = ra.getContent().toString();
+                    final String propertyValue = Objects.toString(ra.getContent(), null);
                     stringBuilder.append(NUPROP_WARNTEXT.get(propertyName)).append(" You have set value of \"")
                             .append(propertyValue).append("\" for \"").append(propertyName)
                             .append("\" property, which is being ignored.");
-                    warnings.add(stringBuilder.toString());
+                    warnMessages.add(stringBuilder.toString());
                 }
             }
         }
@@ -278,7 +438,7 @@ public class BasicDataSourceFactory implements ObjectFactory {
             // and it is not in the "silent" list, tell user we are ignoring it.
             if (!(allPropsAsList.contains(propertyName) || NUPROP_WARNTEXT.containsKey(propertyName)
                     || SILENT_PROPERTIES.contains(propertyName))) {
-                final String propertyValue = ra.getContent().toString();
+                final String propertyValue = Objects.toString(ra.getContent(), null);
                 final StringBuilder stringBuilder = new StringBuilder(nameString);
                 stringBuilder.append("Ignoring unknown property: ").append("value of \"").append(propertyValue)
                         .append("\" for \"").append(propertyName).append("\" property");
@@ -286,321 +446,4 @@ public class BasicDataSourceFactory implements ObjectFactory {
             }
         }
     }
-
-    /**
-     * Creates and configures a {@link BasicDataSource} instance based on the given properties.
-     *
-     * @param properties
-     *            The data source configuration properties.
-     * @return A new a {@link BasicDataSource} instance based on the given properties.
-     * @throws Exception
-     *             Thrown when an error occurs creating the data source.
-     */
-    public static BasicDataSource createDataSource(final Properties properties) throws Exception {
-        final BasicDataSource dataSource = new BasicDataSource();
-        String value = properties.getProperty(PROP_DEFAULT_AUTO_COMMIT);
-        if (value != null) {
-            dataSource.setDefaultAutoCommit(Boolean.valueOf(value));
-        }
-
-        value = properties.getProperty(PROP_DEFAULT_READ_ONLY);
-        if (value != null) {
-            dataSource.setDefaultReadOnly(Boolean.valueOf(value));
-        }
-
-        value = properties.getProperty(PROP_DEFAULT_TRANSACTION_ISOLATION);
-        if (value != null) {
-            int level = PoolableConnectionFactory.UNKNOWN_TRANSACTION_ISOLATION;
-            if ("NONE".equalsIgnoreCase(value)) {
-                level = Connection.TRANSACTION_NONE;
-            } else if ("READ_COMMITTED".equalsIgnoreCase(value)) {
-                level = Connection.TRANSACTION_READ_COMMITTED;
-            } else if ("READ_UNCOMMITTED".equalsIgnoreCase(value)) {
-                level = Connection.TRANSACTION_READ_UNCOMMITTED;
-            } else if ("REPEATABLE_READ".equalsIgnoreCase(value)) {
-                level = Connection.TRANSACTION_REPEATABLE_READ;
-            } else if ("SERIALIZABLE".equalsIgnoreCase(value)) {
-                level = Connection.TRANSACTION_SERIALIZABLE;
-            } else {
-                try {
-                    level = Integer.parseInt(value);
-                } catch (final NumberFormatException e) {
-                    System.err.println("Could not parse defaultTransactionIsolation: " + value);
-                    System.err.println("WARNING: defaultTransactionIsolation not set");
-                    System.err.println("using default value of database driver");
-                    level = PoolableConnectionFactory.UNKNOWN_TRANSACTION_ISOLATION;
-                }
-            }
-            dataSource.setDefaultTransactionIsolation(level);
-        }
-
-        value = properties.getProperty(PROP_DEFAULT_CATALOG);
-        if (value != null) {
-            dataSource.setDefaultCatalog(value);
-        }
-
-        value = properties.getProperty(PROP_DEFAULT_SCHEMA);
-        if (value != null) {
-            dataSource.setDefaultSchema(value);
-        }
-
-        value = properties.getProperty(PROP_CACHE_STATE);
-        if (value != null) {
-            dataSource.setCacheState(Boolean.parseBoolean(value));
-        }
-
-        value = properties.getProperty(PROP_DRIVER_CLASS_NAME);
-        if (value != null) {
-            dataSource.setDriverClassName(value);
-        }
-
-        value = properties.getProperty(PROP_LIFO);
-        if (value != null) {
-            dataSource.setLifo(Boolean.parseBoolean(value));
-        }
-
-        value = properties.getProperty(PROP_MAX_TOTAL);
-        if (value != null) {
-            dataSource.setMaxTotal(Integer.parseInt(value));
-        }
-
-        value = properties.getProperty(PROP_MAX_IDLE);
-        if (value != null) {
-            dataSource.setMaxIdle(Integer.parseInt(value));
-        }
-
-        value = properties.getProperty(PROP_MIN_IDLE);
-        if (value != null) {
-            dataSource.setMinIdle(Integer.parseInt(value));
-        }
-
-        value = properties.getProperty(PROP_INITIAL_SIZE);
-        if (value != null) {
-            dataSource.setInitialSize(Integer.parseInt(value));
-        }
-
-        value = properties.getProperty(PROP_MAX_WAIT_MILLIS);
-        if (value != null) {
-            dataSource.setMaxWaitMillis(Long.parseLong(value));
-        }
-
-        value = properties.getProperty(PROP_TEST_ON_CREATE);
-        if (value != null) {
-            dataSource.setTestOnCreate(Boolean.parseBoolean(value));
-        }
-
-        value = properties.getProperty(PROP_TEST_ON_BORROW);
-        if (value != null) {
-            dataSource.setTestOnBorrow(Boolean.parseBoolean(value));
-        }
-
-        value = properties.getProperty(PROP_TEST_ON_RETURN);
-        if (value != null) {
-            dataSource.setTestOnReturn(Boolean.parseBoolean(value));
-        }
-
-        value = properties.getProperty(PROP_TIME_BETWEEN_EVICTION_RUNS_MILLIS);
-        if (value != null) {
-            dataSource.setTimeBetweenEvictionRunsMillis(Long.parseLong(value));
-        }
-
-        value = properties.getProperty(PROP_NUM_TESTS_PER_EVICTION_RUN);
-        if (value != null) {
-            dataSource.setNumTestsPerEvictionRun(Integer.parseInt(value));
-        }
-
-        value = properties.getProperty(PROP_MIN_EVICTABLE_IDLE_TIME_MILLIS);
-        if (value != null) {
-            dataSource.setMinEvictableIdleTimeMillis(Long.parseLong(value));
-        }
-
-        value = properties.getProperty(PROP_SOFT_MIN_EVICTABLE_IDLE_TIME_MILLIS);
-        if (value != null) {
-            dataSource.setSoftMinEvictableIdleTimeMillis(Long.parseLong(value));
-        }
-
-        value = properties.getProperty(PROP_EVICTION_POLICY_CLASS_NAME);
-        if (value != null) {
-            dataSource.setEvictionPolicyClassName(value);
-        }
-
-        value = properties.getProperty(PROP_TEST_WHILE_IDLE);
-        if (value != null) {
-            dataSource.setTestWhileIdle(Boolean.parseBoolean(value));
-        }
-
-        value = properties.getProperty(PROP_PASSWORD);
-        if (value != null) {
-            dataSource.setPassword(value);
-        }
-
-        value = properties.getProperty(PROP_URL);
-        if (value != null) {
-            dataSource.setUrl(value);
-        }
-
-        value = properties.getProperty(PROP_USER_NAME);
-        if (value != null) {
-            dataSource.setUsername(value);
-        }
-
-        value = properties.getProperty(PROP_VALIDATION_QUERY);
-        if (value != null) {
-            dataSource.setValidationQuery(value);
-        }
-
-        value = properties.getProperty(PROP_VALIDATION_QUERY_TIMEOUT);
-        if (value != null) {
-            dataSource.setValidationQueryTimeout(Integer.parseInt(value));
-        }
-
-        value = properties.getProperty(PROP_ACCESS_TO_UNDERLYING_CONNECTION_ALLOWED);
-        if (value != null) {
-            dataSource.setAccessToUnderlyingConnectionAllowed(Boolean.parseBoolean(value));
-        }
-
-        value = properties.getProperty(PROP_REMOVE_ABANDONED_ON_BORROW);
-        if (value != null) {
-            dataSource.setRemoveAbandonedOnBorrow(Boolean.parseBoolean(value));
-        }
-
-        value = properties.getProperty(PROP_REMOVE_ABANDONED_ON_MAINTENANCE);
-        if (value != null) {
-            dataSource.setRemoveAbandonedOnMaintenance(Boolean.parseBoolean(value));
-        }
-
-        value = properties.getProperty(PROP_REMOVE_ABANDONED_TIMEOUT);
-        if (value != null) {
-            dataSource.setRemoveAbandonedTimeout(Integer.parseInt(value));
-        }
-
-        value = properties.getProperty(PROP_LOG_ABANDONED);
-        if (value != null) {
-            dataSource.setLogAbandoned(Boolean.parseBoolean(value));
-        }
-
-        value = properties.getProperty(PROP_ABANDONED_USAGE_TRACKING);
-        if (value != null) {
-            dataSource.setAbandonedUsageTracking(Boolean.parseBoolean(value));
-        }
-
-        value = properties.getProperty(PROP_POOL_PREPARED_STATEMENTS);
-        if (value != null) {
-            dataSource.setPoolPreparedStatements(Boolean.parseBoolean(value));
-        }
-
-        value = properties.getProperty(PROP_CLEAR_STATEMENT_POOL_ON_RETURN);
-        if (value != null) {
-            dataSource.setClearStatementPoolOnReturn(Boolean.parseBoolean(value));
-        }
-
-        value = properties.getProperty(PROP_MAX_OPEN_PREPARED_STATEMENTS);
-        if (value != null) {
-            dataSource.setMaxOpenPreparedStatements(Integer.parseInt(value));
-        }
-
-        value = properties.getProperty(PROP_CONNECTION_INIT_SQLS);
-        if (value != null) {
-            dataSource.setConnectionInitSqls(parseList(value, ';'));
-        }
-
-        value = properties.getProperty(PROP_CONNECTION_PROPERTIES);
-        if (value != null) {
-            final Properties p = getProperties(value);
-            final Enumeration<?> e = p.propertyNames();
-            while (e.hasMoreElements()) {
-                final String propertyName = (String) e.nextElement();
-                dataSource.addConnectionProperty(propertyName, p.getProperty(propertyName));
-            }
-        }
-
-        value = properties.getProperty(PROP_MAX_CONN_LIFETIME_MILLIS);
-        if (value != null) {
-            dataSource.setMaxConnLifetimeMillis(Long.parseLong(value));
-        }
-
-        value = properties.getProperty(PROP_LOG_EXPIRED_CONNECTIONS);
-        if (value != null) {
-            dataSource.setLogExpiredConnections(Boolean.parseBoolean(value));
-        }
-
-        value = properties.getProperty(PROP_JMX_NAME);
-        if (value != null) {
-            dataSource.setJmxName(value);
-        }
-
-        value = properties.getProperty(PROP_ENABLE_AUTO_COMMIT_ON_RETURN);
-        if (value != null) {
-            dataSource.setAutoCommitOnReturn(Boolean.parseBoolean(value));
-        }
-
-        value = properties.getProperty(PROP_ROLLBACK_ON_RETURN);
-        if (value != null) {
-            dataSource.setRollbackOnReturn(Boolean.parseBoolean(value));
-        }
-
-        value = properties.getProperty(PROP_DEFAULT_QUERY_TIMEOUT);
-        if (value != null) {
-            dataSource.setDefaultQueryTimeout(Integer.valueOf(value));
-        }
-
-        value = properties.getProperty(PROP_FAST_FAIL_VALIDATION);
-        if (value != null) {
-            dataSource.setFastFailValidation(Boolean.parseBoolean(value));
-        }
-
-        value = properties.getProperty(PROP_DISCONNECTION_SQL_CODES);
-        if (value != null) {
-            dataSource.setDisconnectionSqlCodes(parseList(value, ','));
-        }
-
-        value = properties.getProperty(PROP_CONNECTION_FACTORY_CLASS_NAME);
-        if (value != null) {
-            dataSource.setConnectionFactoryClassName(value);
-        }
-
-        // DBCP-215
-        // Trick to make sure that initialSize connections are created
-        if (dataSource.getInitialSize() > 0) {
-            dataSource.getLogWriter();
-        }
-
-        // Return the configured DataSource instance
-        return dataSource;
-    }
-
-    /**
-     * <p>
-     * Parse properties from the string. Format of the string must be [propertyName=property;]*
-     * <p>
-     *
-     * @param propText
-     * @return Properties
-     * @throws Exception
-     */
-    private static Properties getProperties(final String propText) throws Exception {
-        final Properties p = new Properties();
-        if (propText != null) {
-            p.load(new ByteArrayInputStream(propText.replace(';', '\n').getBytes(StandardCharsets.ISO_8859_1)));
-        }
-        return p;
-    }
-
-    /**
-     * Parse list of property values from a delimited string
-     *
-     * @param value
-     *            delimited list of values
-     * @param delimiter
-     *            character used to separate values in the list
-     * @return String Collection of values
-     */
-    private static Collection<String> parseList(final String value, final char delimiter) {
-        final StringTokenizer tokenizer = new StringTokenizer(value, Character.toString(delimiter));
-        final Collection<String> tokens = new ArrayList<>(tokenizer.countTokens());
-        while (tokenizer.hasMoreTokens()) {
-            tokens.add(tokenizer.nextToken());
-        }
-        return tokens;
-    }
 }
diff --git a/java/org/apache/tomcat/dbcp/dbcp2/BasicDataSourceMXBean.java b/java/org/apache/tomcat/dbcp/dbcp2/BasicDataSourceMXBean.java
index 5a637da..06798a3 100644
--- a/java/org/apache/tomcat/dbcp/dbcp2/BasicDataSourceMXBean.java
+++ b/java/org/apache/tomcat/dbcp/dbcp2/BasicDataSourceMXBean.java
@@ -16,337 +16,23 @@
  */
 package org.apache.tomcat.dbcp.dbcp2;
 
-import java.sql.SQLException;
-
 /**
- * Defines the methods that will be made available via JMX.
+ * Interface to keep API compatibility. Methods listed here are not made available to
+ * <a href="https://docs.oracle.com/javase/8/docs/technotes/guides/management/agent.html">JMX</a>.
+ * <p>
+ * As of 2.9.0, this interface extends {@link DataSourceMXBean}.
+ * </p>
  *
  * @since 2.0
  */
-public interface BasicDataSourceMXBean {
-
-    /**
-     * See {@link BasicDataSource#getAbandonedUsageTracking()}
-     *
-     * @return {@link BasicDataSource#getAbandonedUsageTracking()}
-     */
-    boolean getAbandonedUsageTracking();
-
-    /**
-     * See {@link BasicDataSource#getDefaultAutoCommit()}
-     *
-     * @return {@link BasicDataSource#getDefaultAutoCommit()}
-     */
-    Boolean getDefaultAutoCommit();
-
-    /**
-     * See {@link BasicDataSource#getDefaultReadOnly()}
-     *
-     * @return {@link BasicDataSource#getDefaultReadOnly()}
-     */
-    Boolean getDefaultReadOnly();
-
-    /**
-     * See {@link BasicDataSource#getDefaultTransactionIsolation()}
-     *
-     * @return {@link BasicDataSource#getDefaultTransactionIsolation()}
-     */
-    int getDefaultTransactionIsolation();
-
-    /**
-     * See {@link BasicDataSource#getDefaultCatalog()}
-     *
-     * @return {@link BasicDataSource#getDefaultCatalog()}
-     */
-    String getDefaultCatalog();
-
-    /**
-     * See {@link BasicDataSource#getDefaultSchema()}
-     *
-     * @return {@link BasicDataSource#getDefaultSchema()}
-     * @since 2.5.0
-     */
-    default String getDefaultSchema() {
-        return null;
-    }
-
-    /**
-     * See {@link BasicDataSource#getCacheState()}
-     *
-     * @return {@link BasicDataSource#getCacheState()}
-     */
-    boolean getCacheState();
-
-    /**
-     * See {@link BasicDataSource#getDriverClassName()}
-     *
-     * @return {@link BasicDataSource#getDriverClassName()}
-     */
-    String getDriverClassName();
-
-    /**
-     * See {@link BasicDataSource#getLifo()}
-     *
-     * @return {@link BasicDataSource#getLifo()}
-     */
-    boolean getLifo();
-
-    /**
-     * See {@link BasicDataSource#getMaxTotal()}
-     *
-     * @return {@link BasicDataSource#getMaxTotal()}
-     */
-    int getMaxTotal();
-
-    /**
-     * See {@link BasicDataSource#getMaxIdle()}
-     *
-     * @return {@link BasicDataSource#getMaxIdle()}
-     */
-    int getMaxIdle();
-
-    /**
-     * See {@link BasicDataSource#getMinIdle()}
-     *
-     * @return {@link BasicDataSource#getMinIdle()}
-     */
-    int getMinIdle();
-
-    /**
-     * See {@link BasicDataSource#getInitialSize()}
-     *
-     * @return {@link BasicDataSource#getInitialSize()}
-     */
-    int getInitialSize();
-
-    /**
-     * See {@link BasicDataSource#getMaxWaitMillis()}
-     *
-     * @return {@link BasicDataSource#getMaxWaitMillis()}
-     */
-    long getMaxWaitMillis();
-
-    /**
-     * See {@link BasicDataSource#isPoolPreparedStatements()}
-     *
-     * @return {@link BasicDataSource#isPoolPreparedStatements()}
-     */
-    boolean isPoolPreparedStatements();
-
-    /**
-     * See {@link BasicDataSource#isClearStatementPoolOnReturn()}
-     *
-     * @return {@link BasicDataSource#isClearStatementPoolOnReturn()}
-     * @since 2.8.0
-     */
-    default boolean isClearStatementPoolOnReturn() {
-        return false;
-    }
-
-    /**
-     * See {@link BasicDataSource#getMaxOpenPreparedStatements()}
-     *
-     * @return {@link BasicDataSource#getMaxOpenPreparedStatements()}
-     */
-    int getMaxOpenPreparedStatements();
-
-    /**
-     * See {@link BasicDataSource#getTestOnCreate()}
-     *
-     * @return {@link BasicDataSource#getTestOnCreate()}
-     */
-    boolean getTestOnCreate();
-
-    /**
-     * See {@link BasicDataSource#getTestOnBorrow()}
-     *
-     * @return {@link BasicDataSource#getTestOnBorrow()}
-     */
-    boolean getTestOnBorrow();
-
-    /**
-     * See {@link BasicDataSource#getTimeBetweenEvictionRunsMillis()}
-     *
-     * @return {@link BasicDataSource#getTimeBetweenEvictionRunsMillis()}
-     */
-    long getTimeBetweenEvictionRunsMillis();
-
-    /**
-     * See {@link BasicDataSource#getNumTestsPerEvictionRun()}
-     *
-     * @return {@link BasicDataSource#getNumTestsPerEvictionRun()}
-     */
-    int getNumTestsPerEvictionRun();
-
-    /**
-     * See {@link BasicDataSource#getMinEvictableIdleTimeMillis()}
-     *
-     * @return {@link BasicDataSource#getMinEvictableIdleTimeMillis()}
-     */
-    long getMinEvictableIdleTimeMillis();
-
-    /**
-     * See {@link BasicDataSource#getSoftMinEvictableIdleTimeMillis()}
-     *
-     * @return {@link BasicDataSource#getSoftMinEvictableIdleTimeMillis()}
-     */
-    long getSoftMinEvictableIdleTimeMillis();
-
-    /**
-     * See {@link BasicDataSource#getTestWhileIdle()}
-     *
-     * @return {@link BasicDataSource#getTestWhileIdle()}
-     */
-    boolean getTestWhileIdle();
-
-    /**
-     * See {@link BasicDataSource#getNumActive()}
-     *
-     * @return {@link BasicDataSource#getNumActive()}
-     */
-    int getNumActive();
-
-    /**
-     * See {@link BasicDataSource#getNumIdle()}
-     *
-     * @return {@link BasicDataSource#getNumIdle()}
-     */
-    int getNumIdle();
+public interface BasicDataSourceMXBean extends DataSourceMXBean {
 
     /**
      * See {@link BasicDataSource#getPassword()}
      *
      * @return {@link BasicDataSource#getPassword()}
+     * @deprecated exposing password via JMX is an Information Exposure issue.
      */
+    @Deprecated
     String getPassword();
-
-    /**
-     * See {@link BasicDataSource#getUrl()}
-     *
-     * @return {@link BasicDataSource#getUrl()}
-     */
-    String getUrl();
-
-    /**
-     * See {@link BasicDataSource#getUsername()}
-     *
-     * @return {@link BasicDataSource#getUsername()}
-     */
-    String getUsername();
-
-    /**
-     * See {@link BasicDataSource#getValidationQuery()}
-     *
-     * @return {@link BasicDataSource#getValidationQuery()}
-     */
-    String getValidationQuery();
-
-    /**
-     * See {@link BasicDataSource#getValidationQueryTimeout()}
-     *
-     * @return {@link BasicDataSource#getValidationQueryTimeout()}
-     */
-    int getValidationQueryTimeout();
-
-    /**
-     * See {@link BasicDataSource#getConnectionInitSqlsAsArray()}
-     *
-     * @return {@link BasicDataSource#getConnectionInitSqlsAsArray()}
-     */
-    String[] getConnectionInitSqlsAsArray();
-
-    /**
-     * See {@link BasicDataSource#isAccessToUnderlyingConnectionAllowed()}
-     *
-     * @return {@link BasicDataSource#isAccessToUnderlyingConnectionAllowed()}
-     */
-    boolean isAccessToUnderlyingConnectionAllowed();
-
-    /**
-     * See {@link BasicDataSource#getMaxConnLifetimeMillis()}
-     *
-     * @return {@link BasicDataSource#getMaxConnLifetimeMillis()}
-     */
-    long getMaxConnLifetimeMillis();
-
-    /**
-     * See {@link BasicDataSource#getLogExpiredConnections()}
-     *
-     * @return {@link BasicDataSource#getLogExpiredConnections()}
-     * @since 2.1
-     */
-    boolean getLogExpiredConnections();
-
-    /**
-     * See {@link BasicDataSource#getRemoveAbandonedOnBorrow()}
-     *
-     * @return {@link BasicDataSource#getRemoveAbandonedOnBorrow()}
-     */
-    boolean getRemoveAbandonedOnBorrow();
-
-    /**
-     * See {@link BasicDataSource#getRemoveAbandonedOnMaintenance()}
-     *
-     * @return {@link BasicDataSource#getRemoveAbandonedOnMaintenance()}
-     */
-    boolean getRemoveAbandonedOnMaintenance();
-
-    /**
-     * See {@link BasicDataSource#getRemoveAbandonedTimeout()}
-     *
-     * @return {@link BasicDataSource#getRemoveAbandonedTimeout()}
-     */
-    int getRemoveAbandonedTimeout();
-
-    /**
-     * See {@link BasicDataSource#getLogAbandoned()}
-     *
-     * @return {@link BasicDataSource#getLogAbandoned()}
-     */
-    boolean getLogAbandoned();
-
-    /**
-     * See {@link BasicDataSource#isClosed()}
-     *
-     * @return {@link BasicDataSource#isClosed()}
-     */
-    boolean isClosed();
-
-    /**
-     * See {@link BasicDataSource#getFastFailValidation()}
-     *
-     * @return {@link BasicDataSource#getFastFailValidation()}
-     * @since 2.1
-     */
-    boolean getFastFailValidation();
-
-    /**
-     * See {@link BasicDataSource#getDisconnectionSqlCodesAsArray()}
-     *
-     * @return {@link BasicDataSource#getDisconnectionSqlCodesAsArray()}
-     * @since 2.1
-     */
-    String[] getDisconnectionSqlCodesAsArray();
-
-    /**
-     * See {@link BasicDataSource#start()}
-     *
-     * @throws SQLException if an error occurs initializing the datasource
-     *
-     * @since 2.8.0
-     */
-    default void start() throws SQLException {
-        // do nothing
-    }
-
-    /**
-     * See {@link BasicDataSource#restart()}
-     *
-     * @throws SQLException if an error occurs initializing the datasource
-     *
-     * @since 2.8.0
-     */
-    default void restart() throws SQLException {
-        // do nothing by default?
-    }
 }
diff --git a/java/org/apache/tomcat/dbcp/dbcp2/ConnectionFactoryFactory.java b/java/org/apache/tomcat/dbcp/dbcp2/ConnectionFactoryFactory.java
index ad1e870..bd5c244 100644
--- a/java/org/apache/tomcat/dbcp/dbcp2/ConnectionFactoryFactory.java
+++ b/java/org/apache/tomcat/dbcp/dbcp2/ConnectionFactoryFactory.java
@@ -44,7 +44,7 @@ class ConnectionFactoryFactory {
         // Set up the driver connection factory we will use
         final String user = basicDataSource.getUsername();
         if (user != null) {
-            connectionProperties.put("user", user);
+            connectionProperties.put(Constants.KEY_USER, user);
         } else {
             basicDataSource.log("DBCP DataSource configured without a 'username'");
         }
diff --git a/java/org/apache/tomcat/dbcp/dbcp2/Constants.java b/java/org/apache/tomcat/dbcp/dbcp2/Constants.java
index d9278cf..da83187 100644
--- a/java/org/apache/tomcat/dbcp/dbcp2/Constants.java
+++ b/java/org/apache/tomcat/dbcp/dbcp2/Constants.java
@@ -17,7 +17,7 @@
 package org.apache.tomcat.dbcp.dbcp2;
 
 /**
- * Constants for use with JMX.
+ * Constants.
  *
  * @since 2.0
  */
@@ -31,4 +31,18 @@ public class Constants {
 
     public static final String JMX_STATEMENT_POOL_BASE_EXT = JMX_CONNECTION_BASE_EXT;
     public static final String JMX_STATEMENT_POOL_PREFIX = ",statementpool=statements";
+
+    /**
+     * JDBC properties and URL key for passwords.
+     *
+     * @since 2.9.0
+     */
+    public static final String KEY_PASSWORD = "password";
+
+    /**
+     * JDBC properties and URL key for users.
+     *
+     * @since 2.9.0
+     */
+    public static final String KEY_USER = "user";
 }
diff --git a/java/org/apache/tomcat/dbcp/dbcp2/DataSourceConnectionFactory.java b/java/org/apache/tomcat/dbcp/dbcp2/DataSourceConnectionFactory.java
index 5fdceab..3ab0a7a 100644
--- a/java/org/apache/tomcat/dbcp/dbcp2/DataSourceConnectionFactory.java
+++ b/java/org/apache/tomcat/dbcp/dbcp2/DataSourceConnectionFactory.java
@@ -106,6 +106,6 @@ public class DataSourceConnectionFactory implements ConnectionFactory {
      * @since 2.6.0
      */
     public char[] getUserPassword() {
-        return userPassword;
+        return userPassword == null ? null : userPassword.clone();
     }
 }
diff --git a/java/org/apache/tomcat/dbcp/dbcp2/BasicDataSourceMXBean.java b/java/org/apache/tomcat/dbcp/dbcp2/DataSourceMXBean.java
similarity index 62%
copy from java/org/apache/tomcat/dbcp/dbcp2/BasicDataSourceMXBean.java
copy to java/org/apache/tomcat/dbcp/dbcp2/DataSourceMXBean.java
index 5a637da..7315c58 100644
--- a/java/org/apache/tomcat/dbcp/dbcp2/BasicDataSourceMXBean.java
+++ b/java/org/apache/tomcat/dbcp/dbcp2/DataSourceMXBean.java
@@ -19,334 +19,328 @@ package org.apache.tomcat.dbcp.dbcp2;
 import java.sql.SQLException;
 
 /**
- * Defines the methods that will be made available via JMX.
+ * Defines the methods that will be made available via
+ * <a href="https://docs.oracle.com/javase/8/docs/technotes/guides/management/agent.html">JMX</a>.
  *
- * @since 2.0
+ * @since 2.9.0
  */
-public interface BasicDataSourceMXBean {
+public interface DataSourceMXBean {
 
     /**
-     * See {@link BasicDataSource#getAbandonedUsageTracking()}
+     * See {@link BasicDataSource#getAbandonedUsageTracking()}.
      *
      * @return {@link BasicDataSource#getAbandonedUsageTracking()}
      */
     boolean getAbandonedUsageTracking();
 
     /**
-     * See {@link BasicDataSource#getDefaultAutoCommit()}
+     * See {@link BasicDataSource#getCacheState()}.
      *
-     * @return {@link BasicDataSource#getDefaultAutoCommit()}
+     * @return {@link BasicDataSource#getCacheState()}.
      */
-    Boolean getDefaultAutoCommit();
+    boolean getCacheState();
 
     /**
-     * See {@link BasicDataSource#getDefaultReadOnly()}
+     * See {@link BasicDataSource#getConnectionInitSqlsAsArray()}.
      *
-     * @return {@link BasicDataSource#getDefaultReadOnly()}
+     * @return {@link BasicDataSource#getConnectionInitSqlsAsArray()}.
      */
-    Boolean getDefaultReadOnly();
+    String[] getConnectionInitSqlsAsArray();
 
     /**
-     * See {@link BasicDataSource#getDefaultTransactionIsolation()}
+     * See {@link BasicDataSource#getDefaultAutoCommit()}.
      *
-     * @return {@link BasicDataSource#getDefaultTransactionIsolation()}
+     * @return {@link BasicDataSource#getDefaultAutoCommit()}.
      */
-    int getDefaultTransactionIsolation();
+    Boolean getDefaultAutoCommit();
 
     /**
-     * See {@link BasicDataSource#getDefaultCatalog()}
+     * See {@link BasicDataSource#getDefaultCatalog()}.
      *
-     * @return {@link BasicDataSource#getDefaultCatalog()}
+     * @return {@link BasicDataSource#getDefaultCatalog()}.
      */
     String getDefaultCatalog();
 
     /**
-     * See {@link BasicDataSource#getDefaultSchema()}
-     *
-     * @return {@link BasicDataSource#getDefaultSchema()}
-     * @since 2.5.0
-     */
-    default String getDefaultSchema() {
-        return null;
-    }
-
-    /**
-     * See {@link BasicDataSource#getCacheState()}
+     * See {@link BasicDataSource#getDefaultReadOnly()}.
      *
-     * @return {@link BasicDataSource#getCacheState()}
+     * @return {@link BasicDataSource#getDefaultReadOnly()}.
      */
-    boolean getCacheState();
+    Boolean getDefaultReadOnly();
 
     /**
-     * See {@link BasicDataSource#getDriverClassName()}
+     * See {@link BasicDataSource#getDefaultSchema()}.
      *
-     * @return {@link BasicDataSource#getDriverClassName()}
+     * @return {@link BasicDataSource#getDefaultSchema()}.
+     * @since 2.5.0
      */
-    String getDriverClassName();
+    default String getDefaultSchema() {
+        return null;
+    }
 
     /**
-     * See {@link BasicDataSource#getLifo()}
+     * See {@link BasicDataSource#getDefaultTransactionIsolation()}.
      *
-     * @return {@link BasicDataSource#getLifo()}
+     * @return {@link BasicDataSource#getDefaultTransactionIsolation()}.
      */
-    boolean getLifo();
+    int getDefaultTransactionIsolation();
 
     /**
-     * See {@link BasicDataSource#getMaxTotal()}
+     * See {@link BasicDataSource#getDisconnectionSqlCodesAsArray()}.
      *
-     * @return {@link BasicDataSource#getMaxTotal()}
+     * @return {@link BasicDataSource#getDisconnectionSqlCodesAsArray()}.
+     * @since 2.1
      */
-    int getMaxTotal();
+    String[] getDisconnectionSqlCodesAsArray();
 
     /**
-     * See {@link BasicDataSource#getMaxIdle()}
+     * See {@link BasicDataSource#getDriverClassName()}.
      *
-     * @return {@link BasicDataSource#getMaxIdle()}
+     * @return {@link BasicDataSource#getDriverClassName()}.
      */
-    int getMaxIdle();
+    String getDriverClassName();
 
     /**
-     * See {@link BasicDataSource#getMinIdle()}
+     * See {@link BasicDataSource#getFastFailValidation()}.
      *
-     * @return {@link BasicDataSource#getMinIdle()}
+     * @return {@link BasicDataSource#getFastFailValidation()}.
+     * @since 2.1
      */
-    int getMinIdle();
+    boolean getFastFailValidation();
 
     /**
-     * See {@link BasicDataSource#getInitialSize()}
+     * See {@link BasicDataSource#getInitialSize()}.
      *
-     * @return {@link BasicDataSource#getInitialSize()}
+     * @return {@link BasicDataSource#getInitialSize()}.
      */
     int getInitialSize();
 
     /**
-     * See {@link BasicDataSource#getMaxWaitMillis()}
+     * See {@link BasicDataSource#getLifo()}.
      *
-     * @return {@link BasicDataSource#getMaxWaitMillis()}
+     * @return {@link BasicDataSource#getLifo()}.
      */
-    long getMaxWaitMillis();
+    boolean getLifo();
 
     /**
-     * See {@link BasicDataSource#isPoolPreparedStatements()}
+     * See {@link BasicDataSource#getLogAbandoned()}.
      *
-     * @return {@link BasicDataSource#isPoolPreparedStatements()}
+     * @return {@link BasicDataSource#getLogAbandoned()}.
      */
-    boolean isPoolPreparedStatements();
+    boolean getLogAbandoned();
 
     /**
-     * See {@link BasicDataSource#isClearStatementPoolOnReturn()}
+     * See {@link BasicDataSource#getLogExpiredConnections()}.
      *
-     * @return {@link BasicDataSource#isClearStatementPoolOnReturn()}
-     * @since 2.8.0
+     * @return {@link BasicDataSource#getLogExpiredConnections()}.
+     * @since 2.1
      */
-    default boolean isClearStatementPoolOnReturn() {
-        return false;
-    }
+    boolean getLogExpiredConnections();
 
     /**
-     * See {@link BasicDataSource#getMaxOpenPreparedStatements()}
+     * See {@link BasicDataSource#getMaxConnLifetimeMillis()}.
      *
-     * @return {@link BasicDataSource#getMaxOpenPreparedStatements()}
+     * @return {@link BasicDataSource#getMaxConnLifetimeMillis()}.
      */
-    int getMaxOpenPreparedStatements();
+    long getMaxConnLifetimeMillis();
 
     /**
-     * See {@link BasicDataSource#getTestOnCreate()}
+     * See {@link BasicDataSource#getMaxIdle()}.
      *
-     * @return {@link BasicDataSource#getTestOnCreate()}
+     * @return {@link BasicDataSource#getMaxIdle()}.
      */
-    boolean getTestOnCreate();
+    int getMaxIdle();
 
     /**
-     * See {@link BasicDataSource#getTestOnBorrow()}
+     * See {@link BasicDataSource#getMaxOpenPreparedStatements()}.
      *
-     * @return {@link BasicDataSource#getTestOnBorrow()}
+     * @return {@link BasicDataSource#getMaxOpenPreparedStatements()}.
      */
-    boolean getTestOnBorrow();
+    int getMaxOpenPreparedStatements();
 
     /**
-     * See {@link BasicDataSource#getTimeBetweenEvictionRunsMillis()}
+     * See {@link BasicDataSource#getMaxTotal()}.
      *
-     * @return {@link BasicDataSource#getTimeBetweenEvictionRunsMillis()}
+     * @return {@link BasicDataSource#getMaxTotal()}.
      */
-    long getTimeBetweenEvictionRunsMillis();
+    int getMaxTotal();
 
     /**
-     * See {@link BasicDataSource#getNumTestsPerEvictionRun()}
+     * See {@link BasicDataSource#getMaxWaitMillis()}.
      *
-     * @return {@link BasicDataSource#getNumTestsPerEvictionRun()}
+     * @return {@link BasicDataSource#getMaxWaitMillis()}.
      */
-    int getNumTestsPerEvictionRun();
+    long getMaxWaitMillis();
 
     /**
-     * See {@link BasicDataSource#getMinEvictableIdleTimeMillis()}
+     * See {@link BasicDataSource#getMinEvictableIdleTimeMillis()}.
      *
-     * @return {@link BasicDataSource#getMinEvictableIdleTimeMillis()}
+     * @return {@link BasicDataSource#getMinEvictableIdleTimeMillis()}.
      */
     long getMinEvictableIdleTimeMillis();
 
     /**
-     * See {@link BasicDataSource#getSoftMinEvictableIdleTimeMillis()}
+     * See {@link BasicDataSource#getMinIdle()}.
      *
-     * @return {@link BasicDataSource#getSoftMinEvictableIdleTimeMillis()}
+     * @return {@link BasicDataSource#getMinIdle()}.
      */
-    long getSoftMinEvictableIdleTimeMillis();
+    int getMinIdle();
 
     /**
-     * See {@link BasicDataSource#getTestWhileIdle()}
+     * See {@link BasicDataSource#getNumActive()}.
      *
-     * @return {@link BasicDataSource#getTestWhileIdle()}
+     * @return {@link BasicDataSource#getNumActive()}.
      */
-    boolean getTestWhileIdle();
+    int getNumActive();
 
     /**
-     * See {@link BasicDataSource#getNumActive()}
+     * See {@link BasicDataSource#getNumIdle()}.
      *
-     * @return {@link BasicDataSource#getNumActive()}
+     * @return {@link BasicDataSource#getNumIdle()}.
      */
-    int getNumActive();
+    int getNumIdle();
 
     /**
-     * See {@link BasicDataSource#getNumIdle()}
+     * See {@link BasicDataSource#getNumTestsPerEvictionRun()}.
      *
-     * @return {@link BasicDataSource#getNumIdle()}
+     * @return {@link BasicDataSource#getNumTestsPerEvictionRun()}.
      */
-    int getNumIdle();
+    int getNumTestsPerEvictionRun();
 
     /**
-     * See {@link BasicDataSource#getPassword()}
+     * See {@link BasicDataSource#getRemoveAbandonedOnBorrow()}.
      *
-     * @return {@link BasicDataSource#getPassword()}
+     * @return {@link BasicDataSource#getRemoveAbandonedOnBorrow()}.
      */
-    String getPassword();
+    boolean getRemoveAbandonedOnBorrow();
 
     /**
-     * See {@link BasicDataSource#getUrl()}
+     * See {@link BasicDataSource#getRemoveAbandonedOnMaintenance()}.
      *
-     * @return {@link BasicDataSource#getUrl()}
+     * @return {@link BasicDataSource#getRemoveAbandonedOnMaintenance()}.
      */
-    String getUrl();
+    boolean getRemoveAbandonedOnMaintenance();
 
     /**
-     * See {@link BasicDataSource#getUsername()}
+     * See {@link BasicDataSource#getRemoveAbandonedTimeout()}.
      *
-     * @return {@link BasicDataSource#getUsername()}
+     * @return {@link BasicDataSource#getRemoveAbandonedTimeout()}.
      */
-    String getUsername();
+    int getRemoveAbandonedTimeout();
 
     /**
-     * See {@link BasicDataSource#getValidationQuery()}
+     * See {@link BasicDataSource#getSoftMinEvictableIdleTimeMillis()}.
      *
-     * @return {@link BasicDataSource#getValidationQuery()}
+     * @return {@link BasicDataSource#getSoftMinEvictableIdleTimeMillis()}.
      */
-    String getValidationQuery();
+    long getSoftMinEvictableIdleTimeMillis();
 
     /**
-     * See {@link BasicDataSource#getValidationQueryTimeout()}
+     * See {@link BasicDataSource#getTestOnBorrow()}.
      *
-     * @return {@link BasicDataSource#getValidationQueryTimeout()}
+     * @return {@link BasicDataSource#getTestOnBorrow()}.
      */
-    int getValidationQueryTimeout();
+    boolean getTestOnBorrow();
 
     /**
-     * See {@link BasicDataSource#getConnectionInitSqlsAsArray()}
+     * See {@link BasicDataSource#getTestOnCreate()}.
      *
-     * @return {@link BasicDataSource#getConnectionInitSqlsAsArray()}
+     * @return {@link BasicDataSource#getTestOnCreate()}.
      */
-    String[] getConnectionInitSqlsAsArray();
+    boolean getTestOnCreate();
 
     /**
-     * See {@link BasicDataSource#isAccessToUnderlyingConnectionAllowed()}
+     * See {@link BasicDataSource#getTestWhileIdle()}.
      *
-     * @return {@link BasicDataSource#isAccessToUnderlyingConnectionAllowed()}
+     * @return {@link BasicDataSource#getTestWhileIdle()}.
      */
-    boolean isAccessToUnderlyingConnectionAllowed();
+    boolean getTestWhileIdle();
 
     /**
-     * See {@link BasicDataSource#getMaxConnLifetimeMillis()}
+     * See {@link BasicDataSource#getTimeBetweenEvictionRunsMillis()}.
      *
-     * @return {@link BasicDataSource#getMaxConnLifetimeMillis()}
+     * @return {@link BasicDataSource#getTimeBetweenEvictionRunsMillis()}.
      */
-    long getMaxConnLifetimeMillis();
+    long getTimeBetweenEvictionRunsMillis();
 
     /**
-     * See {@link BasicDataSource#getLogExpiredConnections()}
+     * See {@link BasicDataSource#getUrl()}.
      *
-     * @return {@link BasicDataSource#getLogExpiredConnections()}
-     * @since 2.1
+     * @return {@link BasicDataSource#getUrl()}.
      */
-    boolean getLogExpiredConnections();
+    String getUrl();
 
     /**
-     * See {@link BasicDataSource#getRemoveAbandonedOnBorrow()}
+     * See {@link BasicDataSource#getUsername()}.
      *
-     * @return {@link BasicDataSource#getRemoveAbandonedOnBorrow()}
+     * @return {@link BasicDataSource#getUsername()}.
      */
-    boolean getRemoveAbandonedOnBorrow();
+    String getUsername();
 
     /**
-     * See {@link BasicDataSource#getRemoveAbandonedOnMaintenance()}
+     * See {@link BasicDataSource#getValidationQuery()}.
      *
-     * @return {@link BasicDataSource#getRemoveAbandonedOnMaintenance()}
+     * @return {@link BasicDataSource#getValidationQuery()}.
      */
-    boolean getRemoveAbandonedOnMaintenance();
+    String getValidationQuery();
 
     /**
-     * See {@link BasicDataSource#getRemoveAbandonedTimeout()}
+     * See {@link BasicDataSource#getValidationQueryTimeout()}.
      *
-     * @return {@link BasicDataSource#getRemoveAbandonedTimeout()}
+     * @return {@link BasicDataSource#getValidationQueryTimeout()}.
      */
-    int getRemoveAbandonedTimeout();
+    int getValidationQueryTimeout();
 
     /**
-     * See {@link BasicDataSource#getLogAbandoned()}
+     * See {@link BasicDataSource#isAccessToUnderlyingConnectionAllowed()}.
      *
-     * @return {@link BasicDataSource#getLogAbandoned()}
+     * @return {@link BasicDataSource#isAccessToUnderlyingConnectionAllowed()}.
      */
-    boolean getLogAbandoned();
+    boolean isAccessToUnderlyingConnectionAllowed();
 
     /**
-     * See {@link BasicDataSource#isClosed()}
+     * See {@link BasicDataSource#isClearStatementPoolOnReturn()}.
      *
-     * @return {@link BasicDataSource#isClosed()}
+     * @return {@link BasicDataSource#isClearStatementPoolOnReturn()}.
+     * @since 2.8.0
      */
-    boolean isClosed();
+    default boolean isClearStatementPoolOnReturn() {
+        return false;
+    }
 
     /**
-     * See {@link BasicDataSource#getFastFailValidation()}
+     * See {@link BasicDataSource#isClosed()}.
      *
-     * @return {@link BasicDataSource#getFastFailValidation()}
-     * @since 2.1
+     * @return {@link BasicDataSource#isClosed()}.
      */
-    boolean getFastFailValidation();
+    boolean isClosed();
 
     /**
-     * See {@link BasicDataSource#getDisconnectionSqlCodesAsArray()}
+     * See {@link BasicDataSource#isPoolPreparedStatements()}.
      *
-     * @return {@link BasicDataSource#getDisconnectionSqlCodesAsArray()}
-     * @since 2.1
+     * @return {@link BasicDataSource#isPoolPreparedStatements()}.
      */
-    String[] getDisconnectionSqlCodesAsArray();
+    boolean isPoolPreparedStatements();
 
     /**
-     * See {@link BasicDataSource#start()}
+     * See {@link BasicDataSource#restart()}
      *
-     * @throws SQLException if an error occurs initializing the datasource
+     * @throws SQLException if an error occurs initializing the data source.
      *
      * @since 2.8.0
      */
-    default void start() throws SQLException {
-        // do nothing
+    default void restart() throws SQLException {
+        // do nothing by default?
     }
 
     /**
-     * See {@link BasicDataSource#restart()}
+     * See {@link BasicDataSource#start()}
      *
-     * @throws SQLException if an error occurs initializing the datasource
+     * @throws SQLException if an error occurs initializing the data source.
      *
      * @since 2.8.0
      */
-    default void restart() throws SQLException {
-        // do nothing by default?
+    default void start() throws SQLException {
+        // do nothing
     }
 }
diff --git a/java/org/apache/tomcat/dbcp/dbcp2/DelegatingConnection.java b/java/org/apache/tomcat/dbcp/dbcp2/DelegatingConnection.java
index c036329..887fb66 100644
--- a/java/org/apache/tomcat/dbcp/dbcp2/DelegatingConnection.java
+++ b/java/org/apache/tomcat/dbcp/dbcp2/DelegatingConnection.java
@@ -33,6 +33,8 @@ import java.sql.SQLXML;
 import java.sql.Savepoint;
 import java.sql.Statement;
 import java.sql.Struct;
+import java.time.Duration;
+import java.time.Instant;
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.List;
@@ -68,122 +70,75 @@ public class DelegatingConnection<C extends Connection> extends AbandonedTrace i
     private volatile boolean closed;
 
     private boolean cacheState = true;
-    private Boolean autoCommitCached;
-    private Boolean readOnlyCached;
-    private Integer defaultQueryTimeoutSeconds;
+    private Boolean cachedAutoCommit;
+    private Boolean cachedReadOnly;
+    private String cachedCatalog;
+    private String cachedSchema;
+    private Duration defaultQueryTimeoutDuration;
 
     /**
      * Creates a wrapper for the Connection which traces this Connection in the AbandonedObjectPool.
      *
-     * @param c
-     *            the {@link Connection} to delegate all calls to.
+     * @param connection the {@link Connection} to delegate all calls to.
      */
-    public DelegatingConnection(final C c) {
-        connection = c;
+    public DelegatingConnection(final C connection) {
+        this.connection = connection;
     }
 
-    /**
-     * Returns a string representation of the metadata associated with the innermost delegate connection.
-     */
     @Override
-    public synchronized String toString() {
-        String str = null;
-
-        final Connection conn = this.getInnermostDelegateInternal();
-        if (conn != null) {
-            try {
-                if (conn.isClosed()) {
-                    str = "connection is closed";
-                } else {
-                    final StringBuilder sb = new StringBuilder();
-                    sb.append(hashCode());
-                    final DatabaseMetaData meta = conn.getMetaData();
-                    if (meta != null) {
-                        sb.append(", URL=");
-                        sb.append(meta.getURL());
-                        sb.append(", ");
-                        sb.append(meta.getDriverName());
-                        str = sb.toString();
-                    }
-                }
-            } catch (final SQLException ex) {
-                // Ignore
-            }
+    public void abort(final Executor executor) throws SQLException {
+        try {
+            Jdbc41Bridge.abort(connection, executor);
+        } catch (final SQLException e) {
+            handleException(e);
         }
-        return str != null ? str : super.toString();
-    }
-
-    /**
-     * Returns my underlying {@link Connection}.
-     *
-     * @return my underlying {@link Connection}.
-     */
-    public C getDelegate() {
-        return getDelegateInternal();
-    }
-
-    protected final C getDelegateInternal() {
-        return connection;
     }
 
-    /**
-     * Compares innermost delegate to the given connection.
-     *
-     * @param c
-     *            connection to compare innermost delegate with
-     * @return true if innermost delegate equals <code>c</code>
-     */
-    public boolean innermostDelegateEquals(final Connection c) {
-        final Connection innerCon = getInnermostDelegateInternal();
-        if (innerCon == null) {
-            return c == null;
+    protected void activate() {
+        closed = false;
+        setLastUsed();
+        if (connection instanceof DelegatingConnection) {
+            ((DelegatingConnection<?>) connection).activate();
         }
-        return innerCon.equals(c);
     }
 
-    /**
-     * If my underlying {@link Connection} is not a {@code DelegatingConnection}, returns it, otherwise recursively
-     * invokes this method on my delegate.
-     * <p>
-     * Hence this method will return the first delegate that is not a {@code DelegatingConnection}, or {@code null} when
-     * no non-{@code DelegatingConnection} delegate can be found by traversing this chain.
-     * </p>
-     * <p>
-     * This method is useful when you may have nested {@code DelegatingConnection}s, and you want to make sure to obtain
-     * a "genuine" {@link Connection}.
-     * </p>
-     *
-     * @return innermost delegate.
-     */
-    public Connection getInnermostDelegate() {
-        return getInnermostDelegateInternal();
+    protected void checkOpen() throws SQLException {
+        if (closed) {
+            if (null != connection) {
+                String label = "";
+                try {
+                    label = connection.toString();
+                } catch (final Exception ex) {
+                    // ignore, leave label empty
+                }
+                throw new SQLException("Connection " + label + " is closed.");
+            }
+            throw new SQLException("Connection is null.");
+        }
     }
 
     /**
-     * Although this method is public, it is part of the internal API and should not be used by clients. The signature
-     * of this method may change at any time including in ways that break backwards compatibility.
-     *
-     * @return innermost delegate.
+     * Can be used to clear cached state when it is known that the underlying connection may have been accessed
+     * directly.
      */
-    public final Connection getInnermostDelegateInternal() {
-        Connection conn = connection;
-        while (conn instanceof DelegatingConnection) {
-            conn = ((DelegatingConnection<?>) conn).getDelegateInternal();
-            if (this == conn) {
-                return null;
-            }
+    public void clearCachedState() {
+        cachedAutoCommit = null;
+        cachedReadOnly = null;
+        cachedSchema = null;
+        cachedCatalog = null;
+        if (connection instanceof DelegatingConnection) {
+            ((DelegatingConnection<?>) connection).clearCachedState();
         }
-        return conn;
     }
 
-    /**
-     * Sets my delegate.
-     *
-     * @param connection
-     *            my delegate.
-     */
-    public void setDelegate(final C connection) {
-        this.connection = connection;
+    @Override
+    public void clearWarnings() throws SQLException {
+        checkOpen();
+        try {
+            connection.clearWarnings();
+        } catch (final SQLException e) {
+            handleException(e);
+        }
     }
 
     /**
@@ -202,14 +157,6 @@ public class DelegatingConnection<C extends Connection> extends AbandonedTrace i
         }
     }
 
-    protected boolean isClosedInternal() {
-        return closed;
-    }
-
-    protected void setClosedInternal(final boolean closed) {
-        this.closed = closed;
-    }
-
     protected final void closeInternal() throws SQLException {
         try {
             passivate();
@@ -238,35 +185,43 @@ public class DelegatingConnection<C extends Connection> extends AbandonedTrace i
         }
     }
 
-    protected void handleException(final SQLException e) throws SQLException {
-        throw e;
+    @Override
+    public void commit() throws SQLException {
+        checkOpen();
+        try {
+            connection.commit();
+        } catch (final SQLException e) {
+            handleException(e);
+        }
     }
 
-    /**
-     * Handles the given {@code SQLException}.
-     *
-     * @param <T> The throwable type.
-     * @param e   The SQLException
-     * @return the given {@code SQLException}
-     * @since 2.7.0
-     */
-    protected <T extends Throwable> T handleExceptionNoThrow(final T e) {
-        return e;
+    @Override
+    public Array createArrayOf(final String typeName, final Object[] elements) throws SQLException {
+        checkOpen();
+        try {
+            return connection.createArrayOf(typeName, elements);
+        } catch (final SQLException e) {
+            handleException(e);
+            return null;
+        }
     }
 
-    private void initializeStatement(final DelegatingStatement ds) throws SQLException {
-        if (defaultQueryTimeoutSeconds != null && defaultQueryTimeoutSeconds.intValue() != ds.getQueryTimeout()) {
-            ds.setQueryTimeout(defaultQueryTimeoutSeconds.intValue());
+    @Override
+    public Blob createBlob() throws SQLException {
+        checkOpen();
+        try {
+            return connection.createBlob();
+        } catch (final SQLException e) {
+            handleException(e);
+            return null;
         }
     }
 
     @Override
-    public Statement createStatement() throws SQLException {
+    public Clob createClob() throws SQLException {
         checkOpen();
         try {
-            final DelegatingStatement ds = new DelegatingStatement(this, connection.createStatement());
-            initializeStatement(ds);
-            return ds;
+            return connection.createClob();
         } catch (final SQLException e) {
             handleException(e);
             return null;
@@ -274,13 +229,10 @@ public class DelegatingConnection<C extends Connection> extends AbandonedTrace i
     }
 
     @Override
-    public Statement createStatement(final int resultSetType, final int resultSetConcurrency) throws SQLException {
+    public NClob createNClob() throws SQLException {
         checkOpen();
         try {
-            final DelegatingStatement ds = new DelegatingStatement(this,
-                    connection.createStatement(resultSetType, resultSetConcurrency));
-            initializeStatement(ds);
-            return ds;
+            return connection.createNClob();
         } catch (final SQLException e) {
             handleException(e);
             return null;
@@ -288,13 +240,10 @@ public class DelegatingConnection<C extends Connection> extends AbandonedTrace i
     }
 
     @Override
-    public PreparedStatement prepareStatement(final String sql) throws SQLException {
+    public SQLXML createSQLXML() throws SQLException {
         checkOpen();
         try {
-            final DelegatingPreparedStatement dps = new DelegatingPreparedStatement(this,
-                    connection.prepareStatement(sql));
-            initializeStatement(dps);
-            return dps;
+            return connection.createSQLXML();
         } catch (final SQLException e) {
             handleException(e);
             return null;
@@ -302,14 +251,10 @@ public class DelegatingConnection<C extends Connection> extends AbandonedTrace i
     }
 
     @Override
-    public PreparedStatement prepareStatement(final String sql, final int resultSetType, final int resultSetConcurrency)
-            throws SQLException {
+    public Statement createStatement() throws SQLException {
         checkOpen();
         try {
-            final DelegatingPreparedStatement dps = new DelegatingPreparedStatement(this,
-                    connection.prepareStatement(sql, resultSetType, resultSetConcurrency));
-            initializeStatement(dps);
-            return dps;
+            return init(new DelegatingStatement(this, connection.createStatement()));
         } catch (final SQLException e) {
             handleException(e);
             return null;
@@ -317,12 +262,10 @@ public class DelegatingConnection<C extends Connection> extends AbandonedTrace i
     }
 
     @Override
-    public CallableStatement prepareCall(final String sql) throws SQLException {
+    public Statement createStatement(final int resultSetType, final int resultSetConcurrency) throws SQLException {
         checkOpen();
         try {
-            final DelegatingCallableStatement dcs = new DelegatingCallableStatement(this, connection.prepareCall(sql));
-            initializeStatement(dcs);
-            return dcs;
+            return init(new DelegatingStatement(this, connection.createStatement(resultSetType, resultSetConcurrency)));
         } catch (final SQLException e) {
             handleException(e);
             return null;
@@ -330,14 +273,12 @@ public class DelegatingConnection<C extends Connection> extends AbandonedTrace i
     }
 
     @Override
-    public CallableStatement prepareCall(final String sql, final int resultSetType, final int resultSetConcurrency)
-            throws SQLException {
+    public Statement createStatement(final int resultSetType, final int resultSetConcurrency,
+        final int resultSetHoldability) throws SQLException {
         checkOpen();
         try {
-            final DelegatingCallableStatement dcs = new DelegatingCallableStatement(this,
-                    connection.prepareCall(sql, resultSetType, resultSetConcurrency));
-            initializeStatement(dcs);
-            return dcs;
+            return init(new DelegatingStatement(this,
+                connection.createStatement(resultSetType, resultSetConcurrency, resultSetHoldability)));
         } catch (final SQLException e) {
             handleException(e);
             return null;
@@ -345,22 +286,28 @@ public class DelegatingConnection<C extends Connection> extends AbandonedTrace i
     }
 
     @Override
-    public void clearWarnings() throws SQLException {
+    public Struct createStruct(final String typeName, final Object[] attributes) throws SQLException {
         checkOpen();
         try {
-            connection.clearWarnings();
+            return connection.createStruct(typeName, attributes);
         } catch (final SQLException e) {
             handleException(e);
+            return null;
         }
     }
 
     @Override
-    public void commit() throws SQLException {
+    public boolean getAutoCommit() throws SQLException {
         checkOpen();
+        if (cacheState && cachedAutoCommit != null) {
+            return cachedAutoCommit.booleanValue();
+        }
         try {
-            connection.commit();
+            cachedAutoCommit = Boolean.valueOf(connection.getAutoCommit());
+            return cachedAutoCommit.booleanValue();
         } catch (final SQLException e) {
             handleException(e);
+            return false;
         }
     }
 
@@ -374,25 +321,25 @@ public class DelegatingConnection<C extends Connection> extends AbandonedTrace i
     }
 
     @Override
-    public boolean getAutoCommit() throws SQLException {
+    public String getCatalog() throws SQLException {
         checkOpen();
-        if (cacheState && autoCommitCached != null) {
-            return autoCommitCached.booleanValue();
+        if (cacheState && cachedCatalog != null) {
+            return cachedCatalog;
         }
         try {
-            autoCommitCached = Boolean.valueOf(connection.getAutoCommit());
-            return autoCommitCached.booleanValue();
+            cachedCatalog = connection.getCatalog();
+            return cachedCatalog;
         } catch (final SQLException e) {
             handleException(e);
-            return false;
+            return null;
         }
     }
 
     @Override
-    public String getCatalog() throws SQLException {
+    public Properties getClientInfo() throws SQLException {
         checkOpen();
         try {
-            return connection.getCatalog();
+            return connection.getClientInfo();
         } catch (final SQLException e) {
             handleException(e);
             return null;
@@ -400,32 +347,108 @@ public class DelegatingConnection<C extends Connection> extends AbandonedTrace i
     }
 
     @Override
-    public DatabaseMetaData getMetaData() throws SQLException {
+    public String getClientInfo(final String name) throws SQLException {
         checkOpen();
         try {
-            return new DelegatingDatabaseMetaData(this, connection.getMetaData());
+            return connection.getClientInfo(name);
         } catch (final SQLException e) {
             handleException(e);
             return null;
         }
     }
 
+    /**
+     * Gets the default query timeout that will be used for {@link Statement}s created from this connection.
+     * <code>null</code> means that the driver default will be used.
+     *
+     * @return query timeout limit in seconds; zero means there is no limit.
+     * @deprecated Use {@link #getDefaultQueryTimeoutDuration()}.
+     */
+    @Deprecated
+    public Integer getDefaultQueryTimeout() {
+        return defaultQueryTimeoutDuration == null ? null : Integer.valueOf((int) defaultQueryTimeoutDuration.getSeconds());
+    }
+
+    /**
+     * Gets the default query timeout that will be used for {@link Statement}s created from this connection.
+     * <code>null</code> means that the driver default will be used.
+     *
+     * @return query timeout limit; zero means there is no limit.
+     * @since 2.10.0
+     */
+    public Duration getDefaultQueryTimeoutDuration() {
+        return defaultQueryTimeoutDuration;
+    }
+
+    /**
+     * Returns my underlying {@link Connection}.
+     *
+     * @return my underlying {@link Connection}.
+     */
+    public C getDelegate() {
+        return getDelegateInternal();
+    }
+
+    /**
+     * Gets the delegate connection.
+     *
+     * @return the delegate connection.
+     */
+    protected final C getDelegateInternal() {
+        return connection;
+    }
+
     @Override
-    public int getTransactionIsolation() throws SQLException {
+    public int getHoldability() throws SQLException {
         checkOpen();
         try {
-            return connection.getTransactionIsolation();
+            return connection.getHoldability();
         } catch (final SQLException e) {
             handleException(e);
-            return -1;
+            return 0;
         }
     }
 
+    /**
+     * If my underlying {@link Connection} is not a {@code DelegatingConnection}, returns it, otherwise recursively
+     * invokes this method on my delegate.
+     * <p>
+     * Hence this method will return the first delegate that is not a {@code DelegatingConnection}, or {@code null} when
+     * no non-{@code DelegatingConnection} delegate can be found by traversing this chain.
+     * </p>
+     * <p>
+     * This method is useful when you may have nested {@code DelegatingConnection}s, and you want to make sure to obtain
+     * a "genuine" {@link Connection}.
+     * </p>
+     *
+     * @return innermost delegate.
+     */
+    public Connection getInnermostDelegate() {
+        return getInnermostDelegateInternal();
+    }
+
+    /**
+     * Although this method is public, it is part of the internal API and should not be used by clients. The signature
+     * of this method may change at any time including in ways that break backwards compatibility.
+     *
+     * @return innermost delegate.
+     */
+    public final Connection getInnermostDelegateInternal() {
+        Connection conn = connection;
+        while (conn instanceof DelegatingConnection) {
+            conn = ((DelegatingConnection<?>) conn).getDelegateInternal();
+            if (this == conn) {
+                return null;
+            }
+        }
+        return conn;
+    }
+
     @Override
-    public Map<String, Class<?>> getTypeMap() throws SQLException {
+    public DatabaseMetaData getMetaData() throws SQLException {
         checkOpen();
         try {
-            return connection.getTypeMap();
+            return new DelegatingDatabaseMetaData(this, connection.getMetaData());
         } catch (final SQLException e) {
             handleException(e);
             return null;
@@ -433,36 +456,47 @@ public class DelegatingConnection<C extends Connection> extends AbandonedTrace i
     }
 
     @Override
-    public SQLWarning getWarnings() throws SQLException {
+    public int getNetworkTimeout() throws SQLException {
         checkOpen();
         try {
-            return connection.getWarnings();
+            return Jdbc41Bridge.getNetworkTimeout(connection);
         } catch (final SQLException e) {
             handleException(e);
-            return null;
+            return 0;
         }
     }
 
     @Override
-    public boolean isReadOnly() throws SQLException {
+    public String getSchema() throws SQLException {
         checkOpen();
-        if (cacheState && readOnlyCached != null) {
-            return readOnlyCached.booleanValue();
+        if (cacheState && cachedSchema != null) {
+            return cachedSchema;
         }
         try {
-            readOnlyCached = Boolean.valueOf(connection.isReadOnly());
-            return readOnlyCached.booleanValue();
+            cachedSchema = Jdbc41Bridge.getSchema(connection);
+            return cachedSchema;
         } catch (final SQLException e) {
             handleException(e);
-            return false;
+            return null;
         }
     }
 
     @Override
-    public String nativeSQL(final String sql) throws SQLException {
+    public int getTransactionIsolation() throws SQLException {
         checkOpen();
         try {
-            return connection.nativeSQL(sql);
+            return connection.getTransactionIsolation();
+        } catch (final SQLException e) {
+            handleException(e);
+            return -1;
+        }
+    }
+
+    @Override
+    public Map<String, Class<?>> getTypeMap() throws SQLException {
+        checkOpen();
+        try {
+            return connection.getTypeMap();
         } catch (final SQLException e) {
             handleException(e);
             return null;
@@ -470,141 +504,141 @@ public class DelegatingConnection<C extends Connection> extends AbandonedTrace i
     }
 
     @Override
-    public void rollback() throws SQLException {
+    public SQLWarning getWarnings() throws SQLException {
         checkOpen();
         try {
-            connection.rollback();
+            return connection.getWarnings();
         } catch (final SQLException e) {
             handleException(e);
+            return null;
         }
     }
 
     /**
-     * Gets the default query timeout that will be used for {@link Statement}s created from this connection.
-     * <code>null</code> means that the driver default will be used.
+     * Handles the given exception by throwing it.
      *
-     * @return query timeout limit in seconds; zero means there is no limit.
+     * @param e the exception to throw.
+     * @throws SQLException the exception to throw.
      */
-    public Integer getDefaultQueryTimeout() {
-        return defaultQueryTimeoutSeconds;
+    protected void handleException(final SQLException e) throws SQLException {
+        throw e;
     }
 
     /**
-     * Sets the default query timeout that will be used for {@link Statement}s created from this connection.
-     * <code>null</code> means that the driver default will be used.
+     * Handles the given {@code SQLException}.
      *
-     * @param defaultQueryTimeoutSeconds
-     *            the new query timeout limit in seconds; zero means there is no limit
+     * @param <T> The throwable type.
+     * @param e   The SQLException
+     * @return the given {@code SQLException}
+     * @since 2.7.0
      */
-    public void setDefaultQueryTimeout(final Integer defaultQueryTimeoutSeconds) {
-        this.defaultQueryTimeoutSeconds = defaultQueryTimeoutSeconds;
+    protected <T extends Throwable> T handleExceptionNoThrow(final T e) {
+        return e;
     }
 
     /**
-     * Sets the state caching flag.
+     * Initializes the given statement with this connection's settings.
      *
-     * @param cacheState
-     *            The new value for the state caching flag
+     * @param <T> The DelegatingStatement type.
+     * @param delegatingStatement The DelegatingStatement to initialize.
+     * @return The given DelegatingStatement.
+     * @throws SQLException if a database access error occurs, this method is called on a closed Statement.
      */
-    public void setCacheState(final boolean cacheState) {
-        this.cacheState = cacheState;
+    private <T extends DelegatingStatement> T init(final T delegatingStatement) throws SQLException {
+        if (defaultQueryTimeoutDuration != null && defaultQueryTimeoutDuration.getSeconds() != delegatingStatement.getQueryTimeout()) {
+            delegatingStatement.setQueryTimeout((int) defaultQueryTimeoutDuration.getSeconds());
+        }
+        return delegatingStatement;
     }
 
     /**
-     * Can be used to clear cached state when it is known that the underlying connection may have been accessed
-     * directly.
+     * Compares innermost delegate to the given connection.
+     *
+     * @param c
+     *            connection to compare innermost delegate with
+     * @return true if innermost delegate equals <code>c</code>
      */
-    public void clearCachedState() {
-        autoCommitCached = null;
-        readOnlyCached = null;
-        if (connection instanceof DelegatingConnection) {
-            ((DelegatingConnection<?>) connection).clearCachedState();
+    public boolean innermostDelegateEquals(final Connection c) {
+        final Connection innerCon = getInnermostDelegateInternal();
+        if (innerCon == null) {
+            return c == null;
         }
+        return innerCon.equals(c);
     }
 
     @Override
-    public void setAutoCommit(final boolean autoCommit) throws SQLException {
-        checkOpen();
-        try {
-            connection.setAutoCommit(autoCommit);
-            if (cacheState) {
-                autoCommitCached = Boolean.valueOf(connection.getAutoCommit());
-            }
-        } catch (final SQLException e) {
-            autoCommitCached = null;
-            handleException(e);
-        }
+    public boolean isClosed() throws SQLException {
+        return closed || connection == null || connection.isClosed();
     }
 
-    @Override
-    public void setCatalog(final String catalog) throws SQLException {
-        checkOpen();
-        try {
-            connection.setCatalog(catalog);
-        } catch (final SQLException e) {
-            handleException(e);
-        }
+    protected boolean isClosedInternal() {
+        return closed;
     }
 
     @Override
-    public void setReadOnly(final boolean readOnly) throws SQLException {
+    public boolean isReadOnly() throws SQLException {
         checkOpen();
-        try {
-            connection.setReadOnly(readOnly);
-            if (cacheState) {
-                readOnlyCached = Boolean.valueOf(connection.isReadOnly());
-            }
-        } catch (final SQLException e) {
-            readOnlyCached = null;
-            handleException(e);
+        if (cacheState && cachedReadOnly != null) {
+            return cachedReadOnly.booleanValue();
         }
-    }
-
-    @Override
-    public void setTransactionIsolation(final int level) throws SQLException {
-        checkOpen();
         try {
-            connection.setTransactionIsolation(level);
+            cachedReadOnly = Boolean.valueOf(connection.isReadOnly());
+            return cachedReadOnly.booleanValue();
         } catch (final SQLException e) {
             handleException(e);
+            return false;
         }
     }
 
-    @Override
-    public void setTypeMap(final Map<String, Class<?>> map) throws SQLException {
-        checkOpen();
+    /**
+     * Tests if the connection has not been closed and is still valid.
+     *
+     * @param timeout The duration to wait for the database operation used to validate the connection to complete.
+     * @return See {@link Connection#isValid(int)}.
+     * @throws SQLException See {@link Connection#isValid(int)}.
+     * @see Connection#isValid(int)
+     * @since 2.10.0
+     */
+    public boolean isValid(final Duration timeout) throws SQLException {
+        if (isClosed()) {
+            return false;
+        }
         try {
-            connection.setTypeMap(map);
+            return connection.isValid((int) timeout.getSeconds());
         } catch (final SQLException e) {
             handleException(e);
+            return false;
         }
     }
 
+    /**
+     * @deprecated Use {@link #isValid(Duration)}.
+     */
     @Override
-    public boolean isClosed() throws SQLException {
-        return closed || connection == null || connection.isClosed();
+    @Deprecated
+    public boolean isValid(final int timeoutSeconds) throws SQLException {
+        return isValid(Duration.ofSeconds(timeoutSeconds));
     }
 
-    protected void checkOpen() throws SQLException {
-        if (closed) {
-            if (null != connection) {
-                String label = "";
-                try {
-                    label = connection.toString();
-                } catch (final Exception ex) {
-                    // ignore, leave label empty
-                }
-                throw new SQLException("Connection " + label + " is closed.");
-            }
-            throw new SQLException("Connection is null.");
+    @Override
+    public boolean isWrapperFor(final Class<?> iface) throws SQLException {
+        if (iface.isAssignableFrom(getClass())) {
+            return true;
+        }
+        if (iface.isAssignableFrom(connection.getClass())) {
+            return true;
         }
+        return connection.isWrapperFor(iface);
     }
 
-    protected void activate() {
-        closed = false;
-        setLastUsed();
-        if (connection instanceof DelegatingConnection) {
-            ((DelegatingConnection<?>) connection).activate();
+    @Override
+    public String nativeSQL(final String sql) throws SQLException {
+        checkOpen();
+        try {
+            return connection.nativeSQL(sql);
+        } catch (final SQLException e) {
+            handleException(e);
+            return null;
         }
     }
 
@@ -637,35 +671,14 @@ public class DelegatingConnection<C extends Connection> extends AbandonedTrace i
                 throw new SQLExceptionList(thrownList);
             }
         }
-        setLastUsed(0);
-    }
-
-    @Override
-    public int getHoldability() throws SQLException {
-        checkOpen();
-        try {
-            return connection.getHoldability();
-        } catch (final SQLException e) {
-            handleException(e);
-            return 0;
-        }
-    }
-
-    @Override
-    public void setHoldability(final int holdability) throws SQLException {
-        checkOpen();
-        try {
-            connection.setHoldability(holdability);
-        } catch (final SQLException e) {
-            handleException(e);
-        }
+        setLastUsed(Instant.EPOCH);
     }
 
     @Override
-    public Savepoint setSavepoint() throws SQLException {
+    public CallableStatement prepareCall(final String sql) throws SQLException {
         checkOpen();
         try {
-            return connection.setSavepoint();
+            return init(new DelegatingCallableStatement(this, connection.prepareCall(sql)));
         } catch (final SQLException e) {
             handleException(e);
             return null;
@@ -673,10 +686,12 @@ public class DelegatingConnection<C extends Connection> extends AbandonedTrace i
     }
 
     @Override
-    public Savepoint setSavepoint(final String name) throws SQLException {
+    public CallableStatement prepareCall(final String sql, final int resultSetType, final int resultSetConcurrency)
+        throws SQLException {
         checkOpen();
         try {
-            return connection.setSavepoint(name);
+            return init(new DelegatingCallableStatement(this,
+                connection.prepareCall(sql, resultSetType, resultSetConcurrency)));
         } catch (final SQLException e) {
             handleException(e);
             return null;
@@ -684,34 +699,23 @@ public class DelegatingConnection<C extends Connection> extends AbandonedTrace i
     }
 
     @Override
-    public void rollback(final Savepoint savepoint) throws SQLException {
-        checkOpen();
-        try {
-            connection.rollback(savepoint);
-        } catch (final SQLException e) {
-            handleException(e);
-        }
-    }
-
-    @Override
-    public void releaseSavepoint(final Savepoint savepoint) throws SQLException {
+    public CallableStatement prepareCall(final String sql, final int resultSetType, final int resultSetConcurrency,
+        final int resultSetHoldability) throws SQLException {
         checkOpen();
         try {
-            connection.releaseSavepoint(savepoint);
+            return init(new DelegatingCallableStatement(this,
+                connection.prepareCall(sql, resultSetType, resultSetConcurrency, resultSetHoldability)));
         } catch (final SQLException e) {
             handleException(e);
+            return null;
         }
     }
 
     @Override
-    public Statement createStatement(final int resultSetType, final int resultSetConcurrency,
-            final int resultSetHoldability) throws SQLException {
+    public PreparedStatement prepareStatement(final String sql) throws SQLException {
         checkOpen();
         try {
-            final DelegatingStatement ds = new DelegatingStatement(this,
-                    connection.createStatement(resultSetType, resultSetConcurrency, resultSetHoldability));
-            initializeStatement(ds);
-            return ds;
+            return init(new DelegatingPreparedStatement(this, connection.prepareStatement(sql)));
         } catch (final SQLException e) {
             handleException(e);
             return null;
@@ -719,14 +723,10 @@ public class DelegatingConnection<C extends Connection> extends AbandonedTrace i
     }
 
     @Override
-    public PreparedStatement prepareStatement(final String sql, final int resultSetType, final int resultSetConcurrency,
-            final int resultSetHoldability) throws SQLException {
+    public PreparedStatement prepareStatement(final String sql, final int autoGeneratedKeys) throws SQLException {
         checkOpen();
         try {
-            final DelegatingPreparedStatement dps = new DelegatingPreparedStatement(this,
-                    connection.prepareStatement(sql, resultSetType, resultSetConcurrency, resultSetHoldability));
-            initializeStatement(dps);
-            return dps;
+            return init(new DelegatingPreparedStatement(this, connection.prepareStatement(sql, autoGeneratedKeys)));
         } catch (final SQLException e) {
             handleException(e);
             return null;
@@ -734,14 +734,12 @@ public class DelegatingConnection<C extends Connection> extends AbandonedTrace i
     }
 
     @Override
-    public CallableStatement prepareCall(final String sql, final int resultSetType, final int resultSetConcurrency,
-            final int resultSetHoldability) throws SQLException {
+    public PreparedStatement prepareStatement(final String sql, final int resultSetType, final int resultSetConcurrency)
+        throws SQLException {
         checkOpen();
         try {
-            final DelegatingCallableStatement dcs = new DelegatingCallableStatement(this,
-                    connection.prepareCall(sql, resultSetType, resultSetConcurrency, resultSetHoldability));
-            initializeStatement(dcs);
-            return dcs;
+            return init(new DelegatingPreparedStatement(this,
+                connection.prepareStatement(sql, resultSetType, resultSetConcurrency)));
         } catch (final SQLException e) {
             handleException(e);
             return null;
@@ -749,13 +747,12 @@ public class DelegatingConnection<C extends Connection> extends AbandonedTrace i
     }
 
     @Override
-    public PreparedStatement prepareStatement(final String sql, final int autoGeneratedKeys) throws SQLException {
+    public PreparedStatement prepareStatement(final String sql, final int resultSetType, final int resultSetConcurrency,
+        final int resultSetHoldability) throws SQLException {
         checkOpen();
         try {
-            final DelegatingPreparedStatement dps = new DelegatingPreparedStatement(this,
-                    connection.prepareStatement(sql, autoGeneratedKeys));
-            initializeStatement(dps);
-            return dps;
+            return init(new DelegatingPreparedStatement(this,
+                connection.prepareStatement(sql, resultSetType, resultSetConcurrency, resultSetHoldability)));
         } catch (final SQLException e) {
             handleException(e);
             return null;
@@ -766,10 +763,7 @@ public class DelegatingConnection<C extends Connection> extends AbandonedTrace i
     public PreparedStatement prepareStatement(final String sql, final int[] columnIndexes) throws SQLException {
         checkOpen();
         try {
-            final DelegatingPreparedStatement dps = new DelegatingPreparedStatement(this,
-                    connection.prepareStatement(sql, columnIndexes));
-            initializeStatement(dps);
-            return dps;
+            return init(new DelegatingPreparedStatement(this, connection.prepareStatement(sql, columnIndexes)));
         } catch (final SQLException e) {
             handleException(e);
             return null;
@@ -780,10 +774,7 @@ public class DelegatingConnection<C extends Connection> extends AbandonedTrace i
     public PreparedStatement prepareStatement(final String sql, final String[] columnNames) throws SQLException {
         checkOpen();
         try {
-            final DelegatingPreparedStatement dps = new DelegatingPreparedStatement(this,
-                    connection.prepareStatement(sql, columnNames));
-            initializeStatement(dps);
-            return dps;
+            return init(new DelegatingPreparedStatement(this, connection.prepareStatement(sql, columnNames)));
         } catch (final SQLException e) {
             handleException(e);
             return null;
@@ -791,103 +782,82 @@ public class DelegatingConnection<C extends Connection> extends AbandonedTrace i
     }
 
     @Override
-    public boolean isWrapperFor(final Class<?> iface) throws SQLException {
-        if (iface.isAssignableFrom(getClass())) {
-            return true;
-        } else if (iface.isAssignableFrom(connection.getClass())) {
-            return true;
-        } else {
-            return connection.isWrapperFor(iface);
-        }
-    }
-
-    @Override
-    public <T> T unwrap(final Class<T> iface) throws SQLException {
-        if (iface.isAssignableFrom(getClass())) {
-            return iface.cast(this);
-        } else if (iface.isAssignableFrom(connection.getClass())) {
-            return iface.cast(connection);
-        } else {
-            return connection.unwrap(iface);
-        }
-    }
-
-    @Override
-    public Array createArrayOf(final String typeName, final Object[] elements) throws SQLException {
+    public void releaseSavepoint(final Savepoint savepoint) throws SQLException {
         checkOpen();
         try {
-            return connection.createArrayOf(typeName, elements);
+            connection.releaseSavepoint(savepoint);
         } catch (final SQLException e) {
             handleException(e);
-            return null;
         }
     }
 
     @Override
-    public Blob createBlob() throws SQLException {
+    public void rollback() throws SQLException {
         checkOpen();
         try {
-            return connection.createBlob();
+            connection.rollback();
         } catch (final SQLException e) {
             handleException(e);
-            return null;
         }
     }
 
     @Override
-    public Clob createClob() throws SQLException {
+    public void rollback(final Savepoint savepoint) throws SQLException {
         checkOpen();
         try {
-            return connection.createClob();
+            connection.rollback(savepoint);
         } catch (final SQLException e) {
             handleException(e);
-            return null;
         }
     }
 
     @Override
-    public NClob createNClob() throws SQLException {
+    public void setAutoCommit(final boolean autoCommit) throws SQLException {
         checkOpen();
         try {
-            return connection.createNClob();
+            connection.setAutoCommit(autoCommit);
+            if (cacheState) {
+                cachedAutoCommit = Boolean.valueOf(connection.getAutoCommit());
+            }
         } catch (final SQLException e) {
+            cachedAutoCommit = null;
             handleException(e);
-            return null;
         }
     }
 
-    @Override
-    public SQLXML createSQLXML() throws SQLException {
-        checkOpen();
-        try {
-            return connection.createSQLXML();
-        } catch (final SQLException e) {
-            handleException(e);
-            return null;
-        }
+    /**
+     * Sets the state caching flag.
+     *
+     * @param cacheState
+     *            The new value for the state caching flag
+     */
+    public void setCacheState(final boolean cacheState) {
+        this.cacheState = cacheState;
     }
 
     @Override
-    public Struct createStruct(final String typeName, final Object[] attributes) throws SQLException {
+    public void setCatalog(final String catalog) throws SQLException {
         checkOpen();
         try {
-            return connection.createStruct(typeName, attributes);
+            connection.setCatalog(catalog);
+            if (cacheState) {
+                cachedCatalog = connection.getCatalog();
+            }
         } catch (final SQLException e) {
+            cachedCatalog = null;
             handleException(e);
-            return null;
         }
     }
 
     @Override
-    public boolean isValid(final int timeoutSeconds) throws SQLException {
-        if (isClosed()) {
-            return false;
-        }
+    public void setClientInfo(final Properties properties) throws SQLClientInfoException {
         try {
-            return connection.isValid(timeoutSeconds);
+            checkOpen();
+            connection.setClientInfo(properties);
+        } catch (final SQLClientInfoException e) {
+            throw e;
         } catch (final SQLException e) {
-            handleException(e);
-            return false;
+            throw new SQLClientInfoException("Connection is closed.", EMPTY_FAILED_PROPERTIES, e);
         }
     }
 
@@ -903,55 +873,95 @@ public class DelegatingConnection<C extends Connection> extends AbandonedTrace i
         }
     }
 
+    protected void setClosedInternal(final boolean closed) {
+        this.closed = closed;
+    }
+
+    /**
+     * Sets the default query timeout that will be used for {@link Statement}s created from this connection.
+     * <code>null</code> means that the driver default will be used.
+     *
+     * @param defaultQueryTimeoutDuration
+     *            the new query timeout limit Duration; zero means there is no limit.
+     * @since 2.10.0
+     */
+    public void setDefaultQueryTimeout(final Duration defaultQueryTimeoutDuration) {
+        this.defaultQueryTimeoutDuration = defaultQueryTimeoutDuration;
+    }
+
+    /**
+     * Sets the default query timeout that will be used for {@link Statement}s created from this connection.
+     * <code>null</code> means that the driver default will be used.
+     *
+     * @param defaultQueryTimeoutSeconds
+     *            the new query timeout limit in seconds; zero means there is no limit.
+     * @deprecated Use {@link #setDefaultQueryTimeout(Duration)}.
+     */
+    @Deprecated
+    public void setDefaultQueryTimeout(final Integer defaultQueryTimeoutSeconds) {
+        this.defaultQueryTimeoutDuration = defaultQueryTimeoutSeconds == null ? null : Duration.ofSeconds(defaultQueryTimeoutSeconds.intValue());
+    }
+
+    /**
+     * Sets my delegate.
+     *
+     * @param connection
+     *            my delegate.
+     */
+    public void setDelegate(final C connection) {
+        this.connection = connection;
+    }
+
     @Override
-    public void setClientInfo(final Properties properties) throws SQLClientInfoException {
+    public void setHoldability(final int holdability) throws SQLException {
+        checkOpen();
         try {
-            checkOpen();
-            connection.setClientInfo(properties);
-        } catch (final SQLClientInfoException e) {
-            throw e;
+            connection.setHoldability(holdability);
         } catch (final SQLException e) {
-            throw new SQLClientInfoException("Connection is closed.", EMPTY_FAILED_PROPERTIES, e);
+            handleException(e);
         }
     }
 
     @Override
-    public Properties getClientInfo() throws SQLException {
+    public void setNetworkTimeout(final Executor executor, final int milliseconds) throws SQLException {
         checkOpen();
         try {
-            return connection.getClientInfo();
+            Jdbc41Bridge.setNetworkTimeout(connection, executor, milliseconds);
         } catch (final SQLException e) {
             handleException(e);
-            return null;
         }
     }
 
     @Override
-    public String getClientInfo(final String name) throws SQLException {
+    public void setReadOnly(final boolean readOnly) throws SQLException {
         checkOpen();
         try {
-            return connection.getClientInfo(name);
+            connection.setReadOnly(readOnly);
+            if (cacheState) {
+                cachedReadOnly = Boolean.valueOf(connection.isReadOnly());
+            }
         } catch (final SQLException e) {
+            cachedReadOnly = null;
             handleException(e);
-            return null;
         }
     }
 
     @Override
-    public void setSchema(final String schema) throws SQLException {
+    public Savepoint setSavepoint() throws SQLException {
         checkOpen();
         try {
-            Jdbc41Bridge.setSchema(connection, schema);
+            return connection.setSavepoint();
         } catch (final SQLException e) {
             handleException(e);
+            return null;
         }
     }
 
     @Override
-    public String getSchema() throws SQLException {
+    public Savepoint setSavepoint(final String name) throws SQLException {
         checkOpen();
         try {
-            return Jdbc41Bridge.getSchema(connection);
+            return connection.setSavepoint(name);
         } catch (final SQLException e) {
             handleException(e);
             return null;
@@ -959,32 +969,78 @@ public class DelegatingConnection<C extends Connection> extends AbandonedTrace i
     }
 
     @Override
-    public void abort(final Executor executor) throws SQLException {
+    public void setSchema(final String schema) throws SQLException {
+        checkOpen();
         try {
-            Jdbc41Bridge.abort(connection, executor);
+            Jdbc41Bridge.setSchema(connection, schema);
+            if (cacheState) {
+                cachedSchema = connection.getSchema();
+            }
         } catch (final SQLException e) {
+            cachedSchema = null;
             handleException(e);
         }
     }
 
     @Override
-    public void setNetworkTimeout(final Executor executor, final int milliseconds) throws SQLException {
+    public void setTransactionIsolation(final int level) throws SQLException {
         checkOpen();
         try {
-            Jdbc41Bridge.setNetworkTimeout(connection, executor, milliseconds);
+            connection.setTransactionIsolation(level);
         } catch (final SQLException e) {
             handleException(e);
         }
     }
 
     @Override
-    public int getNetworkTimeout() throws SQLException {
+    public void setTypeMap(final Map<String, Class<?>> map) throws SQLException {
         checkOpen();
         try {
-            return Jdbc41Bridge.getNetworkTimeout(connection);
+            connection.setTypeMap(map);
         } catch (final SQLException e) {
             handleException(e);
-            return 0;
         }
     }
+
+    /**
+     * Returns a string representation of the metadata associated with the innermost delegate connection.
+     */
+    @Override
+    public synchronized String toString() {
+        String str = null;
+
+        final Connection conn = this.getInnermostDelegateInternal();
+        if (conn != null) {
+            try {
+                if (conn.isClosed()) {
+                    str = "connection is closed";
+                } else {
+                    final StringBuilder sb = new StringBuilder();
+                    sb.append(hashCode());
+                    final DatabaseMetaData meta = conn.getMetaData();
+                    if (meta != null) {
+                        sb.append(", URL=");
+                        sb.append(meta.getURL());
+                        sb.append(", ");
+                        sb.append(meta.getDriverName());
+                        str = sb.toString();
+                    }
+                }
+            } catch (final SQLException ex) {
+                // Ignore
+            }
+        }
+        return str != null ? str : super.toString();
+    }
+
+    @Override
+    public <T> T unwrap(final Class<T> iface) throws SQLException {
+        if (iface.isAssignableFrom(getClass())) {
+            return iface.cast(this);
+        }
+        if (iface.isAssignableFrom(connection.getClass())) {
+            return iface.cast(connection);
+        }
+        return connection.unwrap(iface);
+    }
 }
diff --git a/java/org/apache/tomcat/dbcp/dbcp2/DelegatingDatabaseMetaData.java b/java/org/apache/tomcat/dbcp/dbcp2/DelegatingDatabaseMetaData.java
index 7b45869..00f816c 100644
--- a/java/org/apache/tomcat/dbcp/dbcp2/DelegatingDatabaseMetaData.java
+++ b/java/org/apache/tomcat/dbcp/dbcp2/DelegatingDatabaseMetaData.java
@@ -984,11 +984,10 @@ public class DelegatingDatabaseMetaData implements DatabaseMetaData {
     }
 
     protected void handleException(final SQLException e) throws SQLException {
-        if (connection != null) {
-            connection.handleException(e);
-        } else {
+        if (connection == null) {
             throw e;
         }
+        connection.handleException(e);
     }
 
     @Override
@@ -1025,11 +1024,11 @@ public class DelegatingDatabaseMetaData implements DatabaseMetaData {
     public boolean isWrapperFor(final Class<?> iface) throws SQLException {
         if (iface.isAssignableFrom(getClass())) {
             return true;
-        } else if (iface.isAssignableFrom(databaseMetaData.getClass())) {
+        }
+        if (iface.isAssignableFrom(databaseMetaData.getClass())) {
             return true;
-        } else {
-            return databaseMetaData.isWrapperFor(iface);
         }
+        return databaseMetaData.isWrapperFor(iface);
     }
 
     @Override
@@ -1805,8 +1804,6 @@ public class DelegatingDatabaseMetaData implements DatabaseMetaData {
         }
     }
 
-    /* JDBC_4_ANT_KEY_BEGIN */
-
     @Override
     public boolean supportsSubqueriesInComparisons() throws SQLException {
         try {
@@ -1903,11 +1900,11 @@ public class DelegatingDatabaseMetaData implements DatabaseMetaData {
     public <T> T unwrap(final Class<T> iface) throws SQLException {
         if (iface.isAssignableFrom(getClass())) {
             return iface.cast(this);
-        } else if (iface.isAssignableFrom(databaseMetaData.getClass())) {
+        }
+        if (iface.isAssignableFrom(databaseMetaData.getClass())) {
             return iface.cast(databaseMetaData);
-        } else {
-            return databaseMetaData.unwrap(iface);
         }
+        return databaseMetaData.unwrap(iface);
     }
 
     @Override
diff --git a/java/org/apache/tomcat/dbcp/dbcp2/DelegatingResultSet.java b/java/org/apache/tomcat/dbcp/dbcp2/DelegatingResultSet.java
index 56a01fd..62b1a3b 100644
--- a/java/org/apache/tomcat/dbcp/dbcp2/DelegatingResultSet.java
+++ b/java/org/apache/tomcat/dbcp/dbcp2/DelegatingResultSet.java
@@ -102,15 +102,15 @@ public final class DelegatingResultSet extends AbandonedTrace implements ResultS
      * Private to ensure all construction is {@link #wrapResultSet(Connection, ResultSet)}
      * </p>
      *
-     * @param conn
+     * @param connection
      *            Connection which created this ResultSet
-     * @param res
+     * @param resultSet
      *            ResultSet to wrap
      */
-    private DelegatingResultSet(final Connection conn, final ResultSet res) {
-        super((AbandonedTrace) conn);
-        this.connection = conn;
-        this.resultSet = res;
+    private DelegatingResultSet(final Connection connection, final ResultSet resultSet) {
+        super((AbandonedTrace) connection);
+        this.connection = connection;
+        this.resultSet = resultSet;
     }
 
     /**
@@ -1117,11 +1117,11 @@ public final class DelegatingResultSet extends AbandonedTrace implements ResultS
     public boolean isWrapperFor(final Class<?> iface) throws SQLException {
         if (iface.isAssignableFrom(getClass())) {
             return true;
-        } else if (iface.isAssignableFrom(resultSet.getClass())) {
+        }
+        if (iface.isAssignableFrom(resultSet.getClass())) {
             return true;
-        } else {
-            return resultSet.isWrapperFor(iface);
         }
+        return resultSet.isWrapperFor(iface);
     }
 
     @Override
@@ -1248,11 +1248,11 @@ public final class DelegatingResultSet extends AbandonedTrace implements ResultS
     public <T> T unwrap(final Class<T> iface) throws SQLException {
         if (iface.isAssignableFrom(getClass())) {
             return iface.cast(this);
-        } else if (iface.isAssignableFrom(resultSet.getClass())) {
+        }
+        if (iface.isAssignableFrom(resultSet.getClass())) {
             return iface.cast(resultSet);
-        } else {
-            return resultSet.unwrap(iface);
         }
+        return resultSet.unwrap(iface);
     }
 
     @Override
diff --git a/java/org/apache/tomcat/dbcp/dbcp2/DelegatingStatement.java b/java/org/apache/tomcat/dbcp/dbcp2/DelegatingStatement.java
index c3c4a08..3f3481b 100644
--- a/java/org/apache/tomcat/dbcp/dbcp2/DelegatingStatement.java
+++ b/java/org/apache/tomcat/dbcp/dbcp2/DelegatingStatement.java
@@ -43,7 +43,7 @@ public class DelegatingStatement extends AbandonedTrace implements Statement {
     /** The connection that created me. **/
     private DelegatingConnection<?> connection;
 
-    private boolean closed = false;
+    private boolean closed;
 
     /**
      * Create a wrapper for the Statement which traces this Statement to the Connection which created it and the code
@@ -625,11 +625,10 @@ public class DelegatingStatement extends AbandonedTrace implements Statement {
     }
 
     protected void handleException(final SQLException e) throws SQLException {
-        if (connection != null) {
-            connection.handleException(e);
-        } else {
+        if (connection == null) {
             throw e;
         }
+        connection.handleException(e);
     }
 
     /*
@@ -670,11 +669,11 @@ public class DelegatingStatement extends AbandonedTrace implements Statement {
     public boolean isWrapperFor(final Class<?> iface) throws SQLException {
         if (iface.isAssignableFrom(getClass())) {
             return true;
-        } else if (iface.isAssignableFrom(statement.getClass())) {
+        }
+        if (iface.isAssignableFrom(statement.getClass())) {
             return true;
-        } else {
-            return statement.isWrapperFor(iface);
         }
+        return statement.isWrapperFor(iface);
     }
 
     /**
@@ -816,10 +815,10 @@ public class DelegatingStatement extends AbandonedTrace implements Statement {
     public <T> T unwrap(final Class<T> iface) throws SQLException {
         if (iface.isAssignableFrom(getClass())) {
             return iface.cast(this);
-        } else if (iface.isAssignableFrom(statement.getClass())) {
+        }
+        if (iface.isAssignableFrom(statement.getClass())) {
             return iface.cast(statement);
-        } else {
-            return statement.unwrap(iface);
         }
+        return statement.unwrap(iface);
     }
 }
diff --git a/java/org/apache/tomcat/dbcp/dbcp2/Jdbc41Bridge.java b/java/org/apache/tomcat/dbcp/dbcp2/Jdbc41Bridge.java
index 15b943d..42f29c4 100644
--- a/java/org/apache/tomcat/dbcp/dbcp2/Jdbc41Bridge.java
+++ b/java/org/apache/tomcat/dbcp/dbcp2/Jdbc41Bridge.java
@@ -77,6 +77,29 @@ public class Jdbc41Bridge {
     }
 
     /**
+     * Delegates to {@link Statement#closeOnCompletion()} without throwing an {@link AbstractMethodError}.
+     * <p>
+     * If the JDBC driver does not implement {@link Statement#closeOnCompletion()}, then just check that the connection
+     * is closed to then throw an SQLException.
+     * </p>
+     *
+     * @param statement
+     *            See {@link Statement#closeOnCompletion()}
+     * @throws SQLException
+     *             See {@link Statement#closeOnCompletion()}
+     * @see Statement#closeOnCompletion()
+     */
+    public static void closeOnCompletion(final Statement statement) throws SQLException {
+        try {
+            statement.closeOnCompletion();
+        } catch (final AbstractMethodError e) {
+            if (statement.isClosed()) {
+                throw new SQLException("Statement closed");
+            }
+        }
+    }
+
+    /**
      * Delegates to {@link DatabaseMetaData#generatedKeyAlwaysReturned()} without throwing a
      * {@link AbstractMethodError}.
      * <p>
@@ -315,6 +338,26 @@ public class Jdbc41Bridge {
     }
 
     /**
+     * Delegates to {@link CommonDataSource#getParentLogger()} without throwing an {@link AbstractMethodError}.
+     * <p>
+     * If the JDBC driver does not implement {@link CommonDataSource#getParentLogger()}, then return null.
+     * </p>
+     *
+     * @param commonDataSource
+     *            See {@link CommonDataSource#getParentLogger()}
+     * @return See {@link CommonDataSource#getParentLogger()}
+     * @throws SQLFeatureNotSupportedException
+     *             See {@link CommonDataSource#getParentLogger()}
+     */
+    public static Logger getParentLogger(final CommonDataSource commonDataSource) throws SQLFeatureNotSupportedException {
+        try {
+            return commonDataSource.getParentLogger();
+        } catch (final AbstractMethodError e) {
+            throw new SQLFeatureNotSupportedException("javax.sql.CommonDataSource#getParentLogger()");
+        }
+    }
+
+    /**
      * Delegates to {@link DatabaseMetaData#getPseudoColumns(String, String, String, String)} without throwing a
      * {@link AbstractMethodError}.
      * <p>
@@ -371,6 +414,31 @@ public class Jdbc41Bridge {
     }
 
     /**
+     * Delegates to {@link Statement#isCloseOnCompletion()} without throwing an {@link AbstractMethodError}.
+     * <p>
+     * If the JDBC driver does not implement {@link Statement#isCloseOnCompletion()}, then just check that the
+     * connection is closed to then throw an SQLException.
+     * </p>
+     *
+     * @param statement
+     *            See {@link Statement#isCloseOnCompletion()}
+     * @return See {@link Statement#isCloseOnCompletion()}
+     * @throws SQLException
+     *             See {@link Statement#isCloseOnCompletion()}
+     * @see Statement#closeOnCompletion()
+     */
+    public static boolean isCloseOnCompletion(final Statement statement) throws SQLException {
+        try {
+            return statement.isCloseOnCompletion();
+        } catch (final AbstractMethodError e) {
+            if (statement.isClosed()) {
+                throw new SQLException("Statement closed");
+            }
+            return false;
+        }
+    }
+
+    /**
      * Delegates to {@link Connection#setNetworkTimeout(Executor, int)} without throwing an {@link AbstractMethodError}.
      * <p>
      * If the JDBC driver does not implement {@link Connection#setNetworkTimeout(Executor, int)}, then do nothing.
@@ -417,72 +485,4 @@ public class Jdbc41Bridge {
         }
     }
 
-    /**
-     * Delegates to {@link Statement#closeOnCompletion()} without throwing an {@link AbstractMethodError}.
-     * <p>
-     * If the JDBC driver does not implement {@link Statement#closeOnCompletion()}, then just check that the connection
-     * is closed to then throw an SQLException.
-     * </p>
-     *
-     * @param statement
-     *            See {@link Statement#closeOnCompletion()}
-     * @throws SQLException
-     *             See {@link Statement#closeOnCompletion()}
-     * @see Statement#closeOnCompletion()
-     */
-    public static void closeOnCompletion(final Statement statement) throws SQLException {
-        try {
-            statement.closeOnCompletion();
-        } catch (final AbstractMethodError e) {
-            if (statement.isClosed()) {
-                throw new SQLException("Statement closed");
-            }
-        }
-    }
-
-    /**
-     * Delegates to {@link Statement#isCloseOnCompletion()} without throwing an {@link AbstractMethodError}.
-     * <p>
-     * If the JDBC driver does not implement {@link Statement#isCloseOnCompletion()}, then just check that the
-     * connection is closed to then throw an SQLException.
-     * </p>
-     *
-     * @param statement
-     *            See {@link Statement#isCloseOnCompletion()}
-     * @return See {@link Statement#isCloseOnCompletion()}
-     * @throws SQLException
-     *             See {@link Statement#isCloseOnCompletion()}
-     * @see Statement#closeOnCompletion()
-     */
-    public static boolean isCloseOnCompletion(final Statement statement) throws SQLException {
-        try {
-            return statement.isCloseOnCompletion();
-        } catch (final AbstractMethodError e) {
-            if (statement.isClosed()) {
-                throw new SQLException("Statement closed");
-            }
-            return false;
-        }
-    }
-
-    /**
-     * Delegates to {@link CommonDataSource#getParentLogger()} without throwing an {@link AbstractMethodError}.
-     * <p>
-     * If the JDBC driver does not implement {@link CommonDataSource#getParentLogger()}, then return null.
-     * </p>
-     *
-     * @param commonDataSource
-     *            See {@link CommonDataSource#getParentLogger()}
-     * @return See {@link CommonDataSource#getParentLogger()}
-     * @throws SQLFeatureNotSupportedException
-     *             See {@link CommonDataSource#getParentLogger()}
-     */
-    public static Logger getParentLogger(final CommonDataSource commonDataSource) throws SQLFeatureNotSupportedException {
-        try {
-            return commonDataSource.getParentLogger();
-        } catch (final AbstractMethodError e) {
-            throw new SQLFeatureNotSupportedException("javax.sql.CommonDataSource#getParentLogger()");
-        }
-    }
-
 }
diff --git a/java/org/apache/tomcat/dbcp/dbcp2/PStmtKey.java b/java/org/apache/tomcat/dbcp/dbcp2/PStmtKey.java
index 3b5daf3..87991e7 100644
--- a/java/org/apache/tomcat/dbcp/dbcp2/PStmtKey.java
+++ b/java/org/apache/tomcat/dbcp/dbcp2/PStmtKey.java
@@ -852,7 +852,7 @@ public class PStmtKey {
      * @return An array of column indexes.
      */
     public int[] getColumnIndexes() {
-        return columnIndexes;
+        return columnIndexes == null ? null : columnIndexes.clone();
     }
 
     /**
@@ -861,7 +861,7 @@ public class PStmtKey {
      * @return An array of column names.
      */
     public String[] getColumnNames() {
-        return columnNames;
+        return columnNames == null ? null : columnNames.clone();
     }
 
     /**
diff --git a/java/org/apache/tomcat/dbcp/dbcp2/PoolableCallableStatement.java b/java/org/apache/tomcat/dbcp/dbcp2/PoolableCallableStatement.java
index 683a24f..16e27a1 100644
--- a/java/org/apache/tomcat/dbcp/dbcp2/PoolableCallableStatement.java
+++ b/java/org/apache/tomcat/dbcp/dbcp2/PoolableCallableStatement.java
@@ -71,6 +71,21 @@ public class PoolableCallableStatement extends DelegatingCallableStatement {
     }
 
     /**
+     * Activates after retrieval from the pool. Adds a trace for this CallableStatement to the Connection that created
+     * it.
+     *
+     * @since 2.4.0 made public, was protected in 2.3.0.
+     */
+    @Override
+    public void activate() throws SQLException {
+        setClosedInternal(false);
+        if (getConnectionInternal() != null) {
+            getConnectionInternal().addTrace(this);
+        }
+        super.activate();
+    }
+
+    /**
      * Returns the CallableStatement to the pool. If {{@link #isClosed()}, this is a No-op.
      */
     @Override
@@ -88,21 +103,6 @@ public class PoolableCallableStatement extends DelegatingCallableStatement {
     }
 
     /**
-     * Activates after retrieval from the pool. Adds a trace for this CallableStatement to the Connection that created
-     * it.
-     *
-     * @since 2.4.0 made public, was protected in 2.3.0.
-     */
-    @Override
-    public void activate() throws SQLException {
-        setClosedInternal(false);
-        if (getConnectionInternal() != null) {
-            getConnectionInternal().addTrace(this);
-        }
-        super.activate();
-    }
-
-    /**
      * Passivates to prepare for return to the pool. Removes the trace associated with this CallableStatement from the
      * Connection that created it. Also closes any associated ResultSets.
      *
diff --git a/java/org/apache/tomcat/dbcp/dbcp2/PoolableConnection.java b/java/org/apache/tomcat/dbcp/dbcp2/PoolableConnection.java
index 6b052ce..52243c5 100644
--- a/java/org/apache/tomcat/dbcp/dbcp2/PoolableConnection.java
+++ b/java/org/apache/tomcat/dbcp/dbcp2/PoolableConnection.java
@@ -21,6 +21,7 @@ import java.sql.Connection;
 import java.sql.PreparedStatement;
 import java.sql.ResultSet;
 import java.sql.SQLException;
+import java.time.Duration;
 import java.util.Collection;
 import java.util.concurrent.Executor;
 
@@ -64,7 +65,7 @@ public class PoolableConnection extends DelegatingConnection<Connection> impleme
      * Indicate that unrecoverable SQLException was thrown when using this connection. Such a connection should be
      * considered broken and not pass validation in the future.
      */
-    private boolean fatalSqlExceptionThrown = false;
+    private boolean fatalSqlExceptionThrown;
 
     /**
      * SQL_STATE codes considered to signal fatal conditions. Overrides the defaults in
@@ -81,6 +82,20 @@ public class PoolableConnection extends DelegatingConnection<Connection> impleme
      *            my underlying connection
      * @param pool
      *            the pool to which I should return when closed
+     * @param jmxName
+     *            JMX name
+     */
+    public PoolableConnection(final Connection conn, final ObjectPool<PoolableConnection> pool,
+            final ObjectName jmxName) {
+        this(conn, pool, jmxName, null, true);
+    }
+
+    /**
+     *
+     * @param conn
+     *            my underlying connection
+     * @param pool
+     *            the pool to which I should return when closed
      * @param jmxObjectName
      *            JMX name
      * @param disconnectSqlCodes
@@ -108,50 +123,16 @@ public class PoolableConnection extends DelegatingConnection<Connection> impleme
     }
 
     /**
+     * Abort my underlying {@link Connection}.
      *
-     * @param conn
-     *            my underlying connection
-     * @param pool
-     *            the pool to which I should return when closed
-     * @param jmxName
-     *            JMX name
-     */
-    public PoolableConnection(final Connection conn, final ObjectPool<PoolableConnection> pool,
-            final ObjectName jmxName) {
-        this(conn, pool, jmxName, null, true);
-    }
-
-    @Override
-    protected void passivate() throws SQLException {
-        super.passivate();
-        setClosedInternal(true);
-        if (getDelegateInternal() instanceof PoolingConnection) {
-            ((PoolingConnection) getDelegateInternal()).connectionReturnedToPool();
-        }
-    }
-
-    /**
-     * {@inheritDoc}
-     * <p>
-     * This method should not be used by a client to determine whether or not a connection should be return to the
-     * connection pool (by calling {@link #close()}). Clients should always attempt to return a connection to the pool
-     * once it is no longer required.
+     * @since 2.9.0
      */
     @Override
-    public boolean isClosed() throws SQLException {
-        if (isClosedInternal()) {
-            return true;
-        }
-
-        if (getDelegateInternal().isClosed()) {
-            // Something has gone wrong. The underlying connection has been
-            // closed without the connection being returned to the pool. Return
-            // it now.
-            close();
-            return true;
+    public void abort(final Executor executor) throws SQLException {
+        if (jmxObjectName != null) {
+            jmxObjectName.unregisterMBean();
         }
-
-        return false;
+        super.abort(executor);
     }
 
     /**
@@ -215,44 +196,132 @@ public class PoolableConnection extends DelegatingConnection<Connection> impleme
     }
 
     /**
-     * Actually close my underlying {@link Connection}.
+     * @return The disconnection SQL codes.
+     * @since 2.6.0
+     */
+    public Collection<String> getDisconnectionSqlCodes() {
+        return disconnectionSqlCodes;
+    }
+
+    /**
+     * Expose the {@link #toString()} method via a bean getter so it can be read as a property via JMX.
      */
     @Override
-    public void reallyClose() throws SQLException {
-        if (jmxObjectName != null) {
-            jmxObjectName.unregisterMBean();
+    public String getToString() {
+        return toString();
+    }
+
+    @Override
+    protected void handleException(final SQLException e) throws SQLException {
+        fatalSqlExceptionThrown |= isFatalException(e);
+        super.handleException(e);
+    }
+
+    /**
+     * {@inheritDoc}
+     * <p>
+     * This method should not be used by a client to determine whether or not a connection should be return to the
+     * connection pool (by calling {@link #close()}). Clients should always attempt to return a connection to the pool
+     * once it is no longer required.
+     */
+    @Override
+    public boolean isClosed() throws SQLException {
+        if (isClosedInternal()) {
+            return true;
         }
 
-        if (validationPreparedStatement != null) {
-            try {
-                validationPreparedStatement.close();
-            } catch (final SQLException sqle) {
-                // Ignore
-            }
+        if (getDelegateInternal().isClosed()) {
+            // Something has gone wrong. The underlying connection has been
+            // closed without the connection being returned to the pool. Return
+            // it now.
+            close();
+            return true;
         }
 
-        super.closeInternal();
+        return false;
     }
 
     /**
-     * Abort my underlying {@link Connection}.
+     * Checks the SQLState of the input exception.
+     * <p>
+     * If {@link #disconnectionSqlCodes} has been set, sql states are compared to those in the configured list of fatal
+     * exception codes. If this property is not set, codes are compared against the default codes in
+     * {@link Utils#DISCONNECTION_SQL_CODES} and in this case anything starting with #{link
+     * Utils.DISCONNECTION_SQL_CODE_PREFIX} is considered a disconnection.
+     * </p>
      *
-     * @since 2.9.0
+     * @param e SQLException to be examined
+     * @return true if the exception signals a disconnection
+     */
+    boolean isDisconnectionSqlException(final SQLException e) {
+        boolean fatalException = false;
+        final String sqlState = e.getSQLState();
+        if (sqlState != null) {
+            fatalException = disconnectionSqlCodes == null
+                ? sqlState.startsWith(Utils.DISCONNECTION_SQL_CODE_PREFIX) || Utils.DISCONNECTION_SQL_CODES.contains(sqlState)
+                : disconnectionSqlCodes.contains(sqlState);
+        }
+        return fatalException;
+    }
+
+    /**
+     * Checks the SQLState of the input exception and any nested SQLExceptions it wraps.
+     * <p>
+     * If {@link #disconnectionSqlCodes} has been set, sql states are compared to those in the
+     * configured list of fatal exception codes. If this property is not set, codes are compared against the default
+     * codes in {@link Utils#DISCONNECTION_SQL_CODES} and in this case anything starting with #{link
+     * Utils.DISCONNECTION_SQL_CODE_PREFIX} is considered a disconnection.
+     * </p>
+     *
+     * @param e
+     *            SQLException to be examined
+     * @return true if the exception signals a disconnection
+     */
+    boolean isFatalException(final SQLException e) {
+        boolean fatalException = isDisconnectionSqlException(e);
+        if (!fatalException) {
+            SQLException parentException = e;
+            SQLException nextException = e.getNextException();
+            while (nextException != null && nextException != parentException && !fatalException) {
+                fatalException = isDisconnectionSqlException(nextException);
+                parentException = nextException;
+                nextException = parentException.getNextException();
+            }
+        }
+        return fatalException;
+    }
+
+    /**
+     * @return Whether to fail-fast.
+     * @since 2.6.0
      */
+    public boolean isFastFailValidation() {
+        return fastFailValidation;
+    }
+
     @Override
-    public void abort(Executor executor) throws SQLException {
-        if (jmxObjectName != null) {
-            jmxObjectName.unregisterMBean();
+    protected void passivate() throws SQLException {
+        super.passivate();
+        setClosedInternal(true);
+        if (getDelegateInternal() instanceof PoolingConnection) {
+            ((PoolingConnection) getDelegateInternal()).connectionReturnedToPool();
         }
-        super.abort(executor);
     }
 
     /**
-     * Expose the {@link #toString()} method via a bean getter so it can be read as a property via JMX.
+     * Actually close my underlying {@link Connection}.
      */
     @Override
-    public String getToString() {
-        return toString();
+    public void reallyClose() throws SQLException {
+        if (jmxObjectName != null) {
+            jmxObjectName.unregisterMBean();
+        }
+
+        if (validationPreparedStatement != null) {
+            Utils.closeQuietly((AutoCloseable) validationPreparedStatement);
+        }
+
+        super.closeInternal();
     }
 
     /**
@@ -272,17 +341,42 @@ public class PoolableConnection extends DelegatingConnection<Connection> impleme
      *            The validation timeout in seconds.
      * @throws SQLException
      *             Thrown when validation fails or an SQLException occurs during validation
+     * @deprecated Use {@link #validate(String, Duration)}.
      */
-    public void validate(final String sql, int timeoutSeconds) throws SQLException {
+    @Deprecated
+    public void validate(final String sql, final int timeoutSeconds) throws SQLException {
+        validate(sql, Duration.ofSeconds(timeoutSeconds));
+    }
+
+    /**
+     * Validates the connection, using the following algorithm:
+     * <ol>
+     * <li>If {@code fastFailValidation} (constructor argument) is {@code true} and this connection has previously
+     * thrown a fatal disconnection exception, a {@code SQLException} is thrown.</li>
+     * <li>If {@code sql} is null, the driver's #{@link Connection#isValid(int) isValid(timeout)} is called. If it
+     * returns {@code false}, {@code SQLException} is thrown; otherwise, this method returns successfully.</li>
+     * <li>If {@code sql} is not null, it is executed as a query and if the resulting {@code ResultSet} contains at
+     * least one row, this method returns successfully. If not, {@code SQLException} is thrown.</li>
+     * </ol>
+     *
+     * @param sql
+     *            The validation SQL query.
+     * @param timeoutDuration
+     *            The validation timeout in seconds.
+     * @throws SQLException
+     *             Thrown when validation fails or an SQLException occurs during validation
+     * @since 2.10.0
+     */
+    public void validate(final String sql, Duration timeoutDuration) throws SQLException {
         if (fastFailValidation && fatalSqlExceptionThrown) {
             throw new SQLException(Utils.getMessage("poolableConnection.validate.fastFail"));
         }
 
-        if (sql == null || sql.length() == 0) {
-            if (timeoutSeconds < 0) {
-                timeoutSeconds = 0;
+        if (sql == null || sql.isEmpty()) {
+            if (timeoutDuration.isNegative()) {
+                timeoutDuration = Duration.ZERO;
             }
-            if (!isValid(timeoutSeconds)) {
+            if (!isValid(timeoutDuration)) {
                 throw new SQLException("isValid() returned false");
             }
             return;
@@ -295,8 +389,8 @@ public class PoolableConnection extends DelegatingConnection<Connection> impleme
             validationPreparedStatement = getInnermostDelegateInternal().prepareStatement(sql);
         }
 
-        if (timeoutSeconds > 0) {
-            validationPreparedStatement.setQueryTimeout(timeoutSeconds);
+        if (timeoutDuration.compareTo(Duration.ZERO) > 0) {
+            validationPreparedStatement.setQueryTimeout((int) timeoutDuration.getSeconds());
         }
 
         try (ResultSet rs = validationPreparedStatement.executeQuery()) {
@@ -307,57 +401,4 @@ public class PoolableConnection extends DelegatingConnection<Connection> impleme
             throw sqle;
         }
     }
-
-    /**
-     * Checks the SQLState of the input exception and any nested SQLExceptions it wraps.
-     * <p>
-     * If {@link #disconnectionSqlCodes} has been set, sql states are compared to those in the
-     * configured list of fatal exception codes. If this property is not set, codes are compared against the default
-     * codes in {@link Utils#DISCONNECTION_SQL_CODES} and in this case anything starting with #{link
-     * Utils.DISCONNECTION_SQL_CODE_PREFIX} is considered a disconnection.
-     * </p>
-     *
-     * @param e
-     *            SQLException to be examined
-     * @return true if the exception signals a disconnection
-     */
-    private boolean isDisconnectionSqlException(final SQLException e) {
-        boolean fatalException = false;
-        final String sqlState = e.getSQLState();
-        if (sqlState != null) {
-            fatalException = disconnectionSqlCodes == null
-                    ? sqlState.startsWith(Utils.DISCONNECTION_SQL_CODE_PREFIX)
-                            || Utils.DISCONNECTION_SQL_CODES.contains(sqlState)
-                    : disconnectionSqlCodes.contains(sqlState);
-            if (!fatalException) {
-                final SQLException nextException = e.getNextException();
-                if (nextException != null && nextException != e) {
-                    fatalException = isDisconnectionSqlException(e.getNextException());
-                }
-            }
-        }
-        return fatalException;
-    }
-
-    @Override
-    protected void handleException(final SQLException e) throws SQLException {
-        fatalSqlExceptionThrown |= isDisconnectionSqlException(e);
-        super.handleException(e);
-    }
-
-    /**
-     * @return The disconnection SQL codes.
-     * @since 2.6.0
-     */
-    public Collection<String> getDisconnectionSqlCodes() {
-        return disconnectionSqlCodes;
-    }
-
-    /**
-     * @return Whether to fail-fast.
-     * @since 2.6.0
-     */
-    public boolean isFastFailValidation() {
-        return fastFailValidation;
-    }
 }
diff --git a/java/org/apache/tomcat/dbcp/dbcp2/PoolableConnectionFactory.java b/java/org/apache/tomcat/dbcp/dbcp2/PoolableConnectionFactory.java
index 84f92f4..f95c262 100644
--- a/java/org/apache/tomcat/dbcp/dbcp2/PoolableConnectionFactory.java
+++ b/java/org/apache/tomcat/dbcp/dbcp2/PoolableConnectionFactory.java
@@ -19,6 +19,8 @@ package org.apache.tomcat.dbcp.dbcp2;
 import java.sql.Connection;
 import java.sql.SQLException;
 import java.sql.Statement;
+import java.time.Duration;
+import java.time.Instant;
 import java.util.Collection;
 import java.util.Objects;
 import java.util.concurrent.atomic.AtomicLong;
@@ -56,7 +58,7 @@ public class PoolableConnectionFactory implements PooledObjectFactory<PoolableCo
 
     private volatile String validationQuery;
 
-    private volatile int validationQueryTimeoutSeconds = -1;
+    private volatile Duration validationQueryTimeoutDuration = Duration.ofSeconds(-1);
 
     private Collection<String> connectionInitSqls;
 
@@ -88,11 +90,11 @@ public class PoolableConnectionFactory implements PooledObjectFactory<PoolableCo
 
     private int maxOpenPreparedStatements = GenericKeyedObjectPoolConfig.DEFAULT_MAX_TOTAL_PER_KEY;
 
-    private long maxConnLifetimeMillis = -1;
+    private Duration maxConnDuration = Duration.ofMillis(-1);
 
-    private final AtomicLong connectionIndex = new AtomicLong(0);
+    private final AtomicLong connectionIndex = new AtomicLong();
 
-    private Integer defaultQueryTimeoutSeconds;
+    private Duration defaultQueryTimeoutDuration;
 
     /**
      * Creates a new {@code PoolableConnectionFactory}.
@@ -112,26 +114,26 @@ public class PoolableConnectionFactory implements PooledObjectFactory<PoolableCo
 
         validateLifetime(p);
 
-        final PoolableConnection conn = p.getObject();
-        conn.activate();
+        final PoolableConnection pConnection = p.getObject();
+        pConnection.activate();
 
-        if (defaultAutoCommit != null && conn.getAutoCommit() != defaultAutoCommit.booleanValue()) {
-            conn.setAutoCommit(defaultAutoCommit.booleanValue());
+        if (defaultAutoCommit != null && pConnection.getAutoCommit() != defaultAutoCommit.booleanValue()) {
+            pConnection.setAutoCommit(defaultAutoCommit.booleanValue());
         }
         if (defaultTransactionIsolation != UNKNOWN_TRANSACTION_ISOLATION
-                && conn.getTransactionIsolation() != defaultTransactionIsolation) {
-            conn.setTransactionIsolation(defaultTransactionIsolation);
+                && pConnection.getTransactionIsolation() != defaultTransactionIsolation) {
+            pConnection.setTransactionIsolation(defaultTransactionIsolation);
         }
-        if (defaultReadOnly != null && conn.isReadOnly() != defaultReadOnly.booleanValue()) {
-            conn.setReadOnly(defaultReadOnly.booleanValue());
+        if (defaultReadOnly != null && pConnection.isReadOnly() != defaultReadOnly.booleanValue()) {
+            pConnection.setReadOnly(defaultReadOnly.booleanValue());
         }
-        if (defaultCatalog != null && !defaultCatalog.equals(conn.getCatalog())) {
-            conn.setCatalog(defaultCatalog);
+        if (defaultCatalog != null && !defaultCatalog.equals(pConnection.getCatalog())) {
+            pConnection.setCatalog(defaultCatalog);
         }
-        if (defaultSchema != null && !defaultSchema.equals(Jdbc41Bridge.getSchema(conn))) {
-            Jdbc41Bridge.setSchema(conn, defaultSchema);
+        if (defaultSchema != null && !defaultSchema.equals(Jdbc41Bridge.getSchema(pConnection))) {
+            Jdbc41Bridge.setSchema(pConnection, defaultSchema);
         }
-        conn.setDefaultQueryTimeout(defaultQueryTimeoutSeconds);
+        pConnection.setDefaultQueryTimeout(defaultQueryTimeoutDuration);
     }
 
     @Override
@@ -144,7 +146,7 @@ public class PoolableConnectionFactory implements PooledObjectFactory<PoolableCo
      */
     @Override
     public void destroyObject(final PooledObject<PoolableConnection> p, final DestroyMode mode) throws Exception {
-        if (mode != null && mode.equals(DestroyMode.ABANDONED)) {
+        if (mode == DestroyMode.ABANDONED) {
             p.getObject().getInnermostDelegate().abort(Runnable::run);
         } else {
             p.getObject().reallyClose();
@@ -152,6 +154,8 @@ public class PoolableConnectionFactory implements PooledObjectFactory<PoolableCo
     }
 
     /**
+     * Gets the cache state.
+     *
      * @return The cache state.
      * @since Made public in 2.6.0.
      */
@@ -160,6 +164,8 @@ public class PoolableConnectionFactory implements PooledObjectFactory<PoolableCo
     }
 
     /**
+     * Gets the connection factory.
+     *
      * @return The connection factory.
      * @since Made public in 2.6.0.
      */
@@ -213,17 +219,31 @@ public class PoolableConnectionFactory implements PooledObjectFactory<PoolableCo
 
     /**
      * @return Default query timeout in seconds.
+     * @deprecated Use {@link #getDefaultQueryTimeoutDuration()}.
      */
+    @Deprecated
     public Integer getDefaultQueryTimeout() {
-        return defaultQueryTimeoutSeconds;
+        return getDefaultQueryTimeoutSeconds();
+    }
+
+    /**
+     * Gets the default query timeout Duration.
+     *
+     * @return Default query timeout Duration.
+     * @since 2.10.0
+     */
+    public Duration getDefaultQueryTimeoutDuration() {
+        return defaultQueryTimeoutDuration;
     }
 
     /**
      * @return Default query timeout in seconds.
      * @since 2.6.0
+     * @deprecated Use {@link #getDefaultQueryTimeoutDuration()}.
      */
+    @Deprecated
     public Integer getDefaultQueryTimeoutSeconds() {
-        return defaultQueryTimeoutSeconds;
+        return defaultQueryTimeoutDuration == null ? null : Integer.valueOf((int) defaultQueryTimeoutDuration.getSeconds());
     }
 
     /**
@@ -271,11 +291,23 @@ public class PoolableConnectionFactory implements PooledObjectFactory<PoolableCo
     }
 
     /**
+     * Gets the Maximum connection duration.
+     *
+     * @return Maximum connection duration.
+     * @since 2.10.0
+     */
+    public Duration getMaxConnDuration() {
+        return maxConnDuration;
+    }
+
+    /**
+     * Gets the Maximum connection lifetime in milliseconds.
+     *
      * @return Maximum connection lifetime in milliseconds.
      * @since 2.6.0
      */
     public long getMaxConnLifetimeMillis() {
-        return maxConnLifetimeMillis;
+        return maxConnDuration.toMillis();
     }
 
     protected int getMaxOpenPreparedStatements() {
@@ -305,11 +337,25 @@ public class PoolableConnectionFactory implements PooledObjectFactory<PoolableCo
     }
 
     /**
+     * Gets the query timeout in seconds.
+     *
+     * @return Validation query timeout in seconds.
+     * @since 2.10.0
+     */
+    public Duration getValidationQueryTimeoutDuration() {
+        return validationQueryTimeoutDuration;
+    }
+
+    /**
+     * Gets the query timeout in seconds.
+     *
      * @return Validation query timeout in seconds.
      * @since 2.6.0
+     * @deprecated Use {@link #getValidationQueryTimeoutDuration()}.
      */
+    @Deprecated
     public int getValidationQueryTimeoutSeconds() {
-        return validationQueryTimeoutSeconds;
+        return (int) validationQueryTimeoutDuration.getSeconds();
     }
 
     protected void initializeConnection(final Connection conn) throws SQLException {
@@ -374,11 +420,7 @@ public class PoolableConnectionFactory implements PooledObjectFactory<PoolableCo
             initializeConnection(conn);
         } catch (final SQLException sqle) {
             // Make sure the connection is closed
-            try {
-                conn.close();
-            } catch (final SQLException ignore) {
-                // ignore
-            }
+            Utils.closeQuietly((AutoCloseable) conn);
             // Rethrow original exception so it is visible to caller
             throw sqle;
         }
@@ -390,7 +432,7 @@ public class PoolableConnectionFactory implements PooledObjectFactory<PoolableCo
             final GenericKeyedObjectPoolConfig<DelegatingPreparedStatement> config = new GenericKeyedObjectPoolConfig<>();
             config.setMaxTotalPerKey(-1);
             config.setBlockWhenExhausted(false);
-            config.setMaxWaitMillis(0);
+            config.setMaxWait(Duration.ZERO);
             config.setMaxIdlePerKey(1);
             config.setMaxTotal(maxOpenPreparedStatements);
             if (dataSourceJmxObjectName != null) {
@@ -505,8 +547,25 @@ public class PoolableConnectionFactory implements PooledObjectFactory<PoolableCo
         this.defaultCatalog = defaultCatalog;
     }
 
+    /**
+     * Sets the query timeout Duration.
+     *
+     * @param defaultQueryTimeoutDuration the query timeout Duration.
+     * @since 2.10.0
+     */
+    public void setDefaultQueryTimeout(final Duration defaultQueryTimeoutDuration) {
+        this.defaultQueryTimeoutDuration = defaultQueryTimeoutDuration;
+    }
+
+    /**
+     * Sets the query timeout in seconds.
+     *
+     * @param defaultQueryTimeoutSeconds the query timeout in seconds.
+     * @deprecated Use {@link #setDefaultQueryTimeout(Duration)}.
+     */
+    @Deprecated
     public void setDefaultQueryTimeout(final Integer defaultQueryTimeoutSeconds) {
-        this.defaultQueryTimeoutSeconds = defaultQueryTimeoutSeconds;
+        this.defaultQueryTimeoutDuration = defaultQueryTimeoutSeconds == null ? null : Duration.ofSeconds(defaultQueryTimeoutSeconds.longValue());
     }
 
     /**
@@ -573,11 +632,25 @@ public class PoolableConnectionFactory implements PooledObjectFactory<PoolableCo
      * Sets the maximum lifetime in milliseconds of a connection after which the connection will always fail activation,
      * passivation and validation. A value of zero or less indicates an infinite lifetime. The default value is -1.
      *
+     * @param maxConnDuration
+     *            The maximum lifetime in milliseconds.
+     * @since 2.10.0
+     */
+    public void setMaxConn(final Duration maxConnDuration) {
+        this.maxConnDuration = maxConnDuration;
+    }
+
+    /**
+     * Sets the maximum lifetime in milliseconds of a connection after which the connection will always fail activation,
+     * passivation and validation. A value of zero or less indicates an infinite lifetime. The default value is -1.
+     *
      * @param maxConnLifetimeMillis
      *            The maximum lifetime in milliseconds.
+     * @deprecated Use {@link #setMaxConn(Duration)}.
      */
+    @Deprecated
     public void setMaxConnLifetimeMillis(final long maxConnLifetimeMillis) {
-        this.maxConnLifetimeMillis = maxConnLifetimeMillis;
+        this.maxConnDuration = Duration.ofMillis(maxConnLifetimeMillis);
     }
 
     /**
@@ -610,11 +683,7 @@ public class PoolableConnectionFactory implements PooledObjectFactory<PoolableCo
      */
     public synchronized void setPool(final ObjectPool<PoolableConnection> pool) {
         if (null != this.pool && pool != this.pool) {
-            try {
-                this.pool.close();
-            } catch (final Exception e) {
-                // ignored !?!
-            }
+            Utils.closeQuietly(this.pool);
         }
         this.pool = pool;
     }
@@ -639,29 +708,47 @@ public class PoolableConnectionFactory implements PooledObjectFactory<PoolableCo
     }
 
     /**
+     * Sets the validation query timeout, the amount of time, that connection validation will wait for a response from the
+     * database when executing a validation query. Use a value less than or equal to 0 for no timeout.
+     *
+     * @param validationQueryTimeoutDuration new validation query timeout duration.
+     * @since 2.10.0
+     */
+    public void setValidationQueryTimeout(final Duration validationQueryTimeoutDuration) {
+        this.validationQueryTimeoutDuration = validationQueryTimeoutDuration;
+    }
+
+    /**
      * Sets the validation query timeout, the amount of time, in seconds, that connection validation will wait for a
      * response from the database when executing a validation query. Use a value less than or equal to 0 for no timeout.
      *
      * @param validationQueryTimeoutSeconds
      *            new validation query timeout value in seconds
+     * @deprecated {@link #setValidationQueryTimeout(Duration)}.
      */
+    @Deprecated
     public void setValidationQueryTimeout(final int validationQueryTimeoutSeconds) {
-        this.validationQueryTimeoutSeconds = validationQueryTimeoutSeconds;
+        this.validationQueryTimeoutDuration = Duration.ofSeconds(validationQueryTimeoutSeconds);
     }
 
+    /**
+     * Validates the given connection if it is open.
+     *
+     * @param conn the connection to validate.
+     * @throws SQLException if the connection is closed or validate fails.
+     */
     public void validateConnection(final PoolableConnection conn) throws SQLException {
         if (conn.isClosed()) {
             throw new SQLException("validateConnection: connection closed");
         }
-        conn.validate(validationQuery, validationQueryTimeoutSeconds);
+        conn.validate(validationQuery, validationQueryTimeoutDuration);
     }
 
     private void validateLifetime(final PooledObject<PoolableConnection> p) throws Exception {
-        if (maxConnLifetimeMillis > 0) {
-            final long lifetime = System.currentTimeMillis() - p.getCreateTime();
-            if (lifetime > maxConnLifetimeMillis) {
-                throw new LifetimeExceededException(Utils.getMessage("connectionFactory.lifetimeExceeded",
-                        Long.valueOf(lifetime), Long.valueOf(maxConnLifetimeMillis)));
+        if (maxConnDuration.compareTo(Duration.ZERO) > 0) {
+            final Duration lifetimeDuration = Duration.between(p.getCreateInstant(), Instant.now());
+            if (lifetimeDuration.compareTo(maxConnDuration) > 0) {
+                throw new LifetimeExceededException(Utils.getMessage("connectionFactory.lifetimeExceeded", lifetimeDuration, maxConnDuration));
             }
         }
     }
diff --git a/java/org/apache/tomcat/dbcp/dbcp2/PoolableConnectionMXBean.java b/java/org/apache/tomcat/dbcp/dbcp2/PoolableConnectionMXBean.java
index eaa9fdd..75db37d 100644
--- a/java/org/apache/tomcat/dbcp/dbcp2/PoolableConnectionMXBean.java
+++ b/java/org/apache/tomcat/dbcp/dbcp2/PoolableConnectionMXBean.java
@@ -24,47 +24,47 @@ import java.sql.SQLException;
  * @since 2.0
  */
 public interface PoolableConnectionMXBean {
-    // Read-only properties
-    boolean isClosed() throws SQLException;
+    // Methods
+    void clearCachedState();
 
-    // SQLWarning getWarnings() throws SQLException;
-    String getToString();
+    void clearWarnings() throws SQLException;
+
+    void close() throws SQLException;
 
     // Read-write properties
     boolean getAutoCommit() throws SQLException;
 
-    void setAutoCommit(boolean autoCommit) throws SQLException;
-
     boolean getCacheState();
 
-    void setCacheState(boolean cacheState);
-
     String getCatalog() throws SQLException;
 
-    void setCatalog(String catalog) throws SQLException;
-
     int getHoldability() throws SQLException;
 
-    void setHoldability(int holdability) throws SQLException;
+    String getSchema() throws SQLException;
 
-    boolean isReadOnly() throws SQLException;
+    // SQLWarning getWarnings() throws SQLException;
+    String getToString();
 
-    void setReadOnly(boolean readOnly) throws SQLException;
+    int getTransactionIsolation() throws SQLException;
 
-    String getSchema() throws SQLException;
+    // Read-only properties
+    boolean isClosed() throws SQLException;
 
-    void setSchema(String schema) throws SQLException;
+    boolean isReadOnly() throws SQLException;
 
-    int getTransactionIsolation() throws SQLException;
+    void reallyClose() throws SQLException;
 
-    void setTransactionIsolation(int level) throws SQLException;
+    void setAutoCommit(boolean autoCommit) throws SQLException;
 
-    // Methods
-    void clearCachedState();
+    void setCacheState(boolean cacheState);
 
-    void clearWarnings() throws SQLException;
+    void setCatalog(String catalog) throws SQLException;
 
-    void close() throws SQLException;
+    void setHoldability(int holdability) throws SQLException;
 
-    void reallyClose() throws SQLException;
+    void setReadOnly(boolean readOnly) throws SQLException;
+
+    void setSchema(String schema) throws SQLException;
+
+    void setTransactionIsolation(int level) throws SQLException;
 }
diff --git a/java/org/apache/tomcat/dbcp/dbcp2/PoolablePreparedStatement.java b/java/org/apache/tomcat/dbcp/dbcp2/PoolablePreparedStatement.java
index 32b63aa..4cedf14 100644
--- a/java/org/apache/tomcat/dbcp/dbcp2/PoolablePreparedStatement.java
+++ b/java/org/apache/tomcat/dbcp/dbcp2/PoolablePreparedStatement.java
@@ -49,7 +49,7 @@ public class PoolablePreparedStatement<K> extends DelegatingPreparedStatement {
      */
     private final K key;
 
-    private volatile boolean batchAdded = false;
+    private volatile boolean batchAdded;
 
     /**
      * Constructor.
@@ -74,6 +74,15 @@ public class PoolablePreparedStatement<K> extends DelegatingPreparedStatement {
         removeThisTrace(getConnectionInternal());
     }
 
+    @Override
+    public void activate() throws SQLException {
+        setClosedInternal(false);
+        if (getConnectionInternal() != null) {
+            getConnectionInternal().addTrace(this);
+        }
+        super.activate();
+    }
+
     /**
      * Add batch.
      */
@@ -110,15 +119,6 @@ public class PoolablePreparedStatement<K> extends DelegatingPreparedStatement {
     }
 
     @Override
-    public void activate() throws SQLException {
-        setClosedInternal(false);
-        if (getConnectionInternal() != null) {
-            getConnectionInternal().addTrace(this);
-        }
-        super.activate();
-    }
-
-    @Override
     public void passivate() throws SQLException {
         // DBCP-372. clearBatch with throw an exception if called when the
         // connection is marked as closed.
diff --git a/java/org/apache/tomcat/dbcp/dbcp2/PoolingConnection.java b/java/org/apache/tomcat/dbcp/dbcp2/PoolingConnection.java
index 4f98039..6586a49 100644
--- a/java/org/apache/tomcat/dbcp/dbcp2/PoolingConnection.java
+++ b/java/org/apache/tomcat/dbcp/dbcp2/PoolingConnection.java
@@ -64,7 +64,7 @@ public class PoolingConnection extends DelegatingConnection<Connection>
     /** Pool of {@link PreparedStatement}s. and {@link CallableStatement}s */
     private KeyedObjectPool<PStmtKey, DelegatingPreparedStatement> pstmtPool;
 
-    private boolean clearStatementPoolOnReturn = false;
+    private boolean clearStatementPoolOnReturn;
 
     /**
      * Constructor.
@@ -151,21 +151,6 @@ public class PoolingConnection extends DelegatingConnection<Connection>
      *
      * @param sql
      *            the SQL string used to define the statement
-     * @param columnIndexes
-     *            An array of column indexes indicating the columns that should be returned from the inserted row or
-     *            rows.
-     *
-     * @return the PStmtKey created for the given arguments.
-     */
-    protected PStmtKey createKey(final String sql, final int[] columnIndexes) {
-        return new PStmtKey(normalizeSQL(sql), getCatalogOrNull(), getSchemaOrNull(), columnIndexes);
-    }
-
-    /**
-     * Creates a PStmtKey for the given arguments.
-     *
-     * @param sql
-     *            the SQL string used to define the statement
      * @param autoGeneratedKeys
      *            A flag indicating whether auto-generated keys should be returned; one of
      *            <code>Statement.RETURN_GENERATED_KEYS</code> or <code>Statement.NO_GENERATED_KEYS</code>.
@@ -258,6 +243,21 @@ public class PoolingConnection extends DelegatingConnection<Connection>
      *
      * @param sql
      *            the SQL string used to define the statement
+     * @param columnIndexes
+     *            An array of column indexes indicating the columns that should be returned from the inserted row or
+     *            rows.
+     *
+     * @return the PStmtKey created for the given arguments.
+     */
+    protected PStmtKey createKey(final String sql, final int[] columnIndexes) {
+        return new PStmtKey(normalizeSQL(sql), getCatalogOrNull(), getSchemaOrNull(), columnIndexes);
+    }
+
+    /**
+     * Creates a PStmtKey for the given arguments.
+     *
+     * @param sql
+     *            the SQL string used to define the statement
      * @param statementType
      *            statement type
      *
@@ -507,24 +507,6 @@ public class PoolingConnection extends DelegatingConnection<Connection>
      *
      * @param sql
      *            the SQL string used to define the PreparedStatement
-     * @param columnIndexes
-     *            An array of column indexes indicating the columns that should be returned from the inserted row or
-     *            rows.
-     * @return a {@link PoolablePreparedStatement}
-     * @throws SQLException
-     *             Wraps an underlying exception.
-     *
-     */
-    @Override
-    public PreparedStatement prepareStatement(final String sql, final int[] columnIndexes) throws SQLException {
-        return prepareStatement(createKey(sql, columnIndexes));
-    }
-
-    /**
-     * Creates or obtains a {@link PreparedStatement} from the pool.
-     *
-     * @param sql
-     *            the SQL string used to define the PreparedStatement
      * @param resultSetType
      *            result set type
      * @param resultSetConcurrency
@@ -565,6 +547,24 @@ public class PoolingConnection extends DelegatingConnection<Connection>
      *
      * @param sql
      *            the SQL string used to define the PreparedStatement
+     * @param columnIndexes
+     *            An array of column indexes indicating the columns that should be returned from the inserted row or
+     *            rows.
+     * @return a {@link PoolablePreparedStatement}
+     * @throws SQLException
+     *             Wraps an underlying exception.
+     *
+     */
+    @Override
+    public PreparedStatement prepareStatement(final String sql, final int[] columnIndexes) throws SQLException {
+        return prepareStatement(createKey(sql, columnIndexes));
+    }
+
+    /**
+     * Creates or obtains a {@link PreparedStatement} from the pool.
+     *
+     * @param sql
+     *            the SQL string used to define the PreparedStatement
      * @param columnNames
      *            column names
      * @return a {@link PoolablePreparedStatement}
diff --git a/java/org/apache/tomcat/dbcp/dbcp2/PoolingDataSource.java b/java/org/apache/tomcat/dbcp/dbcp2/PoolingDataSource.java
index 5f0ca20..f11e1df 100644
--- a/java/org/apache/tomcat/dbcp/dbcp2/PoolingDataSource.java
+++ b/java/org/apache/tomcat/dbcp/dbcp2/PoolingDataSource.java
@@ -41,11 +41,57 @@ import org.apache.tomcat.dbcp.pool2.impl.GenericObjectPool;
  */
 public class PoolingDataSource<C extends Connection> implements DataSource, AutoCloseable {
 
+    /**
+     * PoolGuardConnectionWrapper is a Connection wrapper that makes sure a closed connection cannot be used anymore.
+     *
+     * @since 2.0
+     */
+    private class PoolGuardConnectionWrapper<D extends Connection> extends DelegatingConnection<D> {
+
+        PoolGuardConnectionWrapper(final D delegate) {
+            super(delegate);
+        }
+
+        @Override
+        public void close() throws SQLException {
+            if (getDelegateInternal() != null) {
+                super.close();
+                super.setDelegate(null);
+            }
+        }
+
+        /**
+         * @see org.apache.tomcat.dbcp.dbcp2.DelegatingConnection#getDelegate()
+         */
+        @Override
+        public D getDelegate() {
+            return isAccessToUnderlyingConnectionAllowed() ? super.getDelegate() : null;
+        }
+
+        /**
+         * @see org.apache.tomcat.dbcp.dbcp2.DelegatingConnection#getInnermostDelegate()
+         */
+        @Override
+        public Connection getInnermostDelegate() {
+            return isAccessToUnderlyingConnectionAllowed() ? super.getInnermostDelegate() : null;
+        }
+
+        @Override
+        public boolean isClosed() throws SQLException {
+            return getDelegateInternal() == null || super.isClosed();
+        }
+    }
+
     private static final Log log = LogFactory.getLog(PoolingDataSource.class);
 
     /** Controls access to the underlying connection */
     private boolean accessToUnderlyingConnectionAllowed;
 
+    /** My log writer. */
+    private PrintWriter logWriter;
+
+    private final ObjectPool<C> pool;
+
     /**
      * Constructs a new instance backed by the given connection pool.
      *
@@ -86,45 +132,6 @@ public class PoolingDataSource<C extends Connection> implements DataSource, Auto
     }
 
     /**
-     * Returns the value of the accessToUnderlyingConnectionAllowed property.
-     *
-     * @return true if access to the underlying {@link Connection} is allowed, false otherwise.
-     */
-    public boolean isAccessToUnderlyingConnectionAllowed() {
-        return this.accessToUnderlyingConnectionAllowed;
-    }
-
-    /**
-     * Sets the value of the accessToUnderlyingConnectionAllowed property. It controls if the PoolGuard allows access to
-     * the underlying connection. (Default: false)
-     *
-     * @param allow
-     *            Access to the underlying connection is granted when true.
-     */
-    public void setAccessToUnderlyingConnectionAllowed(final boolean allow) {
-        this.accessToUnderlyingConnectionAllowed = allow;
-    }
-
-    /* JDBC_4_ANT_KEY_BEGIN */
-    @Override
-    public boolean isWrapperFor(final Class<?> iface) throws SQLException {
-        return false;
-    }
-
-    @Override
-    public <T> T unwrap(final Class<T> iface) throws SQLException {
-        throw new SQLException("PoolingDataSource is not a wrapper.");
-    }
-    /* JDBC_4_ANT_KEY_END */
-
-    @Override
-    public Logger getParentLogger() throws SQLFeatureNotSupportedException {
-        throw new SQLFeatureNotSupportedException();
-    }
-
-    // --- DataSource methods -----------------------------------------
-
-    /**
      * Returns a {@link java.sql.Connection} from my pool, according to the contract specified by
      * {@link ObjectPool#borrowObject}.
      */
@@ -160,6 +167,19 @@ public class PoolingDataSource<C extends Connection> implements DataSource, Auto
         throw new UnsupportedOperationException();
     }
 
+    // --- DataSource methods -----------------------------------------
+
+    /**
+     * Throws {@link UnsupportedOperationException}.
+     *
+     * @throws UnsupportedOperationException
+     *             As this implementation does not support this feature.
+     */
+    @Override
+    public int getLoginTimeout() {
+        throw new UnsupportedOperationException("Login timeout is not supported.");
+    }
+
     /**
      * Returns my log writer.
      *
@@ -171,15 +191,38 @@ public class PoolingDataSource<C extends Connection> implements DataSource, Auto
         return logWriter;
     }
 
+    @Override
+    public Logger getParentLogger() throws SQLFeatureNotSupportedException {
+        throw new SQLFeatureNotSupportedException();
+    }
+
+    protected ObjectPool<C> getPool() {
+        return pool;
+    }
+
     /**
-     * Throws {@link UnsupportedOperationException}.
+     * Returns the value of the accessToUnderlyingConnectionAllowed property.
      *
-     * @throws UnsupportedOperationException
-     *             As this implementation does not support this feature.
+     * @return true if access to the underlying {@link Connection} is allowed, false otherwise.
      */
+    public boolean isAccessToUnderlyingConnectionAllowed() {
+        return this.accessToUnderlyingConnectionAllowed;
+    }
+
     @Override
-    public int getLoginTimeout() {
-        throw new UnsupportedOperationException("Login timeout is not supported.");
+    public boolean isWrapperFor(final Class<?> iface) throws SQLException {
+        return iface != null && iface.isInstance(this);
+    }
+
+    /**
+     * Sets the value of the accessToUnderlyingConnectionAllowed property. It controls if the PoolGuard allows access to
+     * the underlying connection. (Default: false)
+     *
+     * @param allow
+     *            Access to the underlying connection is granted when true.
+     */
+    public void setAccessToUnderlyingConnectionAllowed(final boolean allow) {
+        this.accessToUnderlyingConnectionAllowed = allow;
     }
 
     /**
@@ -203,53 +246,11 @@ public class PoolingDataSource<C extends Connection> implements DataSource, Auto
         logWriter = out;
     }
 
-    /** My log writer. */
-    private PrintWriter logWriter = null;
-
-    private final ObjectPool<C> pool;
-
-    protected ObjectPool<C> getPool() {
-        return pool;
-    }
-
-    /**
-     * PoolGuardConnectionWrapper is a Connection wrapper that makes sure a closed connection cannot be used anymore.
-     *
-     * @since 2.0
-     */
-    private class PoolGuardConnectionWrapper<D extends Connection> extends DelegatingConnection<D> {
-
-        PoolGuardConnectionWrapper(final D delegate) {
-            super(delegate);
-        }
-
-        /**
-         * @see org.apache.tomcat.dbcp.dbcp2.DelegatingConnection#getDelegate()
-         */
-        @Override
-        public D getDelegate() {
-            return isAccessToUnderlyingConnectionAllowed() ? super.getDelegate() : null;
-        }
-
-        /**
-         * @see org.apache.tomcat.dbcp.dbcp2.DelegatingConnection#getInnermostDelegate()
-         */
-        @Override
-        public Connection getInnermostDelegate() {
-            return isAccessToUnderlyingConnectionAllowed() ? super.getInnermostDelegate() : null;
-        }
-
-        @Override
-        public void close() throws SQLException {
-            if (getDelegateInternal() != null) {
-                super.close();
-                super.setDelegate(null);
-            }
-        }
-
-        @Override
-        public boolean isClosed() throws SQLException {
-            return getDelegateInternal() == null ? true : super.isClosed();
+    @Override
+    public <T> T unwrap(final Class<T> iface) throws SQLException {
+        if (isWrapperFor(iface)) {
+            return iface.cast(this);
         }
+        throw new SQLException(this + " is not a wrapper for " + iface);
     }
 }
diff --git a/java/org/apache/tomcat/dbcp/dbcp2/PoolingDriver.java b/java/org/apache/tomcat/dbcp/dbcp2/PoolingDriver.java
index 67b4c4d..5f793f8 100644
--- a/java/org/apache/tomcat/dbcp/dbcp2/PoolingDriver.java
+++ b/java/org/apache/tomcat/dbcp/dbcp2/PoolingDriver.java
@@ -36,7 +36,44 @@ import org.apache.tomcat.dbcp.pool2.ObjectPool;
  */
 public class PoolingDriver implements Driver {
 
-    private static final DriverPropertyInfo[] EMPTY_DRIVER_PROPERTY_INFO_ARRAY = new DriverPropertyInfo[0];
+    /**
+     * PoolGuardConnectionWrapper is a Connection wrapper that makes sure a closed connection cannot be used anymore.
+     *
+     * @since 2.0
+     */
+    private class PoolGuardConnectionWrapper extends DelegatingConnection<Connection> {
+
+        private final ObjectPool<? extends Connection> pool;
+
+        PoolGuardConnectionWrapper(final ObjectPool<? extends Connection> pool, final Connection delegate) {
+            super(delegate);
+            this.pool = pool;
+        }
+
+        /**
+         * @see org.apache.tomcat.dbcp.dbcp2.DelegatingConnection#getDelegate()
+         */
+        @Override
+        public Connection getDelegate() {
+            if (isAccessToUnderlyingConnectionAllowed()) {
+                return super.getDelegate();
+            }
+            return null;
+        }
+
+        /**
+         * @see org.apache.tomcat.dbcp.dbcp2.DelegatingConnection#getInnermostDelegate()
+         */
+        @Override
+        public Connection getInnermostDelegate() {
+            if (isAccessToUnderlyingConnectionAllowed()) {
+                return super.getInnermostDelegate();
+            }
+            return null;
+        }
+    }
+
+    private static final DriverPropertyInfo[] EMPTY_DRIVER_PROPERTY_INFO_ARRAY = {};
 
     /* Register myself with the {@link DriverManager}. */
     static {
@@ -50,6 +87,16 @@ public class PoolingDriver implements Driver {
     /** The map of registered pools. */
     protected static final HashMap<String, ObjectPool<? extends Connection>> pools = new HashMap<>();
 
+    /** My URL prefix */
+    public static final String URL_PREFIX = "jdbc:apache:commons:dbcp:";
+
+    protected static final int URL_PREFIX_LEN = URL_PREFIX.length();
+
+    // version numbers
+    protected static final int MAJOR_VERSION = 1;
+
+    protected static final int MINOR_VERSION = 0;
+
     /** Controls access to the underlying connection */
     private final boolean accessToUnderlyingConnectionAllowed;
 
@@ -70,42 +117,9 @@ public class PoolingDriver implements Driver {
         this.accessToUnderlyingConnectionAllowed = accessToUnderlyingConnectionAllowed;
     }
 
-    /**
-     * Returns the value of the accessToUnderlyingConnectionAllowed property.
-     *
-     * @return true if access to the underlying is allowed, false otherwise.
-     */
-    protected boolean isAccessToUnderlyingConnectionAllowed() {
-        return accessToUnderlyingConnectionAllowed;
-    }
-
-    /**
-     * Gets the connection pool for the given name.
-     *
-     * @param name
-     *            The pool name
-     * @return The pool
-     * @throws SQLException
-     *             Thrown when the named pool is not registered.
-     */
-    public synchronized ObjectPool<? extends Connection> getConnectionPool(final String name) throws SQLException {
-        final ObjectPool<? extends Connection> pool = pools.get(name);
-        if (null == pool) {
-            throw new SQLException("Pool not registered: " + name);
-        }
-        return pool;
-    }
-
-    /**
-     * Registers a named pool.
-     *
-     * @param name
-     *            The pool name.
-     * @param pool
-     *            The pool.
-     */
-    public synchronized void registerPool(final String name, final ObjectPool<? extends Connection> pool) {
-        pools.put(name, pool);
+    @Override
+    public boolean acceptsURL(final String url) throws SQLException {
+        return url != null && url.startsWith(URL_PREFIX);
     }
 
     /**
@@ -128,20 +142,6 @@ public class PoolingDriver implements Driver {
         }
     }
 
-    /**
-     * Gets the pool names.
-     *
-     * @return the pool names.
-     */
-    public synchronized String[] getPoolNames() {
-        return pools.keySet().toArray(Utils.EMPTY_STRING_ARRAY);
-    }
-
-    @Override
-    public boolean acceptsURL(final String url) throws SQLException {
-        return url != null && url.startsWith(URL_PREFIX);
-    }
-
     @Override
     public Connection connect(final String url, final Properties info) throws SQLException {
         if (acceptsURL(url)) {
@@ -164,33 +164,21 @@ public class PoolingDriver implements Driver {
         return null;
     }
 
-    @Override
-    public Logger getParentLogger() throws SQLFeatureNotSupportedException {
-        throw new SQLFeatureNotSupportedException();
-    }
-
     /**
-     * Invalidates the given connection.
+     * Gets the connection pool for the given name.
      *
-     * @param conn
-     *            connection to invalidate
+     * @param name
+     *            The pool name
+     * @return The pool
      * @throws SQLException
-     *             if the connection is not a <code>PoolGuardConnectionWrapper</code> or an error occurs invalidating
-     *             the connection
+     *             Thrown when the named pool is not registered.
      */
-    public void invalidateConnection(final Connection conn) throws SQLException {
-        if (conn instanceof PoolGuardConnectionWrapper) { // normal case
-            final PoolGuardConnectionWrapper pgconn = (PoolGuardConnectionWrapper) conn;
-            @SuppressWarnings("unchecked")
-            final ObjectPool<Connection> pool = (ObjectPool<Connection>) pgconn.pool;
-            try {
-                pool.invalidateObject(pgconn.getDelegateInternal());
-            } catch (final Exception e) {
-                // Ignore.
-            }
-        } else {
-            throw new SQLException("Invalid connection class");
+    public synchronized ObjectPool<? extends Connection> getConnectionPool(final String name) throws SQLException {
+        final ObjectPool<? extends Connection> pool = pools.get(name);
+        if (null == pool) {
+            throw new SQLException("Pool not registered: " + name);
         }
+        return pool;
     }
 
     @Override
@@ -204,57 +192,68 @@ public class PoolingDriver implements Driver {
     }
 
     @Override
-    public boolean jdbcCompliant() {
-        return true;
+    public Logger getParentLogger() throws SQLFeatureNotSupportedException {
+        throw new SQLFeatureNotSupportedException();
+    }
+
+    /**
+     * Gets the pool names.
+     *
+     * @return the pool names.
+     */
+    public synchronized String[] getPoolNames() {
+        return pools.keySet().toArray(Utils.EMPTY_STRING_ARRAY);
     }
 
     @Override
     public DriverPropertyInfo[] getPropertyInfo(final String url, final Properties info) {
         return EMPTY_DRIVER_PROPERTY_INFO_ARRAY;
     }
-
-    /** My URL prefix */
-    public static final String URL_PREFIX = "jdbc:apache:commons:dbcp:";
-    protected static final int URL_PREFIX_LEN = URL_PREFIX.length();
-
-    // version numbers
-    protected static final int MAJOR_VERSION = 1;
-    protected static final int MINOR_VERSION = 0;
-
     /**
-     * PoolGuardConnectionWrapper is a Connection wrapper that makes sure a closed connection cannot be used anymore.
+     * Invalidates the given connection.
      *
-     * @since 2.0
+     * @param conn
+     *            connection to invalidate
+     * @throws SQLException
+     *             if the connection is not a <code>PoolGuardConnectionWrapper</code> or an error occurs invalidating
+     *             the connection
      */
-    private class PoolGuardConnectionWrapper extends DelegatingConnection<Connection> {
-
-        private final ObjectPool<? extends Connection> pool;
-
-        PoolGuardConnectionWrapper(final ObjectPool<? extends Connection> pool, final Connection delegate) {
-            super(delegate);
-            this.pool = pool;
+    public void invalidateConnection(final Connection conn) throws SQLException {
+        if (!(conn instanceof PoolGuardConnectionWrapper)) {
+            throw new SQLException("Invalid connection class");
         }
-
-        /**
-         * @see org.apache.tomcat.dbcp.dbcp2.DelegatingConnection#getDelegate()
-         */
-        @Override
-        public Connection getDelegate() {
-            if (isAccessToUnderlyingConnectionAllowed()) {
-                return super.getDelegate();
-            }
-            return null;
+        final PoolGuardConnectionWrapper pgconn = (PoolGuardConnectionWrapper) conn;
+        @SuppressWarnings("unchecked")
+        final ObjectPool<Connection> pool = (ObjectPool<Connection>) pgconn.pool;
+        try {
+            pool.invalidateObject(pgconn.getDelegateInternal());
+        } catch (final Exception e) {
+            // Ignore.
         }
+    }
 
-        /**
-         * @see org.apache.tomcat.dbcp.dbcp2.DelegatingConnection#getInnermostDelegate()
-         */
-        @Override
-        public Connection getInnermostDelegate() {
-            if (isAccessToUnderlyingConnectionAllowed()) {
-                return super.getInnermostDelegate();
-            }
-            return null;
-        }
+    /**
+     * Returns the value of the accessToUnderlyingConnectionAllowed property.
+     *
+     * @return true if access to the underlying is allowed, false otherwise.
+     */
+    protected boolean isAccessToUnderlyingConnectionAllowed() {
+        return accessToUnderlyingConnectionAllowed;
+    }
+    @Override
+    public boolean jdbcCompliant() {
+        return true;
+    }
+
+    /**
+     * Registers a named pool.
+     *
+     * @param name
+     *            The pool name.
+     * @param pool
+     *            The pool.
+     */
+    public synchronized void registerPool(final String name, final ObjectPool<? extends Connection> pool) {
+        pools.put(name, pool);
     }
 }
diff --git a/java/org/apache/tomcat/dbcp/dbcp2/Utils.java b/java/org/apache/tomcat/dbcp/dbcp2/Utils.java
index c0ebadb..d4c4aa7 100644
--- a/java/org/apache/tomcat/dbcp/dbcp2/Utils.java
+++ b/java/org/apache/tomcat/dbcp/dbcp2/Utils.java
@@ -17,7 +17,9 @@
  */
 package org.apache.tomcat.dbcp.dbcp2;
 
+import java.sql.Connection;
 import java.sql.ResultSet;
+import java.sql.Statement;
 import java.text.MessageFormat;
 import java.util.HashSet;
 import java.util.Properties;
@@ -36,8 +38,11 @@ public final class Utils {
 
     /**
      * Whether the security manager is enabled.
+     *
+     * @deprecated No replacement.
      */
-    public static final boolean IS_SECURITY_ENABLED = System.getSecurityManager() != null;
+    @Deprecated
+    public static final boolean IS_SECURITY_ENABLED = isSecurityEnabled();
 
     /** Any SQL_STATE starting with this value is considered a fatal disconnect */
     public static final String DISCONNECTION_SQL_CODE_PREFIX = "08";
@@ -55,9 +60,9 @@ public final class Utils {
      */
     public static final Set<String> DISCONNECTION_SQL_CODES;
 
-    static final ResultSet[] EMPTY_RESULT_SET_ARRAY = new ResultSet[0];
-    static final String[] EMPTY_STRING_ARRAY = new String[0];
+    static final ResultSet[] EMPTY_RESULT_SET_ARRAY = {};
 
+    static final String[] EMPTY_STRING_ARRAY = {};
     static {
         DISCONNECTION_SQL_CODES = new HashSet<>();
         DISCONNECTION_SQL_CODES.add("57P01"); // Admin shutdown
@@ -88,8 +93,8 @@ public final class Utils {
     public static Properties cloneWithoutCredentials(final Properties properties) {
         if (properties != null) {
             final Properties temp = (Properties) properties.clone();
-            temp.remove("user");
-            temp.remove("password");
+            temp.remove(Constants.KEY_USER);
+            temp.remove(Constants.KEY_PASSWORD);
             return temp;
         }
         return properties;
@@ -112,6 +117,39 @@ public final class Utils {
     }
 
     /**
+     * Closes the Connection (which may be null).
+     *
+     * @param connection a Connection, may be {@code null}
+     * @deprecated Use {@link #closeQuietly(AutoCloseable)}.
+     */
+    @Deprecated
+    public static void closeQuietly(final Connection connection) {
+        closeQuietly((AutoCloseable) connection);
+    }
+
+    /**
+     * Closes the ResultSet (which may be null).
+     *
+     * @param resultSet a ResultSet, may be {@code null}
+     * @deprecated Use {@link #closeQuietly(AutoCloseable)}.
+     */
+    @Deprecated
+    public static void closeQuietly(final ResultSet resultSet) {
+        closeQuietly((AutoCloseable) resultSet);
+    }
+
+    /**
+     * Closes the Statement (which may be null).
+     *
+     * @param statement a Statement, may be {@code null}.
+     * @deprecated Use {@link #closeQuietly(AutoCloseable)}.
+     */
+    @Deprecated
+    public static void closeQuietly(final Statement statement) {
+        closeQuietly((AutoCloseable) statement);
+    }
+
+    /**
      * Gets the correct i18n message for the given key.
      *
      * @param key The key to look up an i18n message.
@@ -137,6 +175,10 @@ public final class Utils {
         return mf.format(args, new StringBuffer(), null).toString();
     }
 
+    static boolean isSecurityEnabled() {
+        return System.getSecurityManager() != null;
+    }
+
     /**
      * Converts the given String to a char[].
      *
diff --git a/java/org/apache/tomcat/dbcp/dbcp2/cpdsadapter/ConnectionImpl.java b/java/org/apache/tomcat/dbcp/dbcp2/cpdsadapter/ConnectionImpl.java
index 7cde269..9c820a3 100644
--- a/java/org/apache/tomcat/dbcp/dbcp2/cpdsadapter/ConnectionImpl.java
+++ b/java/org/apache/tomcat/dbcp/dbcp2/cpdsadapter/ConnectionImpl.java
@@ -84,6 +84,44 @@ class ConnectionImpl extends DelegatingConnection<Connection> {
     }
 
     /**
+     * Get the delegated connection, if allowed.
+     *
+     * @return the internal connection, or null if access is not allowed.
+     * @see #isAccessToUnderlyingConnectionAllowed()
+     */
+    @Override
+    public Connection getDelegate() {
+        if (isAccessToUnderlyingConnectionAllowed()) {
+            return getDelegateInternal();
+        }
+        return null;
+    }
+
+    /**
+     * Get the innermost connection, if allowed.
+     *
+     * @return the innermost internal connection, or null if access is not allowed.
+     * @see #isAccessToUnderlyingConnectionAllowed()
+     */
+    @Override
+    public Connection getInnermostDelegate() {
+        if (isAccessToUnderlyingConnectionAllowed()) {
+            return super.getInnermostDelegateInternal();
+        }
+        return null;
+    }
+
+    /**
+     * If false, getDelegate() and getInnermostDelegate() will return null.
+     *
+     * @return true if access is allowed to the underlying connection
+     * @see ConnectionImpl
+     */
+    public boolean isAccessToUnderlyingConnectionAllowed() {
+        return accessToUnderlyingConnectionAllowed;
+    }
+
+    /**
      * If pooling of <code>CallableStatement</code>s is turned on in the {@link DriverAdapterCPDS}, a pooled object may
      * be returned, otherwise delegate to the wrapped JDBC 1.x {@link java.sql.Connection}.
      *
@@ -196,6 +234,17 @@ class ConnectionImpl extends DelegatingConnection<Connection> {
         }
     }
 
+    @Override
+    public PreparedStatement prepareStatement(final String sql, final int autoGeneratedKeys) throws SQLException {
+        checkOpen();
+        try {
+            return new DelegatingPreparedStatement(this, pooledConnection.prepareStatement(sql, autoGeneratedKeys));
+        } catch (final SQLException e) {
+            handleException(e);
+            return null;
+        }
+    }
+
     /**
      * If pooling of <code>PreparedStatement</code>s is turned on in the {@link DriverAdapterCPDS}, a pooled object may
      * be returned, otherwise delegate to the wrapped JDBC 1.x {@link java.sql.Connection}.
@@ -216,6 +265,10 @@ class ConnectionImpl extends DelegatingConnection<Connection> {
         }
     }
 
+    //
+    // Methods for accessing the delegate connection
+    //
+
     @Override
     public PreparedStatement prepareStatement(final String sql, final int resultSetType, final int resultSetConcurrency,
             final int resultSetHoldability) throws SQLException {
@@ -230,17 +283,6 @@ class ConnectionImpl extends DelegatingConnection<Connection> {
     }
 
     @Override
-    public PreparedStatement prepareStatement(final String sql, final int autoGeneratedKeys) throws SQLException {
-        checkOpen();
-        try {
-            return new DelegatingPreparedStatement(this, pooledConnection.prepareStatement(sql, autoGeneratedKeys));
-        } catch (final SQLException e) {
-            handleException(e);
-            return null;
-        }
-    }
-
-    @Override
     public PreparedStatement prepareStatement(final String sql, final int[] columnIndexes) throws SQLException {
         checkOpen();
         try {
@@ -262,46 +304,4 @@ class ConnectionImpl extends DelegatingConnection<Connection> {
         }
     }
 
-    //
-    // Methods for accessing the delegate connection
-    //
-
-    /**
-     * If false, getDelegate() and getInnermostDelegate() will return null.
-     *
-     * @return true if access is allowed to the underlying connection
-     * @see ConnectionImpl
-     */
-    public boolean isAccessToUnderlyingConnectionAllowed() {
-        return accessToUnderlyingConnectionAllowed;
-    }
-
-    /**
-     * Get the delegated connection, if allowed.
-     *
-     * @return the internal connection, or null if access is not allowed.
-     * @see #isAccessToUnderlyingConnectionAllowed()
-     */
-    @Override
-    public Connection getDelegate() {
-        if (isAccessToUnderlyingConnectionAllowed()) {
-            return getDelegateInternal();
-        }
-        return null;
-    }
-
-    /**
-     * Get the innermost connection, if allowed.
-     *
-     * @return the innermost internal connection, or null if access is not allowed.
-     * @see #isAccessToUnderlyingConnectionAllowed()
-     */
-    @Override
-    public Connection getInnermostDelegate() {
-        if (isAccessToUnderlyingConnectionAllowed()) {
-            return super.getInnermostDelegateInternal();
-        }
-        return null;
-    }
-
 }
diff --git a/java/org/apache/tomcat/dbcp/dbcp2/cpdsadapter/DriverAdapterCPDS.java b/java/org/apache/tomcat/dbcp/dbcp2/cpdsadapter/DriverAdapterCPDS.java
index 399114f..ff50366 100644
--- a/java/org/apache/tomcat/dbcp/dbcp2/cpdsadapter/DriverAdapterCPDS.java
+++ b/java/org/apache/tomcat/dbcp/dbcp2/cpdsadapter/DriverAdapterCPDS.java
@@ -21,6 +21,7 @@ import java.io.Serializable;
 import java.sql.DriverManager;
 import java.sql.SQLException;
 import java.sql.SQLFeatureNotSupportedException;
+import java.time.Duration;
 import java.util.Hashtable;
 import java.util.Properties;
 import java.util.logging.Logger;
@@ -37,6 +38,7 @@ import javax.sql.ConnectionPoolDataSource;
 import javax.sql.PooledConnection;
 
 import org.apache.tomcat.dbcp.dbcp2.BasicDataSource;
+import org.apache.tomcat.dbcp.dbcp2.Constants;
 import org.apache.tomcat.dbcp.dbcp2.DelegatingPreparedStatement;
 import org.apache.tomcat.dbcp.dbcp2.PStmtKey;
 import org.apache.tomcat.dbcp.dbcp2.Utils;
@@ -77,10 +79,6 @@ import org.apache.tomcat.dbcp.pool2.impl.GenericKeyedObjectPoolConfig;
  */
 public class DriverAdapterCPDS implements ConnectionPoolDataSource, Referenceable, Serializable, ObjectFactory {
 
-    private static final String KEY_USER = "user";
-
-    private static final String KEY_PASSWORD = "password";
-
     private static final long serialVersionUID = -4820523787212147844L;
 
     private static final String GET_CONNECTION_CALLED = "A PooledConnection was already requested from this source, "
@@ -111,12 +109,13 @@ public class DriverAdapterCPDS implements ConnectionPoolDataSource, Referenceabl
 
     /** Log stream. NOT USED */
     private transient PrintWriter logWriter;
+
     // PreparedStatement pool properties
     private boolean poolPreparedStatements;
     private int maxIdle = 10;
-    private long timeBetweenEvictionRunsMillis = BaseObjectPoolConfig.DEFAULT_TIME_BETWEEN_EVICTION_RUNS_MILLIS;
+    private Duration durationBetweenEvictionRuns = BaseObjectPoolConfig.DEFAULT_TIME_BETWEEN_EVICTION_RUNS;
     private int numTestsPerEvictionRun = -1;
-    private int minEvictableIdleTimeMillis = -1;
+    private Duration minEvictableIdleDuration = BaseObjectPoolConfig.DEFAULT_MIN_EVICTABLE_IDLE_DURATION;
 
     private int maxPreparedStatements = -1;
 
@@ -132,7 +131,7 @@ public class DriverAdapterCPDS implements ConnectionPoolDataSource, Referenceabl
     private boolean accessToUnderlyingConnectionAllowed;
 
     /**
-     * Default no-arg constructor for Serialization
+     * Default no-argument constructor for Serialization
      */
     public DriverAdapterCPDS() {
     }
@@ -160,8 +159,8 @@ public class DriverAdapterCPDS implements ConnectionPoolDataSource, Referenceabl
     }
 
     /**
-     * Gets the value of description. This property is here for use by the code which will deploy this datasource. It is
-     * not used internally.
+     * Gets the value of description. This property is here for use by the code which will deploy this data source. It
+     * is not used internally.
      *
      * @return value of description, may be null.
      * @see #setDescription(String)
@@ -179,6 +178,18 @@ public class DriverAdapterCPDS implements ConnectionPoolDataSource, Referenceabl
         return driver;
     }
 
+    /**
+     * Gets the duration to sleep between runs of the idle object evictor thread. When non-positive, no
+     * idle object evictor thread will be run.
+     *
+     * @return the value of the evictor thread timer
+     * @see #setDurationBetweenEvictionRuns(Duration)
+     * @since 2.9.0
+     */
+    public Duration getDurationBetweenEvictionRuns() {
+        return durationBetweenEvictionRuns;
+    }
+
     private int getIntegerStringContent(final RefAddr ra) {
         return Integer.parseInt(getStringContent(ra));
     }
@@ -207,7 +218,7 @@ public class DriverAdapterCPDS implements ConnectionPoolDataSource, Referenceabl
      * @return the value of maxIdle
      */
     public int getMaxIdle() {
-        return this.maxIdle;
+        return maxIdle;
     }
 
     /**
@@ -223,12 +234,27 @@ public class DriverAdapterCPDS implements ConnectionPoolDataSource, Referenceabl
      * Gets the minimum amount of time a statement may sit idle in the pool before it is eligible for eviction by the
      * idle object evictor (if any).
      *
+     * @see #setMinEvictableIdleDuration
+     * @see #setDurationBetweenEvictionRuns
+     * @return the minimum amount of time a statement may sit idle in the pool.
+     * @since 2.9.0
+     */
+    public Duration getMinEvictableIdleDuration() {
+        return minEvictableIdleDuration;
+    }
+
+    /**
+     * Gets the minimum amount of time a statement may sit idle in the pool before it is eligible for eviction by the
+     * idle object evictor (if any).
+     *
      * @see #setMinEvictableIdleTimeMillis
      * @see #setTimeBetweenEvictionRunsMillis
      * @return the minimum amount of time a statement may sit idle in the pool.
+     * @deprecated USe {@link #getMinEvictableIdleDuration()}.
      */
+    @Deprecated
     public int getMinEvictableIdleTimeMillis() {
-        return minEvictableIdleTimeMillis;
+        return (int) minEvictableIdleDuration.toMillis();
     }
 
     /**
@@ -267,11 +293,11 @@ public class DriverAdapterCPDS implements ConnectionPoolDataSource, Referenceabl
                 if (isNotEmpty(ra)) {
                     setUrl(getStringContent(ra));
                 }
-                ra = ref.get(KEY_USER);
+                ra = ref.get(Constants.KEY_USER);
                 if (isNotEmpty(ra)) {
                     setUser(getStringContent(ra));
                 }
-                ra = ref.get(KEY_PASSWORD);
+                ra = ref.get(Constants.KEY_PASSWORD);
                 if (isNotEmpty(ra)) {
                     setPassword(getStringContent(ra));
                 }
@@ -299,6 +325,7 @@ public class DriverAdapterCPDS implements ConnectionPoolDataSource, Referenceabl
                 if (isNotEmpty(ra)) {
                     setMinEvictableIdleTimeMillis(getIntegerStringContent(ra));
                 }
+
                 ra = ref.get("maxPreparedStatements");
                 if (isNotEmpty(ra)) {
                     setMaxPreparedStatements(getIntegerStringContent(ra));
@@ -336,7 +363,7 @@ public class DriverAdapterCPDS implements ConnectionPoolDataSource, Referenceabl
      * @since 2.4.0
      */
     public char[] getPasswordCharArray() {
-        return userPassword;
+        return userPassword == null ? null : userPassword.clone();
     }
 
     /**
@@ -358,11 +385,11 @@ public class DriverAdapterCPDS implements ConnectionPoolDataSource, Referenceabl
         throws SQLException {
         getConnectionCalled = true;
         PooledConnectionImpl pooledConnection = null;
-        // Workaround for buggy WebLogic 5.1 classloader - ignore the exception upon first invocation.
+        // Workaround for buggy WebLogic 5.1 class loader - ignore the exception upon first invocation.
         try {
             if (connectionProperties != null) {
-                update(connectionProperties, KEY_USER, pooledUserName);
-                update(connectionProperties, KEY_PASSWORD, pooledUserPassword);
+                update(connectionProperties, Constants.KEY_USER, pooledUserName);
+                update(connectionProperties, Constants.KEY_PASSWORD, pooledUserPassword);
                 pooledConnection = new PooledConnectionImpl(
                     DriverManager.getConnection(getUrl(), connectionProperties));
             } else {
@@ -385,22 +412,22 @@ public class DriverAdapterCPDS implements ConnectionPoolDataSource, Referenceabl
             final GenericKeyedObjectPoolConfig<DelegatingPreparedStatement> config = new GenericKeyedObjectPoolConfig<>();
             config.setMaxTotalPerKey(Integer.MAX_VALUE);
             config.setBlockWhenExhausted(false);
-            config.setMaxWaitMillis(0);
+            config.setMaxWait(Duration.ZERO);
             config.setMaxIdlePerKey(getMaxIdle());
             if (getMaxPreparedStatements() <= 0) {
-                // since there is no limit, create a prepared statement pool with an eviction thread;
+                // Since there is no limit, create a prepared statement pool with an eviction thread;
                 // evictor settings are the same as the connection pool settings.
-                config.setTimeBetweenEvictionRunsMillis(getTimeBetweenEvictionRunsMillis());
+                config.setTimeBetweenEvictionRuns(getDurationBetweenEvictionRuns());
                 config.setNumTestsPerEvictionRun(getNumTestsPerEvictionRun());
-                config.setMinEvictableIdleTimeMillis(getMinEvictableIdleTimeMillis());
+                config.setMinEvictableIdleTime(getMinEvictableIdleDuration());
             } else {
-                // since there is a limit, create a prepared statement pool without an eviction thread;
+                // Since there is a limit, create a prepared statement pool without an eviction thread;
                 // pool has LRU functionality so when the limit is reached, 15% of the pool is cleared.
                 // see org.apache.commons.pool2.impl.GenericKeyedObjectPool.clearOldest method
                 config.setMaxTotal(getMaxPreparedStatements());
-                config.setTimeBetweenEvictionRunsMillis(-1);
+                config.setTimeBetweenEvictionRuns(Duration.ofMillis(-1));
                 config.setNumTestsPerEvictionRun(0);
-                config.setMinEvictableIdleTimeMillis(0);
+                config.setMinEvictableIdleTime(Duration.ZERO);
             }
             stmtPool = new GenericKeyedObjectPool<>(pooledConnection, config);
             pooledConnection.setStatementPool(stmtPool);
@@ -421,16 +448,22 @@ public class DriverAdapterCPDS implements ConnectionPoolDataSource, Referenceabl
         ref.add(new StringRefAddr("description", getDescription()));
         ref.add(new StringRefAddr("driver", getDriver()));
         ref.add(new StringRefAddr("loginTimeout", String.valueOf(getLoginTimeout())));
-        ref.add(new StringRefAddr(KEY_PASSWORD, getPassword()));
-        ref.add(new StringRefAddr(KEY_USER, getUser()));
+        ref.add(new StringRefAddr(Constants.KEY_PASSWORD, getPassword()));
+        ref.add(new StringRefAddr(Constants.KEY_USER, getUser()));
         ref.add(new StringRefAddr("url", getUrl()));
 
         ref.add(new StringRefAddr("poolPreparedStatements", String.valueOf(isPoolPreparedStatements())));
         ref.add(new StringRefAddr("maxIdle", String.valueOf(getMaxIdle())));
-        ref.add(new StringRefAddr("timeBetweenEvictionRunsMillis", String.valueOf(getTimeBetweenEvictionRunsMillis())));
         ref.add(new StringRefAddr("numTestsPerEvictionRun", String.valueOf(getNumTestsPerEvictionRun())));
-        ref.add(new StringRefAddr("minEvictableIdleTimeMillis", String.valueOf(getMinEvictableIdleTimeMillis())));
         ref.add(new StringRefAddr("maxPreparedStatements", String.valueOf(getMaxPreparedStatements())));
+        //
+        // Pair of current and deprecated.
+        ref.add(new StringRefAddr("durationBetweenEvictionRuns", String.valueOf(getDurationBetweenEvictionRuns())));
+        ref.add(new StringRefAddr("timeBetweenEvictionRunsMillis", String.valueOf(getTimeBetweenEvictionRunsMillis())));
+        //
+        // Pair of current and deprecated.
+        ref.add(new StringRefAddr("minEvictableIdleDuration", String.valueOf(getMinEvictableIdleDuration())));
+        ref.add(new StringRefAddr("minEvictableIdleTimeMillis", String.valueOf(getMinEvictableIdleTimeMillis())));
 
         return ref;
     }
@@ -444,10 +477,12 @@ public class DriverAdapterCPDS implements ConnectionPoolDataSource, Referenceabl
      * idle object evictor thread will be run.
      *
      * @return the value of the evictor thread timer
-     * @see #setTimeBetweenEvictionRunsMillis(long)
+     * @see #setDurationBetweenEvictionRuns(Duration)
+     * @deprecated Use {@link #getDurationBetweenEvictionRuns()}.
      */
+    @Deprecated
     public long getTimeBetweenEvictionRunsMillis() {
-        return timeBetweenEvictionRunsMillis;
+        return durationBetweenEvictionRuns.toMillis();
     }
 
     /**
@@ -518,11 +553,11 @@ public class DriverAdapterCPDS implements ConnectionPoolDataSource, Referenceabl
         assertInitializationAllowed();
         connectionProperties = props;
         if (connectionProperties != null) {
-            if (connectionProperties.containsKey(KEY_USER)) {
-                setUser(connectionProperties.getProperty(KEY_USER));
+            if (connectionProperties.containsKey(Constants.KEY_USER)) {
+                setUser(connectionProperties.getProperty(Constants.KEY_USER));
             }
-            if (connectionProperties.containsKey(KEY_PASSWORD)) {
-                setPassword(connectionProperties.getProperty(KEY_PASSWORD));
+            if (connectionProperties.containsKey(Constants.KEY_PASSWORD)) {
+                setPassword(connectionProperties.getProperty(Constants.KEY_PASSWORD));
             }
         }
     }
@@ -531,25 +566,40 @@ public class DriverAdapterCPDS implements ConnectionPoolDataSource, Referenceabl
      * Sets the value of description. This property is here for use by the code which will deploy this datasource. It is
      * not used internally.
      *
-     * @param v Value to assign to description.
+     * @param description Value to assign to description.
      */
-    public void setDescription(final String v) {
-        this.description = v;
+    public void setDescription(final String description) {
+        this.description = description;
     }
 
     /**
      * Sets the driver class name. Setting the driver class name cause the driver to be registered with the
      * DriverManager.
      *
-     * @param v Value to assign to driver.
+     * @param driver Value to assign to driver.
      * @throws IllegalStateException if {@link #getPooledConnection()} has been called
      * @throws ClassNotFoundException if the class cannot be located
      */
-    public void setDriver(final String v) throws ClassNotFoundException {
+    public void setDriver(final String driver) throws ClassNotFoundException {
         assertInitializationAllowed();
-        this.driver = v;
+        this.driver = driver;
         // make sure driver is registered
-        Class.forName(v);
+        Class.forName(driver);
+    }
+
+    /**
+     * Sets the duration to sleep between runs of the idle object evictor thread. When non-positive, no
+     * idle object evictor thread will be run.
+     *
+     * @param durationBetweenEvictionRuns The duration to sleep between runs of the idle object evictor
+     *        thread. When non-positive, no idle object evictor thread will be run.
+     * @see #getDurationBetweenEvictionRuns()
+     * @throws IllegalStateException if {@link #getPooledConnection()} has been called
+     * @since 2.9.0
+     */
+    public void setDurationBetweenEvictionRuns(final Duration durationBetweenEvictionRuns) {
+        assertInitializationAllowed();
+        this.durationBetweenEvictionRuns = durationBetweenEvictionRuns;
     }
 
     /**
@@ -558,15 +608,15 @@ public class DriverAdapterCPDS implements ConnectionPoolDataSource, Referenceabl
      */
     @Override
     public void setLoginTimeout(final int seconds) {
-        loginTimeout = seconds;
+        this.loginTimeout = seconds;
     }
 
     /**
      * Sets the log writer for this data source. NOT USED.
      */
     @Override
-    public void setLogWriter(final PrintWriter out) {
-        logWriter = out;
+    public void setLogWriter(final PrintWriter logWriter) {
+        this.logWriter = logWriter;
     }
 
     /**
@@ -594,14 +644,31 @@ public class DriverAdapterCPDS implements ConnectionPoolDataSource, Referenceabl
      * Sets the minimum amount of time a statement may sit idle in the pool before it is eligible for eviction by the
      * idle object evictor (if any). When non-positive, no objects will be evicted from the pool due to idle time alone.
      *
-     * @param minEvictableIdleTimeMillis minimum time to set (in ms)
-     * @see #getMinEvictableIdleTimeMillis()
-     * @see #setTimeBetweenEvictionRunsMillis(long)
+     * @param minEvictableIdleDuration minimum time to set in milliseconds.
+     * @see #getMinEvictableIdleDuration()
+     * @see #setDurationBetweenEvictionRuns(Duration)
+     * @throws IllegalStateException if {@link #getPooledConnection()} has been called.
+     * @since 2.9.0
+     */
+    public void setMinEvictableIdleDuration(final Duration minEvictableIdleDuration) {
+        assertInitializationAllowed();
+        this.minEvictableIdleDuration = minEvictableIdleDuration;
+    }
+
+    /**
+     * Sets the minimum amount of time a statement may sit idle in the pool before it is eligible for eviction by the
+     * idle object evictor (if any). When non-positive, no objects will be evicted from the pool due to idle time alone.
+     *
+     * @param minEvictableIdleTimeMillis minimum time to set in milliseconds.
+     * @see #getMinEvictableIdleDuration()
+     * @see #setDurationBetweenEvictionRuns(Duration)
      * @throws IllegalStateException if {@link #getPooledConnection()} has been called
+     * @deprecated Use {@link #setMinEvictableIdleDuration(Duration)}.
      */
+    @Deprecated
     public void setMinEvictableIdleTimeMillis(final int minEvictableIdleTimeMillis) {
         assertInitializationAllowed();
-        this.minEvictableIdleTimeMillis = minEvictableIdleTimeMillis;
+        this.minEvictableIdleDuration = Duration.ofMillis(minEvictableIdleTimeMillis);
     }
 
     /**
@@ -614,7 +681,7 @@ public class DriverAdapterCPDS implements ConnectionPoolDataSource, Referenceabl
      *
      * @param numTestsPerEvictionRun number of statements to examine per run
      * @see #getNumTestsPerEvictionRun()
-     * @see #setTimeBetweenEvictionRunsMillis(long)
+     * @see #setDurationBetweenEvictionRuns(Duration)
      * @throws IllegalStateException if {@link #getPooledConnection()} has been called
      */
     public void setNumTestsPerEvictionRun(final int numTestsPerEvictionRun) {
@@ -631,7 +698,7 @@ public class DriverAdapterCPDS implements ConnectionPoolDataSource, Referenceabl
     public void setPassword(final char[] userPassword) {
         assertInitializationAllowed();
         this.userPassword = Utils.clone(userPassword);
-        update(connectionProperties, KEY_PASSWORD, Utils.toString(this.userPassword));
+        update(connectionProperties, Constants.KEY_PASSWORD, Utils.toString(this.userPassword));
     }
 
     /**
@@ -643,7 +710,7 @@ public class DriverAdapterCPDS implements ConnectionPoolDataSource, Referenceabl
     public void setPassword(final String userPassword) {
         assertInitializationAllowed();
         this.userPassword = Utils.toCharArray(userPassword);
-        update(connectionProperties, KEY_PASSWORD, userPassword);
+        update(connectionProperties, Constants.KEY_PASSWORD, userPassword);
     }
 
     /**
@@ -663,35 +730,37 @@ public class DriverAdapterCPDS implements ConnectionPoolDataSource, Referenceabl
      *
      * @param timeBetweenEvictionRunsMillis The number of milliseconds to sleep between runs of the idle object evictor
      *        thread. When non-positive, no idle object evictor thread will be run.
-     * @see #getTimeBetweenEvictionRunsMillis()
+     * @see #getDurationBetweenEvictionRuns()
      * @throws IllegalStateException if {@link #getPooledConnection()} has been called
+     * @deprecated Use {@link #setDurationBetweenEvictionRuns(Duration)}.
      */
+    @Deprecated
     public void setTimeBetweenEvictionRunsMillis(final long timeBetweenEvictionRunsMillis) {
         assertInitializationAllowed();
-        this.timeBetweenEvictionRunsMillis = timeBetweenEvictionRunsMillis;
+        this.durationBetweenEvictionRuns = Duration.ofMillis(timeBetweenEvictionRunsMillis);
     }
 
     /**
      * Sets the value of URL string used to locate the database for this datasource.
      *
-     * @param v Value to assign to url.
+     * @param url Value to assign to url.
      * @throws IllegalStateException if {@link #getPooledConnection()} has been called
      */
-    public void setUrl(final String v) {
+    public void setUrl(final String url) {
         assertInitializationAllowed();
-        this.url = v;
+        this.url = url;
     }
 
     /**
      * Sets the value of default user (login or user name).
      *
-     * @param v Value to assign to user.
+     * @param userName Value to assign to user.
      * @throws IllegalStateException if {@link #getPooledConnection()} has been called
      */
-    public void setUser(final String v) {
+    public void setUser(final String userName) {
         assertInitializationAllowed();
-        this.userName = v;
-        update(connectionProperties, KEY_USER, v);
+        this.userName = userName;
+        update(connectionProperties, Constants.KEY_USER, userName);
     }
 
     /**
@@ -717,11 +786,11 @@ public class DriverAdapterCPDS implements ConnectionPoolDataSource, Referenceabl
         builder.append(", maxIdle=");
         builder.append(maxIdle);
         builder.append(", timeBetweenEvictionRunsMillis=");
-        builder.append(timeBetweenEvictionRunsMillis);
+        builder.append(durationBetweenEvictionRuns);
         builder.append(", numTestsPerEvictionRun=");
         builder.append(numTestsPerEvictionRun);
         builder.append(", minEvictableIdleTimeMillis=");
-        builder.append(minEvictableIdleTimeMillis);
+        builder.append(minEvictableIdleDuration);
         builder.append(", maxPreparedStatements=");
         builder.append(maxPreparedStatements);
         builder.append(", getConnectionCalled=");
diff --git a/java/org/apache/tomcat/dbcp/dbcp2/cpdsadapter/PooledConnectionImpl.java b/java/org/apache/tomcat/dbcp/dbcp2/cpdsadapter/PooledConnectionImpl.java
index 7069bc8..60dcc75 100644
--- a/java/org/apache/tomcat/dbcp/dbcp2/cpdsadapter/PooledConnectionImpl.java
+++ b/java/org/apache/tomcat/dbcp/dbcp2/cpdsadapter/PooledConnectionImpl.java
@@ -20,7 +20,9 @@ import java.sql.CallableStatement;
 import java.sql.Connection;
 import java.sql.PreparedStatement;
 import java.sql.SQLException;
-import java.util.Vector;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
 
 import javax.sql.ConnectionEvent;
 import javax.sql.ConnectionEventListener;
@@ -67,12 +69,12 @@ class PooledConnectionImpl
     /**
      * ConnectionEventListeners.
      */
-    private final Vector<ConnectionEventListener> eventListeners;
+    private final List<ConnectionEventListener> eventListeners;
 
     /**
      * StatementEventListeners.
      */
-    private final Vector<StatementEventListener> statementEventListeners = new Vector<>();
+    private final List<StatementEventListener> statementEventListeners = Collections.synchronizedList(new ArrayList<>());
 
     /**
      * Flag set to true, once {@link #close()} is called.
@@ -100,7 +102,7 @@ class PooledConnectionImpl
         } else {
             this.delegatingConnection = new DelegatingConnection<>(connection);
         }
-        eventListeners = new Vector<>();
+        eventListeners = Collections.synchronizedList(new ArrayList<>());
         closed = false;
     }
 
@@ -125,7 +127,6 @@ class PooledConnectionImpl
         }
     }
 
-    /* JDBC_4_ANT_KEY_BEGIN */
     @Override
     public void addStatementEventListener(final StatementEventListener listener) {
         if (!statementEventListeners.contains(listener)) {
@@ -205,20 +206,6 @@ class PooledConnectionImpl
      *
      * @param sql
      *            The SQL statement.
-     * @param columnIndexes
-     *            An array of column indexes indicating the columns that should be returned from the inserted row or
-     *            rows.
-     * @return a key to uniquely identify a prepared statement.
-     */
-    protected PStmtKey createKey(final String sql, final int[] columnIndexes) {
-        return new PStmtKey(normalizeSQL(sql), getCatalogOrNull(), getSchemaOrNull(), columnIndexes);
-    }
-
-    /**
-     * Creates a {@link PStmtKey} for the given arguments.
-     *
-     * @param sql
-     *            The SQL statement.
      * @param resultSetType
      *            A result set type; one of <code>ResultSet.TYPE_FORWARD_ONLY</code>,
      *            <code>ResultSet.TYPE_SCROLL_INSENSITIVE</code>, or <code>ResultSet.TYPE_SCROLL_SENSITIVE</code>.
@@ -306,6 +293,20 @@ class PooledConnectionImpl
      *
      * @param sql
      *            The SQL statement.
+     * @param columnIndexes
+     *            An array of column indexes indicating the columns that should be returned from the inserted row or
+     *            rows.
+     * @return a key to uniquely identify a prepared statement.
+     */
+    protected PStmtKey createKey(final String sql, final int[] columnIndexes) {
+        return new PStmtKey(normalizeSQL(sql), getCatalogOrNull(), getSchemaOrNull(), columnIndexes);
+    }
+
+    /**
+     * Creates a {@link PStmtKey} for the given arguments.
+     *
+     * @param sql
+     *            The SQL statement.
      * @param statementType
      *            The SQL statement type, prepared or callable.
      * @return a key to uniquely identify a prepared statement.
@@ -368,14 +369,6 @@ class PooledConnectionImpl
         }
     }
 
-    private String getSchemaOrNull() {
-        try {
-            return connection == null ? null : Jdbc41Bridge.getSchema(connection);
-        } catch (final SQLException e) {
-            return null;
-        }
-    }
-
     /**
      * Returns a JDBC connection.
      *
@@ -398,6 +391,14 @@ class PooledConnectionImpl
         return logicalConnection;
     }
 
+    private String getSchemaOrNull() {
+        try {
+            return connection == null ? null : Jdbc41Bridge.getSchema(connection);
+        } catch (final SQLException e) {
+            return null;
+        }
+    }
+
     /**
      * Returns the value of the accessToUnderlyingConnectionAllowed property.
      *
@@ -612,19 +613,6 @@ class PooledConnectionImpl
         }
     }
 
-    PreparedStatement prepareStatement(final String sql, final int[] columnIndexes) throws SQLException {
-        if (pStmtPool == null) {
-            return connection.prepareStatement(sql, columnIndexes);
-        }
-        try {
-            return pStmtPool.borrowObject(createKey(sql, columnIndexes));
-        } catch (final RuntimeException e) {
-            throw e;
-        } catch (final Exception e) {
-            throw new SQLException("Borrow prepareStatement from pool failed", e);
-        }
-    }
-
     /**
      * Creates or obtains a {@link PreparedStatement} from my pool.
      *
@@ -671,6 +659,19 @@ class PooledConnectionImpl
         }
     }
 
+    PreparedStatement prepareStatement(final String sql, final int[] columnIndexes) throws SQLException {
+        if (pStmtPool == null) {
+            return connection.prepareStatement(sql, columnIndexes);
+        }
+        try {
+            return pStmtPool.borrowObject(createKey(sql, columnIndexes));
+        } catch (final RuntimeException e) {
+            throw e;
+        } catch (final Exception e) {
+            throw new SQLException("Borrow prepareStatement from pool failed", e);
+        }
+    }
+
     PreparedStatement prepareStatement(final String sql, final String[] columnNames) throws SQLException {
         if (pStmtPool == null) {
             return connection.prepareStatement(sql, columnNames);
@@ -692,7 +693,6 @@ class PooledConnectionImpl
         eventListeners.remove(listener);
     }
 
-    /* JDBC_4_ANT_KEY_BEGIN */
     @Override
     public void removeStatementEventListener(final StatementEventListener listener) {
         statementEventListeners.remove(listener);
@@ -715,20 +715,6 @@ class PooledConnectionImpl
     }
 
     /**
-     * My {@link KeyedPooledObjectFactory} method for validating {@link PreparedStatement}s.
-     *
-     * @param key
-     *            Ignored.
-     * @param pooledObject
-     *            Ignored.
-     * @return {@code true}
-     */
-    @Override
-    public boolean validateObject(final PStmtKey key, final PooledObject<DelegatingPreparedStatement> pooledObject) {
-        return true;
-    }
-
-    /**
      * @since 2.6.0
      */
     @Override
@@ -753,4 +739,18 @@ class PooledConnectionImpl
         builder.append("]");
         return builder.toString();
     }
+
+    /**
+     * My {@link KeyedPooledObjectFactory} method for validating {@link PreparedStatement}s.
+     *
+     * @param key
+     *            Ignored.
+     * @param pooledObject
+     *            Ignored.
+     * @return {@code true}
+     */
+    @Override
+    public boolean validateObject(final PStmtKey key, final PooledObject<DelegatingPreparedStatement> pooledObject) {
+        return true;
+    }
 }
diff --git a/java/org/apache/tomcat/dbcp/dbcp2/datasources/CPDSConnectionFactory.java b/java/org/apache/tomcat/dbcp/dbcp2/datasources/CPDSConnectionFactory.java
index 311bad9..7ee9031 100644
--- a/java/org/apache/tomcat/dbcp/dbcp2/datasources/CPDSConnectionFactory.java
+++ b/java/org/apache/tomcat/dbcp/dbcp2/datasources/CPDSConnectionFactory.java
@@ -20,6 +20,8 @@ import java.sql.Connection;
 import java.sql.ResultSet;
 import java.sql.SQLException;
 import java.sql.Statement;
+import java.time.Duration;
+import java.time.Instant;
 import java.util.Collections;
 import java.util.Map;
 import java.util.Set;
@@ -48,12 +50,11 @@ class CPDSConnectionFactory
 
     private final ConnectionPoolDataSource cpds;
     private final String validationQuery;
-    private final int validationQueryTimeoutSeconds;
+    private final Duration validationQueryTimeoutDuration;
     private final boolean rollbackAfterValidation;
     private ObjectPool<PooledConnectionAndInfo> pool;
-    private final String userName;
-    private char[] userPassword;
-    private long maxConnLifetimeMillis = -1;
+    private UserPassKey userPassKey;
+    private Duration maxConnDuration = Duration.ofMillis(-1);
 
     /**
      * Map of PooledConnections for which close events are ignored. Connections are muted when they are being validated.
@@ -74,24 +75,23 @@ class CPDSConnectionFactory
      *            a query to use to {@link #validateObject validate} {@link Connection}s. Should return at least one
      *            row. May be {@code null} in which case {@link Connection#isValid(int)} will be used to validate
      *            connections.
-     * @param validationQueryTimeoutSeconds
-     *            Timeout in seconds before validation fails
+     * @param validationQueryTimeoutDuration
+     *            Timeout Duration before validation fails
      * @param rollbackAfterValidation
      *            whether a rollback should be issued after {@link #validateObject validating} {@link Connection}s.
      * @param userName
      *            The user name to use to create connections
      * @param userPassword
      *            The password to use to create connections
-     * @since 2.4.0
+     * @since 2.10.0
      */
     public CPDSConnectionFactory(final ConnectionPoolDataSource cpds, final String validationQuery,
-            final int validationQueryTimeoutSeconds, final boolean rollbackAfterValidation, final String userName,
-            final char[] userPassword) {
+            final Duration validationQueryTimeoutDuration, final boolean rollbackAfterValidation, final String userName,
+        final char[] userPassword) {
         this.cpds = cpds;
         this.validationQuery = validationQuery;
-        this.validationQueryTimeoutSeconds = validationQueryTimeoutSeconds;
-        this.userName = userName;
-        this.userPassword = userPassword;
+        this.validationQueryTimeoutDuration = validationQueryTimeoutDuration;
+        this.userPassKey = new UserPassKey(userName, userPassword);
         this.rollbackAfterValidation = rollbackAfterValidation;
     }
 
@@ -104,7 +104,7 @@ class CPDSConnectionFactory
      *            a query to use to {@link #validateObject validate} {@link Connection}s. Should return at least one
      *            row. May be {@code null} in which case {@link Connection#isValid(int)} will be used to validate
      *            connections.
-     * @param validationQueryTimeoutSeconds
+     * @param validationQueryTimeoutDuration
      *            Timeout in seconds before validation fails
      * @param rollbackAfterValidation
      *            whether a rollback should be issued after {@link #validateObject validating} {@link Connection}s.
@@ -112,149 +112,92 @@ class CPDSConnectionFactory
      *            The user name to use to create connections
      * @param userPassword
      *            The password to use to create connections
+     * @since 2.10.0
      */
-    public CPDSConnectionFactory(final ConnectionPoolDataSource cpds, final String validationQuery,
-            final int validationQueryTimeoutSeconds, final boolean rollbackAfterValidation, final String userName,
-            final String userPassword) {
-        this(cpds, validationQuery, validationQueryTimeoutSeconds, rollbackAfterValidation, userName,
-                Utils.toCharArray(userPassword));
-    }
-
-    /**
-     * (Testing API) Gets the value of password for the default user.
-     *
-     * @return value of password.
-     */
-    char[] getPasswordCharArray() {
-        return userPassword;
+    public CPDSConnectionFactory(final ConnectionPoolDataSource cpds, final String validationQuery, final Duration validationQueryTimeoutDuration,
+        final boolean rollbackAfterValidation, final String userName, final String userPassword) {
+        this(cpds, validationQuery, validationQueryTimeoutDuration, rollbackAfterValidation, userName, Utils.toCharArray(userPassword));
     }
 
     /**
-     * Returns the object pool used to pool connections created by this factory.
+     * Creates a new {@code PoolableConnectionFactory}.
      *
-     * @return ObjectPool managing pooled connections
+     * @param cpds
+     *            the ConnectionPoolDataSource from which to obtain PooledConnection's
+     * @param validationQuery
+     *            a query to use to {@link #validateObject validate} {@link Connection}s. Should return at least one
+     *            row. May be {@code null} in which case {@link Connection#isValid(int)} will be used to validate
+     *            connections.
+     * @param validationQueryTimeoutSeconds
+     *            Timeout in seconds before validation fails
+     * @param rollbackAfterValidation
+     *            whether a rollback should be issued after {@link #validateObject validating} {@link Connection}s.
+     * @param userName
+     *            The user name to use to create connections
+     * @param userPassword
+     *            The password to use to create connections
+     * @since 2.4.0
+     * @deprecated Use {@link #CPDSConnectionFactory(ConnectionPoolDataSource, String, Duration, boolean, String, char[])}.
      */
-    public ObjectPool<PooledConnectionAndInfo> getPool() {
-        return pool;
+    @Deprecated
+    public CPDSConnectionFactory(final ConnectionPoolDataSource cpds, final String validationQuery,
+            final int validationQueryTimeoutSeconds, final boolean rollbackAfterValidation, final String userName,
+        final char[] userPassword) {
+        this.cpds = cpds;
+        this.validationQuery = validationQuery;
+        this.validationQueryTimeoutDuration = Duration.ofSeconds(validationQueryTimeoutSeconds);
+        this.userPassKey = new UserPassKey(userName, userPassword);
+        this.rollbackAfterValidation = rollbackAfterValidation;
     }
 
     /**
+     * Creates a new {@code PoolableConnectionFactory}.
      *
-     * @param pool
-     *            the {@link ObjectPool} in which to pool those {@link Connection}s
+     * @param cpds
+     *            the ConnectionPoolDataSource from which to obtain PooledConnection's
+     * @param validationQuery
+     *            a query to use to {@link #validateObject validate} {@link Connection}s. Should return at least one
+     *            row. May be {@code null} in which case {@link Connection#isValid(int)} will be used to validate
+     *            connections.
+     * @param validationQueryTimeoutSeconds
+     *            Timeout in seconds before validation fails
+     * @param rollbackAfterValidation
+     *            whether a rollback should be issued after {@link #validateObject validating} {@link Connection}s.
+     * @param userName
+     *            The user name to use to create connections
+     * @param userPassword
+     *            The password to use to create connections
+     * @deprecated Use {@link #CPDSConnectionFactory(ConnectionPoolDataSource, String, Duration, boolean, String, String)}.
      */
-    public void setPool(final ObjectPool<PooledConnectionAndInfo> pool) {
-        this.pool = pool;
+    @Deprecated
+    public CPDSConnectionFactory(final ConnectionPoolDataSource cpds, final String validationQuery, final int validationQueryTimeoutSeconds,
+            final boolean rollbackAfterValidation, final String userName, final String userPassword) {
+        this(cpds, validationQuery, validationQueryTimeoutSeconds, rollbackAfterValidation, userName, Utils.toCharArray(userPassword));
     }
 
     @Override
-    public synchronized PooledObject<PooledConnectionAndInfo> makeObject() {
-        final PooledConnectionAndInfo pci;
-        try {
-            PooledConnection pc = null;
-            if (userName == null) {
-                pc = cpds.getPooledConnection();
-            } else {
-                pc = cpds.getPooledConnection(userName, Utils.toString(userPassword));
-            }
-
-            if (pc == null) {
-                throw new IllegalStateException("Connection pool data source returned null from getPooledConnection");
-            }
-
-            // should we add this object as a listener or the pool.
-            // consider the validateObject method in decision
-            pc.addConnectionEventListener(this);
-            pci = new PooledConnectionAndInfo(pc, userName, userPassword);
-            pcMap.put(pc, pci);
-        } catch (final SQLException e) {
-            throw new RuntimeException(e.getMessage());
-        }
-        return new DefaultPooledObject<>(pci);
+    public void activateObject(final PooledObject<PooledConnectionAndInfo> p) throws Exception {
+        validateLifetime(p);
     }
 
     /**
-     * Closes the PooledConnection and stops listening for events from it.
+     * Verifies that the user name matches the user whose connections are being managed by this factory and closes the
+     * pool if this is the case; otherwise does nothing.
      */
     @Override
-    public void destroyObject(final PooledObject<PooledConnectionAndInfo> p) throws Exception {
-        doDestroyObject(p.getObject());
-    }
-
-    private void doDestroyObject(final PooledConnectionAndInfo pci) throws Exception {
-        final PooledConnection pc = pci.getPooledConnection();
-        pc.removeConnectionEventListener(this);
-        pcMap.remove(pc);
-        pc.close();
-    }
-
-    @Override
-    public boolean validateObject(final PooledObject<PooledConnectionAndInfo> p) {
-        try {
-            validateLifetime(p);
-        } catch (final Exception e) {
-            return false;
-        }
-        boolean valid = false;
-        final PooledConnection pconn = p.getObject().getPooledConnection();
-        Connection conn = null;
-        validatingSet.add(pconn);
-        if (null == validationQuery) {
-            int timeoutSeconds = validationQueryTimeoutSeconds;
-            if (timeoutSeconds < 0) {
-                timeoutSeconds = 0;
-            }
-            try {
-                conn = pconn.getConnection();
-                valid = conn.isValid(timeoutSeconds);
-            } catch (final SQLException e) {
-                valid = false;
-            } finally {
-                Utils.closeQuietly(conn);
-                validatingSet.remove(pconn);
-            }
-        } else {
-            Statement stmt = null;
-            ResultSet rset = null;
-            // logical Connection from the PooledConnection must be closed
-            // before another one can be requested and closing it will
-            // generate an event. Keep track so we know not to return
-            // the PooledConnection
-            validatingSet.add(pconn);
-            try {
-                conn = pconn.getConnection();
-                stmt = conn.createStatement();
-                rset = stmt.executeQuery(validationQuery);
-                valid = rset.next();
-                if (rollbackAfterValidation) {
-                    conn.rollback();
-                }
-            } catch (final Exception e) {
-                valid = false;
-            } finally {
-                Utils.closeQuietly(rset);
-                Utils.closeQuietly(stmt);
-                Utils.closeQuietly(conn);
-                validatingSet.remove(pconn);
+    public void closePool(final String userName) throws SQLException {
+        synchronized (this) {
+            if (userName == null || !userName.equals(this.userPassKey.getUserName())) {
+                return;
             }
         }
-        return valid;
-    }
-
-    @Override
-    public void passivateObject(final PooledObject<PooledConnectionAndInfo> p) throws Exception {
-        validateLifetime(p);
-    }
-
-    @Override
-    public void activateObject(final PooledObject<PooledConnectionAndInfo> p) throws Exception {
-        validateLifetime(p);
+        try {
+            pool.close();
+        } catch (final Exception ex) {
+            throw new SQLException("Error closing connection pool", ex);
+        }
     }
 
-    // ***********************************************************************
-    // java.sql.ConnectionEventListener implementation
-    // ***********************************************************************
-
     /**
      * This will be called if the Connection returned by the getConnection method came from a PooledConnection, and the
      * user calls the close() method of this connection object. What we need to do here is to release this
@@ -309,9 +252,38 @@ class CPDSConnectionFactory
         }
     }
 
-    // ***********************************************************************
-    // PooledConnectionManager implementation
-    // ***********************************************************************
+    /**
+     * Closes the PooledConnection and stops listening for events from it.
+     */
+    @Override
+    public void destroyObject(final PooledObject<PooledConnectionAndInfo> p) throws Exception {
+        doDestroyObject(p.getObject());
+    }
+
+    private void doDestroyObject(final PooledConnectionAndInfo pci) throws Exception {
+        final PooledConnection pc = pci.getPooledConnection();
+        pc.removeConnectionEventListener(this);
+        pcMap.remove(pc);
+        pc.close();
+    }
+
+    /**
+     * (Testing API) Gets the value of password for the default user.
+     *
+     * @return value of password.
+     */
+    char[] getPasswordCharArray() {
+        return userPassKey.getPasswordCharArray();
+    }
+
+    /**
+     * Returns the object pool used to pool connections created by this factory.
+     *
+     * @return ObjectPool managing pooled connections
+     */
+    public ObjectPool<PooledConnectionAndInfo> getPool() {
+        return pool;
+    }
 
     /**
      * Invalidates the PooledConnection in the pool. The CPDSConnectionFactory closes the connection and pool counters
@@ -332,25 +304,65 @@ class CPDSConnectionFactory
         }
     }
 
+    // ***********************************************************************
+    // java.sql.ConnectionEventListener implementation
+    // ***********************************************************************
+
+    @Override
+    public synchronized PooledObject<PooledConnectionAndInfo> makeObject() {
+        final PooledConnectionAndInfo pci;
+        try {
+            PooledConnection pc = null;
+            if (userPassKey.getUserName() == null) {
+                pc = cpds.getPooledConnection();
+            } else {
+                pc = cpds.getPooledConnection(userPassKey.getUserName(), userPassKey.getPassword());
+            }
+
+            if (pc == null) {
+                throw new IllegalStateException("Connection pool data source returned null from getPooledConnection");
+            }
+
+            // should we add this object as a listener or the pool.
+            // consider the validateObject method in decision
+            pc.addConnectionEventListener(this);
+            pci = new PooledConnectionAndInfo(pc, userPassKey);
+            pcMap.put(pc, pci);
+        } catch (final SQLException e) {
+            throw new RuntimeException(e.getMessage());
+        }
+        return new DefaultPooledObject<>(pci);
+    }
+
+    @Override
+    public void passivateObject(final PooledObject<PooledConnectionAndInfo> p) throws Exception {
+        validateLifetime(p);
+    }
+
     /**
-     * Sets the database password used when creating new connections.
+     * Sets the maximum Duration of a connection after which the connection will always fail activation,
+     * passivation and validation.
      *
-     * @param userPassword
-     *            new password
+     * @param maxConnDuration
+     *            A value of zero or less indicates an infinite lifetime. The default value is -1 milliseconds.
+     * @since 2.10.0
      */
-    public synchronized void setPassword(final char[] userPassword) {
-        this.userPassword =  Utils.clone(userPassword);
+    public void setMaxConn(final Duration maxConnDuration) {
+        this.maxConnDuration = maxConnDuration;
     }
 
     /**
-     * Sets the database password used when creating new connections.
+     * Sets the maximum lifetime in milliseconds of a connection after which the connection will always fail activation,
+     * passivation and validation.
      *
-     * @param userPassword
-     *            new password
+     * @param maxConnDuration
+     *            A value of zero or less indicates an infinite lifetime. The default value is -1 milliseconds.
+     * @since 2.9.0
+     * @deprecated Use {@link #setMaxConn(Duration)}.
      */
-    @Override
-    public synchronized void setPassword(final String userPassword) {
-        this.userPassword = Utils.toCharArray(userPassword);
+    @Deprecated
+    public void setMaxConnLifetime(final Duration maxConnDuration) {
+        this.maxConnDuration = maxConnDuration;
     }
 
     /**
@@ -359,37 +371,41 @@ class CPDSConnectionFactory
      *
      * @param maxConnLifetimeMillis
      *            A value of zero or less indicates an infinite lifetime. The default value is -1.
+     * @deprecated Use {@link #setMaxConn(Duration)}.
      */
+    @Deprecated
     public void setMaxConnLifetimeMillis(final long maxConnLifetimeMillis) {
-        this.maxConnLifetimeMillis = maxConnLifetimeMillis;
+        setMaxConnLifetime(Duration.ofMillis(maxConnLifetimeMillis));
     }
 
     /**
-     * Verifies that the user name matches the user whose connections are being managed by this factory and closes the
-     * pool if this is the case; otherwise does nothing.
+     * Sets the database password used when creating new connections.
+     *
+     * @param userPassword
+     *            new password
+     */
+    public synchronized void setPassword(final char[] userPassword) {
+        this.userPassKey = new UserPassKey(userPassKey.getUserName(), userPassword);
+    }
+
+    /**
+     * Sets the database password used when creating new connections.
+     *
+     * @param userPassword
+     *            new password
      */
     @Override
-    public void closePool(final String userName) throws SQLException {
-        synchronized (this) {
-            if (userName == null || !userName.equals(this.userName)) {
-                return;
-            }
-        }
-        try {
-            pool.close();
-        } catch (final Exception ex) {
-            throw new SQLException("Error closing connection pool", ex);
-        }
+    public synchronized void setPassword(final String userPassword) {
+        this.userPassKey = new UserPassKey(userPassKey.getUserName(), userPassword);
     }
 
-    private void validateLifetime(final PooledObject<PooledConnectionAndInfo> p) throws Exception {
-        if (maxConnLifetimeMillis > 0) {
-            final long lifetime = System.currentTimeMillis() - p.getCreateTime();
-            if (lifetime > maxConnLifetimeMillis) {
-                throw new Exception(Utils.getMessage("connectionFactory.lifetimeExceeded", Long.valueOf(lifetime),
-                        Long.valueOf(maxConnLifetimeMillis)));
-            }
-        }
+    /**
+     *
+     * @param pool
+     *            the {@link ObjectPool} in which to pool those {@link Connection}s
+     */
+    public void setPool(final ObjectPool<PooledConnectionAndInfo> pool) {
+        this.pool = pool;
     }
 
     /**
@@ -402,19 +418,81 @@ class CPDSConnectionFactory
         builder.append(cpds);
         builder.append(", validationQuery=");
         builder.append(validationQuery);
-        builder.append(", validationQueryTimeoutSeconds=");
-        builder.append(validationQueryTimeoutSeconds);
+        builder.append(", validationQueryTimeoutDuration=");
+        builder.append(validationQueryTimeoutDuration);
         builder.append(", rollbackAfterValidation=");
         builder.append(rollbackAfterValidation);
         builder.append(", pool=");
         builder.append(pool);
-        builder.append(", maxConnLifetimeMillis=");
-        builder.append(maxConnLifetimeMillis);
+        builder.append(", maxConnDuration=");
+        builder.append(maxConnDuration);
         builder.append(", validatingSet=");
         builder.append(validatingSet);
         builder.append(", pcMap=");
         builder.append(pcMap);
-        builder.append(']');
+        builder.append("]");
         return builder.toString();
     }
+
+    private void validateLifetime(final PooledObject<PooledConnectionAndInfo> pooledObject) throws Exception {
+        if (maxConnDuration.compareTo(Duration.ZERO) > 0) {
+            final Duration lifetimeDuration = Duration.between(pooledObject.getCreateInstant(), Instant.now());
+            if (lifetimeDuration.compareTo(maxConnDuration) > 0) {
+                throw new Exception(Utils.getMessage("connectionFactory.lifetimeExceeded", lifetimeDuration, maxConnDuration));
+            }
+        }
+    }
+
+    @Override
+    public boolean validateObject(final PooledObject<PooledConnectionAndInfo> p) {
+        try {
+            validateLifetime(p);
+        } catch (final Exception e) {
+            return false;
+        }
+        boolean valid = false;
+        final PooledConnection pconn = p.getObject().getPooledConnection();
+        Connection conn = null;
+        validatingSet.add(pconn);
+        if (null == validationQuery) {
+            Duration timeoutDuration = validationQueryTimeoutDuration;
+            if (timeoutDuration.isNegative()) {
+                timeoutDuration = Duration.ZERO;
+            }
+            try {
+                conn = pconn.getConnection();
+                valid = conn.isValid((int) timeoutDuration.getSeconds());
+            } catch (final SQLException e) {
+                valid = false;
+            } finally {
+                Utils.closeQuietly((AutoCloseable) conn);
+                validatingSet.remove(pconn);
+            }
+        } else {
+            Statement stmt = null;
+            ResultSet rset = null;
+            // logical Connection from the PooledConnection must be closed
+            // before another one can be requested and closing it will
+            // generate an event. Keep track so we know not to return
+            // the PooledConnection
+            validatingSet.add(pconn);
+            try {
+                conn = pconn.getConnection();
+                stmt = conn.createStatement();
+                rset = stmt.executeQuery(validationQuery);
+                valid = rset.next();
+                if (rollbackAfterValidation) {
+                    conn.rollback();
+                }
+            } catch (final Exception e) {
+                valid = false;
+            } finally {
+                Utils.closeQuietly((AutoCloseable) rset);
+                Utils.closeQuietly((AutoCloseable) stmt);
+                Utils.closeQuietly((AutoCloseable) conn);
+                validatingSet.remove(pconn);
+            }
+        }
+        return valid;
+    }
 }
diff --git a/java/org/apache/tomcat/dbcp/dbcp2/datasources/CharArray.java b/java/org/apache/tomcat/dbcp/dbcp2/datasources/CharArray.java
new file mode 100644
index 0000000..58b4d9a
--- /dev/null
+++ b/java/org/apache/tomcat/dbcp/dbcp2/datasources/CharArray.java
@@ -0,0 +1,88 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.tomcat.dbcp.dbcp2.datasources;
+
+import java.util.Arrays;
+
+import org.apache.tomcat.dbcp.dbcp2.Utils;
+
+/**
+ * A {@code char} array wrapper that does not reveal its contents inadvertently through toString(). In contrast to, for
+ * example, AtomicReference which toString()'s its contents.
+ *
+ * May contain null.
+ *
+ * @since 2.9.0
+ */
+final class CharArray {
+
+    static final CharArray NULL = new CharArray((char[]) null);
+
+    private final char[] chars;
+
+    CharArray(final char[] chars) {
+        this.chars = Utils.clone(chars);
+    }
+
+    CharArray(final String string) {
+        this.chars = Utils.toCharArray(string);
+    }
+
+    /**
+     * Converts the value of char array as a String.
+     *
+     * @return value as a string, may be null.
+     */
+    String asString() {
+        return Utils.toString(chars);
+    }
+
+    @Override
+    public boolean equals(final Object obj) {
+        if (this == obj) {
+            return true;
+        }
+        if (!(obj instanceof CharArray)) {
+            return false;
+        }
+        final CharArray other = (CharArray) obj;
+        return Arrays.equals(chars, other.chars);
+    }
+
+    /**
+     * Gets the value of char array.
+     *
+     * @return value, may be null.
+     */
+    char[] get() {
+        return chars == null ? null : chars.clone();
+    }
+
+    @Override
+    public int hashCode() {
+        return Arrays.hashCode(chars);
+    }
+
+    /**
+     * Calls {@code super.toString()} and does not reveal its contents inadvertently.
+     */
+    @Override
+    public String toString() {
+        return super.toString();
+    }
+}
diff --git a/java/org/apache/tomcat/dbcp/dbcp2/datasources/InstanceKeyDataSource.java b/java/org/apache/tomcat/dbcp/dbcp2/datasources/InstanceKeyDataSource.java
index 7f99a9b..43cd8ba 100644
--- a/java/org/apache/tomcat/dbcp/dbcp2/datasources/InstanceKeyDataSource.java
+++ b/java/org/apache/tomcat/dbcp/dbcp2/datasources/InstanceKeyDataSource.java
@@ -23,6 +23,7 @@ import java.nio.charset.StandardCharsets;
 import java.sql.Connection;
 import java.sql.SQLException;
 import java.sql.SQLFeatureNotSupportedException;
+import java.time.Duration;
 import java.util.Properties;
 import java.util.logging.Logger;
 
@@ -35,6 +36,7 @@ import javax.sql.PooledConnection;
 
 import org.apache.tomcat.dbcp.pool2.impl.BaseObjectPoolConfig;
 import org.apache.tomcat.dbcp.pool2.impl.GenericKeyedObjectPoolConfig;
+import org.apache.tomcat.dbcp.pool2.impl.GenericObjectPool;
 
 /**
  * <p>
@@ -99,8 +101,8 @@ public abstract class InstanceKeyDataSource implements DataSource, Referenceable
     /** Environment that may be used to set up a JNDI initial context. */
     private Properties jndiEnvironment;
 
-    /** Login TimeOut in seconds */
-    private int loginTimeout;
+    /** Login Timeout */
+    private Duration loginTimeoutDuration = Duration.ZERO;
 
     /** Log stream */
     private PrintWriter logWriter;
@@ -114,22 +116,22 @@ public abstract class InstanceKeyDataSource implements DataSource, Referenceable
     private boolean defaultLifo = BaseObjectPoolConfig.DEFAULT_LIFO;
     private int defaultMaxIdle = GenericKeyedObjectPoolConfig.DEFAULT_MAX_IDLE_PER_KEY;
     private int defaultMaxTotal = GenericKeyedObjectPoolConfig.DEFAULT_MAX_TOTAL;
-    private long defaultMaxWaitMillis = BaseObjectPoolConfig.DEFAULT_MAX_WAIT_MILLIS;
-    private long defaultMinEvictableIdleTimeMillis = BaseObjectPoolConfig.DEFAULT_MIN_EVICTABLE_IDLE_TIME_MILLIS;
+    private Duration defaultMaxWaitDuration = BaseObjectPoolConfig.DEFAULT_MAX_WAIT;
+    private Duration defaultMinEvictableIdleDuration = BaseObjectPoolConfig.DEFAULT_MIN_EVICTABLE_IDLE_DURATION;
     private int defaultMinIdle = GenericKeyedObjectPoolConfig.DEFAULT_MIN_IDLE_PER_KEY;
     private int defaultNumTestsPerEvictionRun = BaseObjectPoolConfig.DEFAULT_NUM_TESTS_PER_EVICTION_RUN;
-    private long defaultSoftMinEvictableIdleTimeMillis = BaseObjectPoolConfig.DEFAULT_SOFT_MIN_EVICTABLE_IDLE_TIME_MILLIS;
+    private Duration defaultSoftMinEvictableIdleDuration = BaseObjectPoolConfig.DEFAULT_SOFT_MIN_EVICTABLE_IDLE_DURATION;
     private boolean defaultTestOnCreate = BaseObjectPoolConfig.DEFAULT_TEST_ON_CREATE;
     private boolean defaultTestOnBorrow = BaseObjectPoolConfig.DEFAULT_TEST_ON_BORROW;
     private boolean defaultTestOnReturn = BaseObjectPoolConfig.DEFAULT_TEST_ON_RETURN;
     private boolean defaultTestWhileIdle = BaseObjectPoolConfig.DEFAULT_TEST_WHILE_IDLE;
-    private long defaultTimeBetweenEvictionRunsMillis = BaseObjectPoolConfig.DEFAULT_TIME_BETWEEN_EVICTION_RUNS_MILLIS;
+    private Duration defaultDurationBetweenEvictionRuns = BaseObjectPoolConfig.DEFAULT_TIME_BETWEEN_EVICTION_RUNS;
 
     // Connection factory properties
     private String validationQuery;
-    private int validationQueryTimeoutSeconds = -1;
+    private Duration validationQueryTimeoutDuration = Duration.ofSeconds(-1);
     private boolean rollbackAfterValidation;
-    private long maxConnLifetimeMillis = -1;
+    private Duration maxConnDuration = Duration.ofMillis(-1);
 
     // Connection properties
     private Boolean defaultAutoCommit;
@@ -137,7 +139,7 @@ public abstract class InstanceKeyDataSource implements DataSource, Referenceable
     private Boolean defaultReadOnly;
 
     /**
-     * Default no-arg constructor for Serialization
+     * Default no-arg constructor for Serialization.
      */
     public InstanceKeyDataSource() {
     }
@@ -159,27 +161,138 @@ public abstract class InstanceKeyDataSource implements DataSource, Referenceable
     @Override
     public abstract void close() throws Exception;
 
-    protected abstract PooledConnectionManager getConnectionManager(UserPassKey upkey);
+    private void closeDueToException(final PooledConnectionAndInfo info) {
+        if (info != null) {
+            try {
+                info.getPooledConnection().getConnection().close();
+            } catch (final Exception e) {
+                // do not throw this exception because we are in the middle
+                // of handling another exception. But record it because
+                // it potentially leaks connections from the pool.
+                getLogWriter().println("[ERROR] Could not return connection to pool during exception handling. " + e.getMessage());
+            }
+        }
+    }
 
-    /* JDBC_4_ANT_KEY_BEGIN */
+    /**
+     * Attempts to establish a database connection.
+     */
     @Override
-    public boolean isWrapperFor(final Class<?> iface) throws SQLException {
-        return false;
+    public Connection getConnection() throws SQLException {
+        return getConnection(null, null);
     }
 
+    /**
+     * Attempts to retrieve a database connection using {@link #getPooledConnectionAndInfo(String, String)} with the
+     * provided user name and password. The password on the {@code PooledConnectionAndInfo} instance returned by
+     * <code>getPooledConnectionAndInfo</code> is compared to the <code>password</code> parameter. If the comparison
+     * fails, a database connection using the supplied user name and password is attempted. If the connection attempt
+     * fails, an SQLException is thrown, indicating that the given password did not match the password used to create
+     * the pooled connection. If the connection attempt succeeds, this means that the database password has been
+     * changed. In this case, the <code>PooledConnectionAndInfo</code> instance retrieved with the old password is
+     * destroyed and the <code>getPooledConnectionAndInfo</code> is repeatedly invoked until a
+     * <code>PooledConnectionAndInfo</code> instance with the new password is returned.
+     */
     @Override
-    public <T> T unwrap(final Class<T> iface) throws SQLException {
-        throw new SQLException("InstanceKeyDataSource is not a wrapper.");
+    public Connection getConnection(final String userName, final String userPassword) throws SQLException {
+        if (instanceKey == null) {
+            throw new SQLException("Must set the ConnectionPoolDataSource "
+                    + "through setDataSourceName or setConnectionPoolDataSource" + " before calling getConnection.");
+        }
+        getConnectionCalled = true;
+        PooledConnectionAndInfo info = null;
+        try {
+            info = getPooledConnectionAndInfo(userName, userPassword);
+        } catch (final RuntimeException | SQLException e) {
+            closeDueToException(info);
+            throw e;
+        } catch (final Exception e) {
+            closeDueToException(info);
+            throw new SQLException("Cannot borrow connection from pool", e);
+        }
+
+        // Password on PooledConnectionAndInfo does not match
+        if (!(null == userPassword ? null == info.getPassword() : userPassword.equals(info.getPassword()))) {
+            try { // See if password has changed by attempting connection
+                testCPDS(userName, userPassword);
+            } catch (final SQLException ex) {
+                // Password has not changed, so refuse client, but return connection to the pool
+                closeDueToException(info);
+                throw new SQLException(
+                        "Given password did not match password used" + " to create the PooledConnection.", ex);
+            } catch (final javax.naming.NamingException ne) {
+                throw new SQLException("NamingException encountered connecting to database", ne);
+            }
+            /*
+             * Password must have changed -> destroy connection and keep retrying until we get a new, good one,
+             * destroying any idle connections with the old password as we pull them from the pool.
+             */
+            final UserPassKey upkey = info.getUserPassKey();
+            final PooledConnectionManager manager = getConnectionManager(upkey);
+            // Destroy and remove from pool
+            manager.invalidate(info.getPooledConnection());
+            // Reset the password on the factory if using CPDSConnectionFactory
+            manager.setPassword(upkey.getPassword());
+            info = null;
+            for (int i = 0; i < 10; i++) { // Bound the number of retries - only needed if bad instances return
+                try {
+                    info = getPooledConnectionAndInfo(userName, userPassword);
+                } catch (final RuntimeException | SQLException e) {
+                    closeDueToException(info);
+                    throw e;
+                } catch (final Exception e) {
+                    closeDueToException(info);
+                    throw new SQLException("Cannot borrow connection from pool", e);
+                }
+                if (info != null && userPassword != null && userPassword.equals(info.getPassword())) {
+                    break;
+                }
+                if (info != null) {
+                    manager.invalidate(info.getPooledConnection());
+                }
+                info = null;
+            }
+            if (info == null) {
+                throw new SQLException("Cannot borrow connection from pool - password change failure.");
+            }
+        }
+
+        final Connection connection = info.getPooledConnection().getConnection();
+        try {
+            setupDefaults(connection, userName);
+            connection.clearWarnings();
+            return connection;
+        } catch (final SQLException ex) {
+            try {
+                connection.close();
+            } catch (final Exception exc) {
+                getLogWriter().println("ignoring exception during close: " + exc);
+            }
+            throw ex;
+        }
     }
-    /* JDBC_4_ANT_KEY_END */
 
-    @Override
-    public Logger getParentLogger() throws SQLFeatureNotSupportedException {
-        throw new SQLFeatureNotSupportedException();
+    protected abstract PooledConnectionManager getConnectionManager(UserPassKey upkey);
+
+    /**
+     * Gets the value of connectionPoolDataSource. This method will return null, if the backing data source is being
+     * accessed via JNDI.
+     *
+     * @return value of connectionPoolDataSource.
+     */
+    public ConnectionPoolDataSource getConnectionPoolDataSource() {
+        return dataSource;
     }
 
-    // -------------------------------------------------------------------
-    // Properties
+    /**
+     * Gets the name of the ConnectionPoolDataSource which backs this pool. This name is used to look up the data source
+     * from a JNDI service provider.
+     *
+     * @return value of dataSourceName.
+     */
+    public String getDataSourceName() {
+        return dataSourceName;
+    }
 
     /**
      * Gets the default value for {@link GenericKeyedObjectPoolConfig#getBlockWhenExhausted()} for each per user pool.
@@ -192,15 +305,13 @@ public abstract class InstanceKeyDataSource implements DataSource, Referenceable
     }
 
     /**
-     * Sets the default value for {@link GenericKeyedObjectPoolConfig#getBlockWhenExhausted()} for each per user pool.
+     * Gets the default value for {@link GenericObjectPool#getDurationBetweenEvictionRuns()} for each per user pool.
      *
-     * @param blockWhenExhausted
-     *            The default value for {@link GenericKeyedObjectPoolConfig#getBlockWhenExhausted()} for each per user
-     *            pool.
+     * @return The default value for {@link GenericObjectPool#getDurationBetweenEvictionRuns()} for each per user pool.
+     * @since 2.10.0
      */
-    public void setDefaultBlockWhenExhausted(final boolean blockWhenExhausted) {
-        assertInitializationAllowed();
-        this.defaultBlockWhenExhausted = blockWhenExhausted;
+    public Duration getDefaultDurationBetweenEvictionRuns() {
+        return this.defaultDurationBetweenEvictionRuns;
     }
 
     /**
@@ -215,19 +326,6 @@ public abstract class InstanceKeyDataSource implements DataSource, Referenceable
     }
 
     /**
-     * Sets the default value for {@link GenericKeyedObjectPoolConfig#getEvictionPolicyClassName()} for each per user
-     * pool.
-     *
-     * @param evictionPolicyClassName
-     *            The default value for {@link GenericKeyedObjectPoolConfig#getEvictionPolicyClassName()} for each per
-     *            user pool.
-     */
-    public void setDefaultEvictionPolicyClassName(final String evictionPolicyClassName) {
-        assertInitializationAllowed();
-        this.defaultEvictionPolicyClassName = evictionPolicyClassName;
-    }
-
-    /**
      * Gets the default value for {@link GenericKeyedObjectPoolConfig#getLifo()} for each per user pool.
      *
      * @return The default value for {@link GenericKeyedObjectPoolConfig#getLifo()} for each per user pool.
@@ -237,17 +335,6 @@ public abstract class InstanceKeyDataSource implements DataSource, Referenceable
     }
 
     /**
-     * Sets the default value for {@link GenericKeyedObjectPoolConfig#getLifo()} for each per user pool.
-     *
-     * @param lifo
-     *            The default value for {@link GenericKeyedObjectPoolConfig#getLifo()} for each per user pool.
-     */
-    public void setDefaultLifo(final boolean lifo) {
-        assertInitializationAllowed();
-        this.defaultLifo = lifo;
-    }
-
-    /**
      * Gets the default value for {@link GenericKeyedObjectPoolConfig#getMaxIdlePerKey()} for each per user pool.
      *
      * @return The default value for {@link GenericKeyedObjectPoolConfig#getMaxIdlePerKey()} for each per user pool.
@@ -257,17 +344,6 @@ public abstract class InstanceKeyDataSource implements DataSource, Referenceable
     }
 
     /**
-     * Sets the default value for {@link GenericKeyedObjectPoolConfig#getMaxIdlePerKey()} for each per user pool.
-     *
-     * @param maxIdle
-     *            The default value for {@link GenericKeyedObjectPoolConfig#getMaxIdlePerKey()} for each per user pool.
-     */
-    public void setDefaultMaxIdle(final int maxIdle) {
-        assertInitializationAllowed();
-        this.defaultMaxIdle = maxIdle;
-    }
-
-    /**
      * Gets the default value for {@link GenericKeyedObjectPoolConfig#getMaxTotalPerKey()} for each per user pool.
      *
      * @return The default value for {@link GenericKeyedObjectPoolConfig#getMaxTotalPerKey()} for each per user pool.
@@ -277,58 +353,49 @@ public abstract class InstanceKeyDataSource implements DataSource, Referenceable
     }
 
     /**
-     * Sets the default value for {@link GenericKeyedObjectPoolConfig#getMaxTotalPerKey()} for each per user pool.
+     * Gets the default value for {@link GenericKeyedObjectPoolConfig#getMaxWaitDuration()} for each per user pool.
      *
-     * @param maxTotal
-     *            The default value for {@link GenericKeyedObjectPoolConfig#getMaxTotalPerKey()} for each per user pool.
+     * @return The default value for {@link GenericKeyedObjectPoolConfig#getMaxWaitDuration()} for each per user pool.
+     * @since 2.9.0
      */
-    public void setDefaultMaxTotal(final int maxTotal) {
-        assertInitializationAllowed();
-        this.defaultMaxTotal = maxTotal;
+    public Duration getDefaultMaxWait() {
+        return this.defaultMaxWaitDuration;
     }
 
     /**
-     * Gets the default value for {@link GenericKeyedObjectPoolConfig#getMaxWaitMillis()} for each per user pool.
+     * Gets the default value for {@link GenericKeyedObjectPoolConfig#getMaxWaitDuration()} for each per user pool.
      *
-     * @return The default value for {@link GenericKeyedObjectPoolConfig#getMaxWaitMillis()} for each per user pool.
+     * @return The default value for {@link GenericKeyedObjectPoolConfig#getMaxWaitDuration()} for each per user pool.
+     * @deprecated Use {@link #getDefaultMaxWait()}.
      */
+    @Deprecated
     public long getDefaultMaxWaitMillis() {
-        return this.defaultMaxWaitMillis;
-    }
-
-    /**
-     * Sets the default value for {@link GenericKeyedObjectPoolConfig#getMaxWaitMillis()} for each per user pool.
-     *
-     * @param maxWaitMillis
-     *            The default value for {@link GenericKeyedObjectPoolConfig#getMaxWaitMillis()} for each per user pool.
-     */
-    public void setDefaultMaxWaitMillis(final long maxWaitMillis) {
-        assertInitializationAllowed();
-        this.defaultMaxWaitMillis = maxWaitMillis;
+        return getDefaultMaxWait().toMillis();
     }
 
     /**
-     * Gets the default value for {@link GenericKeyedObjectPoolConfig#getMinEvictableIdleTimeMillis()} for each per user
+     * Gets the default value for {@link GenericKeyedObjectPoolConfig#getMinEvictableIdleDuration()} for each per user
      * pool.
      *
-     * @return The default value for {@link GenericKeyedObjectPoolConfig#getMinEvictableIdleTimeMillis()} for each per
+     * @return The default value for {@link GenericKeyedObjectPoolConfig#getMinEvictableIdleDuration()} for each per
      *         user pool.
+     * @since 2.10.0
      */
-    public long getDefaultMinEvictableIdleTimeMillis() {
-        return this.defaultMinEvictableIdleTimeMillis;
+    public Duration getDefaultMinEvictableIdleDuration() {
+        return this.defaultMinEvictableIdleDuration;
     }
 
     /**
-     * Sets the default value for {@link GenericKeyedObjectPoolConfig#getMinEvictableIdleTimeMillis()} for each per user
+     * Gets the default value for {@link GenericKeyedObjectPoolConfig#getMinEvictableIdleDuration()} for each per user
      * pool.
      *
-     * @param minEvictableIdleTimeMillis
-     *            The default value for {@link GenericKeyedObjectPoolConfig#getMinEvictableIdleTimeMillis()} for each
-     *            per user pool.
+     * @return The default value for {@link GenericKeyedObjectPoolConfig#getMinEvictableIdleDuration()} for each per
+     *         user pool.
+     * @deprecated Use {@link #getDefaultMinEvictableIdleDuration()}.
      */
-    public void setDefaultMinEvictableIdleTimeMillis(final long minEvictableIdleTimeMillis) {
-        assertInitializationAllowed();
-        this.defaultMinEvictableIdleTimeMillis = minEvictableIdleTimeMillis;
+    @Deprecated
+    public long getDefaultMinEvictableIdleTimeMillis() {
+        return this.defaultMinEvictableIdleDuration.toMillis();
     }
 
     /**
@@ -341,17 +408,6 @@ public abstract class InstanceKeyDataSource implements DataSource, Referenceable
     }
 
     /**
-     * Sets the default value for {@link GenericKeyedObjectPoolConfig#getMinIdlePerKey()} for each per user pool.
-     *
-     * @param minIdle
-     *            The default value for {@link GenericKeyedObjectPoolConfig#getMinIdlePerKey()} for each per user pool.
-     */
-    public void setDefaultMinIdle(final int minIdle) {
-        assertInitializationAllowed();
-        this.defaultMinIdle = minIdle;
-    }
-
-    /**
      * Gets the default value for {@link GenericKeyedObjectPoolConfig#getNumTestsPerEvictionRun()} for each per user
      * pool.
      *
@@ -363,16 +419,15 @@ public abstract class InstanceKeyDataSource implements DataSource, Referenceable
     }
 
     /**
-     * Sets the default value for {@link GenericKeyedObjectPoolConfig#getNumTestsPerEvictionRun()} for each per user
-     * pool.
+     * Gets the default value for {@link org.apache.tomcat.dbcp.pool2.impl.GenericObjectPool
+     * GenericObjectPool#getSoftMinEvictableIdleTimeMillis()} for each per user pool.
      *
-     * @param numTestsPerEvictionRun
-     *            The default value for {@link GenericKeyedObjectPoolConfig#getNumTestsPerEvictionRun()} for each per
-     *            user pool.
+     * @return The default value for {@link org.apache.tomcat.dbcp.pool2.impl.GenericObjectPool
+     *         GenericObjectPool#getSoftMinEvictableIdleTimeMillis()} for each per user pool.
+     * @since 2.10.0
      */
-    public void setDefaultNumTestsPerEvictionRun(final int numTestsPerEvictionRun) {
-        assertInitializationAllowed();
-        this.defaultNumTestsPerEvictionRun = numTestsPerEvictionRun;
+    public Duration getDefaultSoftMinEvictableIdleDuration() {
+        return this.defaultSoftMinEvictableIdleDuration;
     }
 
     /**
@@ -381,22 +436,22 @@ public abstract class InstanceKeyDataSource implements DataSource, Referenceable
      *
      * @return The default value for {@link org.apache.tomcat.dbcp.pool2.impl.GenericObjectPool
      *         GenericObjectPool#getSoftMinEvictableIdleTimeMillis()} for each per user pool.
+     * @deprecated Use {@link #getDefaultSoftMinEvictableIdleDuration()}.
      */
+    @Deprecated
     public long getDefaultSoftMinEvictableIdleTimeMillis() {
-        return this.defaultSoftMinEvictableIdleTimeMillis;
+        return this.defaultSoftMinEvictableIdleDuration.toMillis();
     }
 
     /**
-     * Sets the default value for {@link org.apache.tomcat.dbcp.pool2.impl.GenericObjectPool
-     * GenericObjectPool#getSoftMinEvictableIdleTimeMillis()} for each per user pool.
+     * Gets the default value for {@link org.apache.tomcat.dbcp.pool2.impl.GenericObjectPool
+     * GenericObjectPool#getTestOnBorrow()} for each per user pool.
      *
-     * @param softMinEvictableIdleTimeMillis
-     *            The default value for {@link org.apache.tomcat.dbcp.pool2.impl.GenericObjectPool
-     *            GenericObjectPool#getSoftMinEvictableIdleTimeMillis()} for each per user pool.
+     * @return The default value for {@link org.apache.tomcat.dbcp.pool2.impl.GenericObjectPool
+     *         GenericObjectPool#getTestOnBorrow()} for each per user pool.
      */
-    public void setDefaultSoftMinEvictableIdleTimeMillis(final long softMinEvictableIdleTimeMillis) {
-        assertInitializationAllowed();
-        this.defaultSoftMinEvictableIdleTimeMillis = softMinEvictableIdleTimeMillis;
+    public boolean getDefaultTestOnBorrow() {
+        return this.defaultTestOnBorrow;
     }
 
     /**
@@ -411,206 +466,440 @@ public abstract class InstanceKeyDataSource implements DataSource, Referenceable
     }
 
     /**
-     * Sets the default value for {@link org.apache.tomcat.dbcp.pool2.impl.GenericObjectPool
-     * GenericObjectPool#getTestOnCreate()} for each per user pool.
+     * Gets the default value for {@link org.apache.tomcat.dbcp.pool2.impl.GenericObjectPool
+     * GenericObjectPool#getTestOnReturn()} for each per user pool.
      *
-     * @param testOnCreate
-     *            The default value for {@link org.apache.tomcat.dbcp.pool2.impl.GenericObjectPool
-     *            GenericObjectPool#getTestOnCreate()} for each per user pool.
+     * @return The default value for {@link org.apache.tomcat.dbcp.pool2.impl.GenericObjectPool
+     *         GenericObjectPool#getTestOnReturn()} for each per user pool.
      */
-    public void setDefaultTestOnCreate(final boolean testOnCreate) {
-        assertInitializationAllowed();
-        this.defaultTestOnCreate = testOnCreate;
+    public boolean getDefaultTestOnReturn() {
+        return this.defaultTestOnReturn;
     }
 
     /**
      * Gets the default value for {@link org.apache.tomcat.dbcp.pool2.impl.GenericObjectPool
-     * GenericObjectPool#getTestOnBorrow()} for each per user pool.
+     * GenericObjectPool#getTestWhileIdle()} for each per user pool.
      *
      * @return The default value for {@link org.apache.tomcat.dbcp.pool2.impl.GenericObjectPool
-     *         GenericObjectPool#getTestOnBorrow()} for each per user pool.
+     *         GenericObjectPool#getTestWhileIdle()} for each per user pool.
      */
-    public boolean getDefaultTestOnBorrow() {
-        return this.defaultTestOnBorrow;
+    public boolean getDefaultTestWhileIdle() {
+        return this.defaultTestWhileIdle;
     }
 
     /**
-     * Sets the default value for {@link org.apache.tomcat.dbcp.pool2.impl.GenericObjectPool
-     * GenericObjectPool#getTestOnBorrow()} for each per user pool.
+     * Gets the default value for {@link GenericObjectPool#getDurationBetweenEvictionRuns ()} for each per user pool.
      *
-     * @param testOnBorrow
-     *            The default value for {@link org.apache.tomcat.dbcp.pool2.impl.GenericObjectPool
-     *            GenericObjectPool#getTestOnBorrow()} for each per user pool.
+     * @return The default value for {@link GenericObjectPool#getDurationBetweenEvictionRuns ()} for each per user pool.
+     * @deprecated Use {@link #getDefaultDurationBetweenEvictionRuns()}.
      */
-    public void setDefaultTestOnBorrow(final boolean testOnBorrow) {
-        assertInitializationAllowed();
-        this.defaultTestOnBorrow = testOnBorrow;
+    @Deprecated
+    public long getDefaultTimeBetweenEvictionRunsMillis() {
+        return this.defaultDurationBetweenEvictionRuns.toMillis();
     }
 
     /**
-     * Gets the default value for {@link org.apache.tomcat.dbcp.pool2.impl.GenericObjectPool
-     * GenericObjectPool#getTestOnReturn()} for each per user pool.
+     * Gets the value of defaultTransactionIsolation, which defines the state of connections handed out from this pool.
+     * The value can be changed on the Connection using Connection.setTransactionIsolation(int). If this method returns
+     * -1, the default is JDBC driver dependent.
      *
-     * @return The default value for {@link org.apache.tomcat.dbcp.pool2.impl.GenericObjectPool
-     *         GenericObjectPool#getTestOnReturn()} for each per user pool.
+     * @return value of defaultTransactionIsolation.
      */
-    public boolean getDefaultTestOnReturn() {
-        return this.defaultTestOnReturn;
+    public int getDefaultTransactionIsolation() {
+        return defaultTransactionIsolation;
     }
 
     /**
-     * Sets the default value for {@link org.apache.tomcat.dbcp.pool2.impl.GenericObjectPool
-     * GenericObjectPool#getTestOnReturn()} for each per user pool.
+     * Gets the description. This property is defined by JDBC as for use with GUI (or other) tools that might deploy the
+     * datasource. It serves no internal purpose.
      *
-     * @param testOnReturn
-     *            The default value for {@link org.apache.tomcat.dbcp.pool2.impl.GenericObjectPool
-     *            GenericObjectPool#getTestOnReturn()} for each per user pool.
+     * @return value of description.
      */
-    public void setDefaultTestOnReturn(final boolean testOnReturn) {
-        assertInitializationAllowed();
-        this.defaultTestOnReturn = testOnReturn;
+    public String getDescription() {
+        return description;
     }
 
     /**
-     * Gets the default value for {@link org.apache.tomcat.dbcp.pool2.impl.GenericObjectPool
-     * GenericObjectPool#getTestWhileIdle()} for each per user pool.
+     * Gets the instance key.
      *
-     * @return The default value for {@link org.apache.tomcat.dbcp.pool2.impl.GenericObjectPool
-     *         GenericObjectPool#getTestWhileIdle()} for each per user pool.
+     * @return the instance key.
      */
-    public boolean getDefaultTestWhileIdle() {
-        return this.defaultTestWhileIdle;
+    protected String getInstanceKey() {
+        return instanceKey;
     }
 
     /**
-     * Sets the default value for {@link org.apache.tomcat.dbcp.pool2.impl.GenericObjectPool
-     * GenericObjectPool#getTestWhileIdle()} for each per user pool.
+     * Gets the value of jndiEnvironment which is used when instantiating a JNDI InitialContext. This InitialContext is
+     * used to locate the back end ConnectionPoolDataSource.
      *
-     * @param testWhileIdle
-     *            The default value for {@link org.apache.tomcat.dbcp.pool2.impl.GenericObjectPool
-     *            GenericObjectPool#getTestWhileIdle()} for each per user pool.
+     * @param key
+     *            JNDI environment key.
+     * @return value of jndiEnvironment.
      */
-    public void setDefaultTestWhileIdle(final boolean testWhileIdle) {
-        assertInitializationAllowed();
-        this.defaultTestWhileIdle = testWhileIdle;
+    public String getJndiEnvironment(final String key) {
+        String value = null;
+        if (jndiEnvironment != null) {
+            value = jndiEnvironment.getProperty(key);
+        }
+        return value;
     }
 
     /**
-     * Gets the default value for {@link org.apache.tomcat.dbcp.pool2.impl.GenericObjectPool
-     * GenericObjectPool#getTimeBetweenEvictionRunsMillis ()} for each per user pool.
+     * Gets the value of loginTimeout.
      *
-     * @return The default value for {@link org.apache.tomcat.dbcp.pool2.impl.GenericObjectPool
-     *         GenericObjectPool#getTimeBetweenEvictionRunsMillis ()} for each per user pool.
+     * @return value of loginTimeout.
+     * @deprecated Use {@link #getLoginTimeoutDuration()}.
      */
-    public long getDefaultTimeBetweenEvictionRunsMillis() {
-        return this.defaultTimeBetweenEvictionRunsMillis;
+    @Deprecated
+    @Override
+    public int getLoginTimeout() {
+        return (int) loginTimeoutDuration.getSeconds();
     }
 
     /**
-     * Sets the default value for {@link org.apache.tomcat.dbcp.pool2.impl.GenericObjectPool
-     * GenericObjectPool#getTimeBetweenEvictionRunsMillis ()} for each per user pool.
+     * Gets the value of loginTimeout.
      *
-     * @param timeBetweenEvictionRunsMillis
-     *            The default value for {@link org.apache.tomcat.dbcp.pool2.impl.GenericObjectPool
-     *            GenericObjectPool#getTimeBetweenEvictionRunsMillis ()} for each per user pool.
+     * @return value of loginTimeout.
+     * @since 2.10.0
      */
-    public void setDefaultTimeBetweenEvictionRunsMillis(final long timeBetweenEvictionRunsMillis) {
-        assertInitializationAllowed();
-        this.defaultTimeBetweenEvictionRunsMillis = timeBetweenEvictionRunsMillis;
+    public Duration getLoginTimeoutDuration() {
+        return loginTimeoutDuration;
     }
 
     /**
-     * Gets the value of connectionPoolDataSource. This method will return null, if the backing data source is being
-     * accessed via JNDI.
+     * Gets the value of logWriter.
      *
-     * @return value of connectionPoolDataSource.
+     * @return value of logWriter.
      */
-    public ConnectionPoolDataSource getConnectionPoolDataSource() {
-        return dataSource;
+    @Override
+    public PrintWriter getLogWriter() {
+        if (logWriter == null) {
+            logWriter = new PrintWriter(new OutputStreamWriter(System.out, StandardCharsets.UTF_8));
+        }
+        return logWriter;
+    }
+
+    /**
+     * Gets the maximum permitted lifetime of a connection. A value of zero or less indicates an
+     * infinite lifetime.
+     *
+     * @return The maximum permitted lifetime of a connection. A value of zero or less indicates an
+     *         infinite lifetime.
+     * @since 2.10.0
+     */
+    public Duration getMaxConnDuration() {
+        return maxConnDuration;
+    }
+
+    /**
+     * Gets the maximum permitted lifetime of a connection. A value of zero or less indicates an
+     * infinite lifetime.
+     *
+     * @return The maximum permitted lifetime of a connection. A value of zero or less indicates an
+     *         infinite lifetime.
+     * @deprecated Use {@link #getMaxConnDuration()}.
+     */
+    @Deprecated
+    public Duration getMaxConnLifetime() {
+        return maxConnDuration;
+    }
+
+    /**
+     * Gets the maximum permitted lifetime of a connection in milliseconds. A value of zero or less indicates an
+     * infinite lifetime.
+     *
+     * @return The maximum permitted lifetime of a connection in milliseconds. A value of zero or less indicates an
+     *         infinite lifetime.
+     * @deprecated Use {@link #getMaxConnLifetime()}.
+     */
+    @Deprecated
+    public long getMaxConnLifetimeMillis() {
+        return maxConnDuration.toMillis();
+    }
+
+    @Override
+    public Logger getParentLogger() throws SQLFeatureNotSupportedException {
+        throw new SQLFeatureNotSupportedException();
+    }
+
+    /**
+     * This method is protected but can only be implemented in this package because PooledConnectionAndInfo is a package
+     * private type.
+     *
+     * @param userName The user name.
+     * @param userPassword The user password.
+     * @return Matching PooledConnectionAndInfo.
+     * @throws SQLException Connection or registration failure.
+     */
+    protected abstract PooledConnectionAndInfo getPooledConnectionAndInfo(String userName, String userPassword)
+            throws SQLException;
+
+    /**
+     * Gets the SQL query that will be used to validate connections from this pool before returning them to the caller.
+     * If specified, this query <strong>MUST</strong> be an SQL SELECT statement that returns at least one row. If not
+     * specified, {@link Connection#isValid(int)} will be used to validate connections.
+     *
+     * @return The SQL query that will be used to validate connections from this pool before returning them to the
+     *         caller.
+     */
+    public String getValidationQuery() {
+        return this.validationQuery;
+    }
+
+    /**
+     * Returns the timeout in seconds before the validation query fails.
+     *
+     * @return The timeout in seconds before the validation query fails.
+     * @deprecated Use {@link #getValidationQueryTimeoutDuration()}.
+     */
+    @Deprecated
+    public int getValidationQueryTimeout() {
+        return (int) validationQueryTimeoutDuration.getSeconds();
+    }
+
+    /**
+     * Returns the timeout Duration before the validation query fails.
+     *
+     * @return The timeout Duration before the validation query fails.
+     */
+    public Duration getValidationQueryTimeoutDuration() {
+        return validationQueryTimeoutDuration;
+    }
+
+    /**
+     * Gets the value of defaultAutoCommit, which defines the state of connections handed out from this pool. The value
+     * can be changed on the Connection using Connection.setAutoCommit(boolean). The default is <code>null</code> which
+     * will use the default value for the drive.
+     *
+     * @return value of defaultAutoCommit.
+     */
+    public Boolean isDefaultAutoCommit() {
+        return defaultAutoCommit;
+    }
+
+    /**
+     * Gets the value of defaultReadOnly, which defines the state of connections handed out from this pool. The value
+     * can be changed on the Connection using Connection.setReadOnly(boolean). The default is <code>null</code> which
+     * will use the default value for the drive.
+     *
+     * @return value of defaultReadOnly.
+     */
+    public Boolean isDefaultReadOnly() {
+        return defaultReadOnly;
+    }
+
+    /**
+     * Whether a rollback will be issued after executing the SQL query that will be used to validate connections from
+     * this pool before returning them to the caller.
+     *
+     * @return true if a rollback will be issued after executing the validation query
+     */
+    public boolean isRollbackAfterValidation() {
+        return this.rollbackAfterValidation;
+    }
+
+    @Override
+    public boolean isWrapperFor(final Class<?> iface) throws SQLException {
+        return iface.isInstance(this);
     }
 
     /**
      * Sets the back end ConnectionPoolDataSource. This property should not be set if using JNDI to access the
      * data source.
      *
-     * @param v
+     * @param dataSource
      *            Value to assign to connectionPoolDataSource.
      */
-    public void setConnectionPoolDataSource(final ConnectionPoolDataSource v) {
+    public void setConnectionPoolDataSource(final ConnectionPoolDataSource dataSource) {
         assertInitializationAllowed();
         if (dataSourceName != null) {
             throw new IllegalStateException("Cannot set the DataSource, if JNDI is used.");
         }
-        if (dataSource != null) {
+        if (this.dataSource != null) {
             throw new IllegalStateException("The CPDS has already been set. It cannot be altered.");
         }
-        dataSource = v;
+        this.dataSource = dataSource;
         instanceKey = InstanceKeyDataSourceFactory.registerNewInstance(this);
     }
 
     /**
-     * Gets the name of the ConnectionPoolDataSource which backs this pool. This name is used to look up the data source
-     * from a JNDI service provider.
-     *
-     * @return value of dataSourceName.
-     */
-    public String getDataSourceName() {
-        return dataSourceName;
-    }
-
-    /**
      * Sets the name of the ConnectionPoolDataSource which backs this pool. This name is used to look up the data source
      * from a JNDI service provider.
      *
-     * @param v
+     * @param dataSourceName
      *            Value to assign to dataSourceName.
      */
-    public void setDataSourceName(final String v) {
+    public void setDataSourceName(final String dataSourceName) {
         assertInitializationAllowed();
         if (dataSource != null) {
             throw new IllegalStateException("Cannot set the JNDI name for the DataSource, if already "
                     + "set using setConnectionPoolDataSource.");
         }
-        if (dataSourceName != null) {
+        if (this.dataSourceName != null) {
             throw new IllegalStateException("The DataSourceName has already been set. " + "It cannot be altered.");
         }
-        this.dataSourceName = v;
+        this.dataSourceName = dataSourceName;
         instanceKey = InstanceKeyDataSourceFactory.registerNewInstance(this);
     }
 
     /**
-     * Gets the value of defaultAutoCommit, which defines the state of connections handed out from this pool. The value
+     * Sets the value of defaultAutoCommit, which defines the state of connections handed out from this pool. The value
      * can be changed on the Connection using Connection.setAutoCommit(boolean). The default is <code>null</code> which
      * will use the default value for the drive.
      *
-     * @return value of defaultAutoCommit.
+     * @param defaultAutoCommit
+     *            Value to assign to defaultAutoCommit.
      */
-    public Boolean isDefaultAutoCommit() {
-        return defaultAutoCommit;
+    public void setDefaultAutoCommit(final Boolean defaultAutoCommit) {
+        assertInitializationAllowed();
+        this.defaultAutoCommit = defaultAutoCommit;
     }
 
     /**
-     * Sets the value of defaultAutoCommit, which defines the state of connections handed out from this pool. The value
-     * can be changed on the Connection using Connection.setAutoCommit(boolean). The default is <code>null</code> which
-     * will use the default value for the drive.
+     * Sets the default value for {@link GenericKeyedObjectPoolConfig#getBlockWhenExhausted()} for each per user pool.
      *
-     * @param v
-     *            Value to assign to defaultAutoCommit.
+     * @param blockWhenExhausted
+     *            The default value for {@link GenericKeyedObjectPoolConfig#getBlockWhenExhausted()} for each per user
+     *            pool.
      */
-    public void setDefaultAutoCommit(final Boolean v) {
+    public void setDefaultBlockWhenExhausted(final boolean blockWhenExhausted) {
         assertInitializationAllowed();
-        this.defaultAutoCommit = v;
+        this.defaultBlockWhenExhausted = blockWhenExhausted;
     }
 
     /**
-     * Gets the value of defaultReadOnly, which defines the state of connections handed out from this pool. The value
-     * can be changed on the Connection using Connection.setReadOnly(boolean). The default is <code>null</code> which
-     * will use the default value for the drive.
+     * Sets the default value for {@link GenericObjectPool#getDurationBetweenEvictionRuns ()} for each per user pool.
      *
-     * @return value of defaultReadOnly.
+     * @param defaultDurationBetweenEvictionRuns The default value for
+     *        {@link GenericObjectPool#getDurationBetweenEvictionRuns ()} for each per user pool.
+     * @since 2.10.0
      */
-    public Boolean isDefaultReadOnly() {
-        return defaultReadOnly;
+    public void setDefaultDurationBetweenEvictionRuns(final Duration defaultDurationBetweenEvictionRuns) {
+        assertInitializationAllowed();
+        this.defaultDurationBetweenEvictionRuns = defaultDurationBetweenEvictionRuns;
+    }
+
+    /**
+     * Sets the default value for {@link GenericKeyedObjectPoolConfig#getEvictionPolicyClassName()} for each per user
+     * pool.
+     *
+     * @param evictionPolicyClassName
+     *            The default value for {@link GenericKeyedObjectPoolConfig#getEvictionPolicyClassName()} for each per
+     *            user pool.
+     */
+    public void setDefaultEvictionPolicyClassName(final String evictionPolicyClassName) {
+        assertInitializationAllowed();
+        this.defaultEvictionPolicyClassName = evictionPolicyClassName;
+    }
+
+    /**
+     * Sets the default value for {@link GenericKeyedObjectPoolConfig#getLifo()} for each per user pool.
+     *
+     * @param lifo
+     *            The default value for {@link GenericKeyedObjectPoolConfig#getLifo()} for each per user pool.
+     */
+    public void setDefaultLifo(final boolean lifo) {
+        assertInitializationAllowed();
+        this.defaultLifo = lifo;
+    }
+
+    /**
+     * Sets the default value for {@link GenericKeyedObjectPoolConfig#getMaxIdlePerKey()} for each per user pool.
+     *
+     * @param maxIdle
+     *            The default value for {@link GenericKeyedObjectPoolConfig#getMaxIdlePerKey()} for each per user pool.
+     */
+    public void setDefaultMaxIdle(final int maxIdle) {
+        assertInitializationAllowed();
+        this.defaultMaxIdle = maxIdle;
+    }
+
+    /**
+     * Sets the default value for {@link GenericKeyedObjectPoolConfig#getMaxTotalPerKey()} for each per user pool.
+     *
+     * @param maxTotal
+     *            The default value for {@link GenericKeyedObjectPoolConfig#getMaxTotalPerKey()} for each per user pool.
+     */
+    public void setDefaultMaxTotal(final int maxTotal) {
+        assertInitializationAllowed();
+        this.defaultMaxTotal = maxTotal;
+    }
+
+    /**
+     * Sets the default value for {@link GenericKeyedObjectPoolConfig#getMaxWaitDuration()} for each per user pool.
+     *
+     * @param maxWaitMillis
+     *            The default value for {@link GenericKeyedObjectPoolConfig#getMaxWaitDuration()} for each per user pool.
+     * @since 2.9.0
+     */
+    public void setDefaultMaxWait(final Duration maxWaitMillis) {
+        assertInitializationAllowed();
+        this.defaultMaxWaitDuration = maxWaitMillis;
+    }
+
+    /**
+     * Sets the default value for {@link GenericKeyedObjectPoolConfig#getMaxWaitMillis()} for each per user pool.
+     *
+     * @param maxWaitMillis
+     *            The default value for {@link GenericKeyedObjectPoolConfig#getMaxWaitMillis()} for each per user pool.
+     * @deprecated Use {@link #setDefaultMaxWait(Duration)}.
+     */
+    @Deprecated
+    public void setDefaultMaxWaitMillis(final long maxWaitMillis) {
+        setDefaultMaxWait(Duration.ofMillis(maxWaitMillis));
+    }
+
+    /**
+     * Sets the default value for {@link GenericKeyedObjectPoolConfig#getMinEvictableIdleDuration()} for each per user
+     * pool.
+     *
+     * @param defaultMinEvictableIdleDuration
+     *            The default value for {@link GenericKeyedObjectPoolConfig#getMinEvictableIdleDuration()} for each
+     *            per user pool.
+     * @since 2.10.0
+     */
+    public void setDefaultMinEvictableIdle(final Duration defaultMinEvictableIdleDuration) {
+        assertInitializationAllowed();
+        this.defaultMinEvictableIdleDuration = defaultMinEvictableIdleDuration;
+    }
+
+    /**
+     * Sets the default value for {@link GenericKeyedObjectPoolConfig#getMinEvictableIdleDuration()} for each per user
+     * pool.
+     *
+     * @param minEvictableIdleTimeMillis
+     *            The default value for {@link GenericKeyedObjectPoolConfig#getMinEvictableIdleDuration()} for each
+     *            per user pool.
+     * @deprecated Use {@link #setDefaultMinEvictableIdle(Duration)}.
+     */
+    @Deprecated
+    public void setDefaultMinEvictableIdleTimeMillis(final long minEvictableIdleTimeMillis) {
+        assertInitializationAllowed();
+        this.defaultMinEvictableIdleDuration = Duration.ofMillis(minEvictableIdleTimeMillis);
+    }
+
+    /**
+     * Sets the default value for {@link GenericKeyedObjectPoolConfig#getMinIdlePerKey()} for each per user pool.
+     *
+     * @param minIdle
+     *            The default value for {@link GenericKeyedObjectPoolConfig#getMinIdlePerKey()} for each per user pool.
+     */
+    public void setDefaultMinIdle(final int minIdle) {
+        assertInitializationAllowed();
+        this.defaultMinIdle = minIdle;
+    }
+
+    /**
+     * Sets the default value for {@link GenericKeyedObjectPoolConfig#getNumTestsPerEvictionRun()} for each per user
+     * pool.
+     *
+     * @param numTestsPerEvictionRun
+     *            The default value for {@link GenericKeyedObjectPoolConfig#getNumTestsPerEvictionRun()} for each per
+     *            user pool.
+     */
+    public void setDefaultNumTestsPerEvictionRun(final int numTestsPerEvictionRun) {
+        assertInitializationAllowed();
+        this.defaultNumTestsPerEvictionRun = numTestsPerEvictionRun;
     }
 
     /**
@@ -618,103 +907,140 @@ public abstract class InstanceKeyDataSource implements DataSource, Referenceable
      * can be changed on the Connection using Connection.setReadOnly(boolean). The default is <code>null</code> which
      * will use the default value for the drive.
      *
-     * @param v
+     * @param defaultReadOnly
      *            Value to assign to defaultReadOnly.
      */
-    public void setDefaultReadOnly(final Boolean v) {
+    public void setDefaultReadOnly(final Boolean defaultReadOnly) {
         assertInitializationAllowed();
-        this.defaultReadOnly = v;
+        this.defaultReadOnly = defaultReadOnly;
     }
 
     /**
-     * Gets the value of defaultTransactionIsolation, which defines the state of connections handed out from this pool.
-     * The value can be changed on the Connection using Connection.setTransactionIsolation(int). If this method returns
-     * -1, the default is JDBC driver dependent.
+     * Sets the default value for {@link org.apache.tomcat.dbcp.pool2.impl.GenericObjectPool
+     * GenericObjectPool#getSoftMinEvictableIdleTimeMillis()} for each per user pool.
      *
-     * @return value of defaultTransactionIsolation.
+     * @param defaultSoftMinEvictableIdleDuration
+     *            The default value for {@link org.apache.tomcat.dbcp.pool2.impl.GenericObjectPool
+     *            GenericObjectPool#getSoftMinEvictableIdleTimeMillis()} for each per user pool.
+     * @since 2.10.0
      */
-    public int getDefaultTransactionIsolation() {
-        return defaultTransactionIsolation;
+    public void setDefaultSoftMinEvictableIdle(final Duration defaultSoftMinEvictableIdleDuration) {
+        assertInitializationAllowed();
+        this.defaultSoftMinEvictableIdleDuration = defaultSoftMinEvictableIdleDuration;
     }
 
     /**
-     * Sets the value of defaultTransactionIsolation, which defines the state of connections handed out from this pool.
-     * The value can be changed on the Connection using Connection.setTransactionIsolation(int). The default is JDBC
-     * driver dependent.
+     * Sets the default value for {@link org.apache.tomcat.dbcp.pool2.impl.GenericObjectPool
+     * GenericObjectPool#getSoftMinEvictableIdleTimeMillis()} for each per user pool.
      *
-     * @param v
-     *            Value to assign to defaultTransactionIsolation
+     * @param softMinEvictableIdleTimeMillis
+     *            The default value for {@link org.apache.tomcat.dbcp.pool2.impl.GenericObjectPool
+     *            GenericObjectPool#getSoftMinEvictableIdleTimeMillis()} for each per user pool.
+     * @deprecated Use {@link #setDefaultSoftMinEvictableIdle(Duration)}.
      */
-    public void setDefaultTransactionIsolation(final int v) {
+    @Deprecated
+    public void setDefaultSoftMinEvictableIdleTimeMillis(final long softMinEvictableIdleTimeMillis) {
         assertInitializationAllowed();
-        switch (v) {
-        case Connection.TRANSACTION_NONE:
-        case Connection.TRANSACTION_READ_COMMITTED:
-        case Connection.TRANSACTION_READ_UNCOMMITTED:
-        case Connection.TRANSACTION_REPEATABLE_READ:
-        case Connection.TRANSACTION_SERIALIZABLE:
-            break;
-        default:
-            throw new IllegalArgumentException(BAD_TRANSACTION_ISOLATION);
-        }
-        this.defaultTransactionIsolation = v;
+        this.defaultSoftMinEvictableIdleDuration = Duration.ofMillis(softMinEvictableIdleTimeMillis);
     }
 
     /**
-     * Gets the description. This property is defined by JDBC as for use with GUI (or other) tools that might deploy the
-     * datasource. It serves no internal purpose.
+     * Sets the default value for {@link org.apache.tomcat.dbcp.pool2.impl.GenericObjectPool
+     * GenericObjectPool#getTestOnBorrow()} for each per user pool.
      *
-     * @return value of description.
+     * @param testOnBorrow
+     *            The default value for {@link org.apache.tomcat.dbcp.pool2.impl.GenericObjectPool
+     *            GenericObjectPool#getTestOnBorrow()} for each per user pool.
      */
-    public String getDescription() {
-        return description;
+    public void setDefaultTestOnBorrow(final boolean testOnBorrow) {
+        assertInitializationAllowed();
+        this.defaultTestOnBorrow = testOnBorrow;
     }
 
     /**
-     * Sets the description. This property is defined by JDBC as for use with GUI (or other) tools that might deploy the
-     * datasource. It serves no internal purpose.
+     * Sets the default value for {@link org.apache.tomcat.dbcp.pool2.impl.GenericObjectPool
+     * GenericObjectPool#getTestOnCreate()} for each per user pool.
      *
-     * @param v
-     *            Value to assign to description.
+     * @param testOnCreate
+     *            The default value for {@link org.apache.tomcat.dbcp.pool2.impl.GenericObjectPool
+     *            GenericObjectPool#getTestOnCreate()} for each per user pool.
+     */
+    public void setDefaultTestOnCreate(final boolean testOnCreate) {
+        assertInitializationAllowed();
+        this.defaultTestOnCreate = testOnCreate;
+    }
+
+    /**
+     * Sets the default value for {@link org.apache.tomcat.dbcp.pool2.impl.GenericObjectPool
+     * GenericObjectPool#getTestOnReturn()} for each per user pool.
+     *
+     * @param testOnReturn
+     *            The default value for {@link org.apache.tomcat.dbcp.pool2.impl.GenericObjectPool
+     *            GenericObjectPool#getTestOnReturn()} for each per user pool.
      */
-    public void setDescription(final String v) {
-        this.description = v;
+    public void setDefaultTestOnReturn(final boolean testOnReturn) {
+        assertInitializationAllowed();
+        this.defaultTestOnReturn = testOnReturn;
     }
 
-    protected String getInstanceKey() {
-        return instanceKey;
+    /**
+     * Sets the default value for {@link org.apache.tomcat.dbcp.pool2.impl.GenericObjectPool
+     * GenericObjectPool#getTestWhileIdle()} for each per user pool.
+     *
+     * @param testWhileIdle
+     *            The default value for {@link org.apache.tomcat.dbcp.pool2.impl.GenericObjectPool
+     *            GenericObjectPool#getTestWhileIdle()} for each per user pool.
+     */
+    public void setDefaultTestWhileIdle(final boolean testWhileIdle) {
+        assertInitializationAllowed();
+        this.defaultTestWhileIdle = testWhileIdle;
     }
 
     /**
-     * Gets the value of jndiEnvironment which is used when instantiating a JNDI InitialContext. This InitialContext is
-     * used to locate the back end ConnectionPoolDataSource.
+     * Sets the default value for {@link GenericObjectPool#getDurationBetweenEvictionRuns()} for each per user pool.
      *
-     * @param key
-     *            JNDI environment key.
-     * @return value of jndiEnvironment.
+     * @param timeBetweenEvictionRunsMillis The default value for
+     *        {@link GenericObjectPool#getDurationBetweenEvictionRuns()} for each per user pool.
+     * @deprecated Use {@link #setDefaultDurationBetweenEvictionRuns(Duration)}.
      */
-    public String getJndiEnvironment(final String key) {
-        String value = null;
-        if (jndiEnvironment != null) {
-            value = jndiEnvironment.getProperty(key);
+    @Deprecated
+    public void setDefaultTimeBetweenEvictionRunsMillis(final long timeBetweenEvictionRunsMillis) {
+        assertInitializationAllowed();
+        this.defaultDurationBetweenEvictionRuns = Duration.ofMillis(timeBetweenEvictionRunsMillis);
+    }
+
+    /**
+     * Sets the value of defaultTransactionIsolation, which defines the state of connections handed out from this pool.
+     * The value can be changed on the Connection using Connection.setTransactionIsolation(int). The default is JDBC
+     * driver dependent.
+     *
+     * @param defaultTransactionIsolation
+     *            Value to assign to defaultTransactionIsolation
+     */
+    public void setDefaultTransactionIsolation(final int defaultTransactionIsolation) {
+        assertInitializationAllowed();
+        switch (defaultTransactionIsolation) {
+        case Connection.TRANSACTION_NONE:
+        case Connection.TRANSACTION_READ_COMMITTED:
+        case Connection.TRANSACTION_READ_UNCOMMITTED:
+        case Connection.TRANSACTION_REPEATABLE_READ:
+        case Connection.TRANSACTION_SERIALIZABLE:
+            break;
+        default:
+            throw new IllegalArgumentException(BAD_TRANSACTION_ISOLATION);
         }
-        return value;
+        this.defaultTransactionIsolation = defaultTransactionIsolation;
     }
 
     /**
-     * Sets the value of the given JNDI environment property to be used when instantiating a JNDI InitialContext. This
-     * InitialContext is used to locate the back end ConnectionPoolDataSource.
+     * Sets the description. This property is defined by JDBC as for use with GUI (or other) tools that might deploy the
+     * datasource. It serves no internal purpose.
      *
-     * @param key
-     *            the JNDI environment property to set.
-     * @param value
-     *            the value assigned to specified JNDI environment property.
+     * @param description
+     *            Value to assign to description.
      */
-    public void setJndiEnvironment(final String key, final String value) {
-        if (jndiEnvironment == null) {
-            jndiEnvironment = new Properties();
-        }
-        jndiEnvironment.setProperty(key, value);
+    public void setDescription(final String description) {
+        this.description = description;
     }
 
     /**
@@ -734,103 +1060,95 @@ public abstract class InstanceKeyDataSource implements DataSource, Referenceable
     }
 
     /**
-     * Gets the value of loginTimeout.
+     * Sets the value of the given JNDI environment property to be used when instantiating a JNDI InitialContext. This
+     * InitialContext is used to locate the back end ConnectionPoolDataSource.
      *
-     * @return value of loginTimeout.
+     * @param key
+     *            the JNDI environment property to set.
+     * @param value
+     *            the value assigned to specified JNDI environment property.
      */
-    @Override
-    public int getLoginTimeout() {
-        return loginTimeout;
+    public void setJndiEnvironment(final String key, final String value) {
+        if (jndiEnvironment == null) {
+            jndiEnvironment = new Properties();
+        }
+        jndiEnvironment.setProperty(key, value);
     }
 
     /**
      * Sets the value of loginTimeout.
      *
-     * @param v
+     * @param loginTimeout
      *            Value to assign to loginTimeout.
+     * @since 2.10.0
      */
-    @Override
-    public void setLoginTimeout(final int v) {
-        this.loginTimeout = v;
+    public void setLoginTimeout(final Duration loginTimeout) {
+        this.loginTimeoutDuration = loginTimeout;
     }
 
     /**
-     * Gets the value of logWriter.
+     * Sets the value of loginTimeout.
      *
-     * @return value of logWriter.
+     * @param loginTimeout
+     *            Value to assign to loginTimeout.
+     * @deprecated Use {@link #setLoginTimeout(Duration)}.
      */
+    @Deprecated
     @Override
-    public PrintWriter getLogWriter() {
-        if (logWriter == null) {
-            logWriter = new PrintWriter(new OutputStreamWriter(System.out, StandardCharsets.UTF_8));
-        }
-        return logWriter;
+    public void setLoginTimeout(final int loginTimeout) {
+        this.loginTimeoutDuration = Duration.ofSeconds(loginTimeout);
     }
 
     /**
      * Sets the value of logWriter.
      *
-     * @param v
+     * @param logWriter
      *            Value to assign to logWriter.
      */
     @Override
-    public void setLogWriter(final PrintWriter v) {
-        this.logWriter = v;
-    }
-
-    /**
-     * Gets the SQL query that will be used to validate connections from this pool before returning them to the caller.
-     * If specified, this query <strong>MUST</strong> be an SQL SELECT statement that returns at least one row. If not
-     * specified, {@link Connection#isValid(int)} will be used to validate connections.
-     *
-     * @return The SQL query that will be used to validate connections from this pool before returning them to the
-     *         caller.
-     */
-    public String getValidationQuery() {
-        return this.validationQuery;
-    }
-
-    /**
-     * Sets the SQL query that will be used to validate connections from this pool before returning them to the caller.
-     * If specified, this query <strong>MUST</strong> be an SQL SELECT statement that returns at least one row. If not
-     * specified, connections will be validated using {@link Connection#isValid(int)}.
-     *
-     * @param validationQuery
-     *            The SQL query that will be used to validate connections from this pool before returning them to the
-     *            caller.
-     */
-    public void setValidationQuery(final String validationQuery) {
-        assertInitializationAllowed();
-        this.validationQuery = validationQuery;
-    }
-
-    /**
-     * Returns the timeout in seconds before the validation query fails.
-     *
-     * @return The timeout in seconds before the validation query fails.
-     */
-    public int getValidationQueryTimeout() {
-        return validationQueryTimeoutSeconds;
+    public void setLogWriter(final PrintWriter logWriter) {
+        this.logWriter = logWriter;
     }
 
     /**
-     * Sets the timeout in seconds before the validation query fails.
+     * <p>
+     * Sets the maximum permitted lifetime of a connection. A value of zero or less indicates an
+     * infinite lifetime.
+     * </p>
+     * <p>
+     * Note: this method currently has no effect once the pool has been initialized. The pool is initialized the first
+     * time one of the following methods is invoked: <code>getConnection, setLogwriter,
+     * setLoginTimeout, getLoginTimeout, getLogWriter.</code>
+     * </p>
      *
-     * @param validationQueryTimeoutSeconds
-     *            The new timeout in seconds
+     * @param maxConnLifetimeMillis
+     *            The maximum permitted lifetime of a connection. A value of zero or less indicates an
+     *            infinite lifetime.
+     * @since 2.9.0
      */
-    public void setValidationQueryTimeout(final int validationQueryTimeoutSeconds) {
-        this.validationQueryTimeoutSeconds = validationQueryTimeoutSeconds;
+    public void setMaxConnLifetime(final Duration maxConnLifetimeMillis) {
+        this.maxConnDuration = maxConnLifetimeMillis;
     }
 
     /**
-     * Whether a rollback will be issued after executing the SQL query that will be used to validate connections from
-     * this pool before returning them to the caller.
+     * <p>
+     * Sets the maximum permitted lifetime of a connection in milliseconds. A value of zero or less indicates an
+     * infinite lifetime.
+     * </p>
+     * <p>
+     * Note: this method currently has no effect once the pool has been initialized. The pool is initialized the first
+     * time one of the following methods is invoked: <code>getConnection, setLogwriter,
+     * setLoginTimeout, getLoginTimeout, getLogWriter.</code>
+     * </p>
      *
-     * @return true if a rollback will be issued after executing the validation query
+     * @param maxConnLifetimeMillis
+     *            The maximum permitted lifetime of a connection in milliseconds. A value of zero or less indicates an
+     *            infinite lifetime.
+     * @deprecated Use {@link #setMaxConnLifetime(Duration)}.
      */
-    public boolean isRollbackAfterValidation() {
-        return this.rollbackAfterValidation;
+    @Deprecated
+    public void setMaxConnLifetimeMillis(final long maxConnLifetimeMillis) {
+        setMaxConnLifetime(Duration.ofMillis(maxConnLifetimeMillis));
     }
 
     /**
@@ -846,157 +1164,42 @@ public abstract class InstanceKeyDataSource implements DataSource, Referenceable
         this.rollbackAfterValidation = rollbackAfterValidation;
     }
 
-    /**
-     * Returns the maximum permitted lifetime of a connection in milliseconds. A value of zero or less indicates an
-     * infinite lifetime.
-     *
-     * @return The maximum permitted lifetime of a connection in milliseconds. A value of zero or less indicates an
-     *         infinite lifetime.
-     */
-    public long getMaxConnLifetimeMillis() {
-        return maxConnLifetimeMillis;
-    }
+    protected abstract void setupDefaults(Connection connection, String userName) throws SQLException;
 
     /**
-     * <p>
-     * Sets the maximum permitted lifetime of a connection in milliseconds. A value of zero or less indicates an
-     * infinite lifetime.
-     * </p>
-     * <p>
-     * Note: this method currently has no effect once the pool has been initialized. The pool is initialized the first
-     * time one of the following methods is invoked: <code>getConnection, setLogwriter,
-     * setLoginTimeout, getLoginTimeout, getLogWriter.</code>
-     * </p>
+     * Sets the SQL query that will be used to validate connections from this pool before returning them to the caller.
+     * If specified, this query <strong>MUST</strong> be an SQL SELECT statement that returns at least one row. If not
+     * specified, connections will be validated using {@link Connection#isValid(int)}.
      *
-     * @param maxConnLifetimeMillis
-     *            The maximum permitted lifetime of a connection in milliseconds. A value of zero or less indicates an
-     *            infinite lifetime.
+     * @param validationQuery
+     *            The SQL query that will be used to validate connections from this pool before returning them to the
+     *            caller.
      */
-    public void setMaxConnLifetimeMillis(final long maxConnLifetimeMillis) {
-        this.maxConnLifetimeMillis = maxConnLifetimeMillis;
+    public void setValidationQuery(final String validationQuery) {
+        assertInitializationAllowed();
+        this.validationQuery = validationQuery;
     }
 
-    // ----------------------------------------------------------------------
-    // Instrumentation Methods
-
-    // ----------------------------------------------------------------------
-    // DataSource implementation
-
     /**
-     * Attempts to establish a database connection.
+     * Sets the timeout duration before the validation query fails.
+     *
+     * @param validationQueryTimeoutDuration
+     *            The new timeout duration.
      */
-    @Override
-    public Connection getConnection() throws SQLException {
-        return getConnection(null, null);
+    public void setValidationQueryTimeout(final Duration validationQueryTimeoutDuration) {
+        this.validationQueryTimeoutDuration = validationQueryTimeoutDuration;
     }
 
     /**
-     * Attempts to retrieve a database connection using {@link #getPooledConnectionAndInfo(String, String)} with the
-     * provided user name and password. The password on the {@link PooledConnectionAndInfo} instance returned by
-     * <code>getPooledConnectionAndInfo</code> is compared to the <code>password</code> parameter. If the comparison
-     * fails, a database connection using the supplied user name and password is attempted. If the connection attempt
-     * fails, an SQLException is thrown, indicating that the given password did not match the password used to create
-     * the pooled connection. If the connection attempt succeeds, this means that the database password has been
-     * changed. In this case, the <code>PooledConnectionAndInfo</code> instance retrieved with the old password is
-     * destroyed and the <code>getPooledConnectionAndInfo</code> is repeatedly invoked until a
-     * <code>PooledConnectionAndInfo</code> instance with the new password is returned.
+     * Sets the timeout in seconds before the validation query fails.
+     *
+     * @param validationQueryTimeoutSeconds
+     *            The new timeout in seconds
+     * @deprecated Use {@link #setValidationQueryTimeout(Duration)}.
      */
-    @Override
-    public Connection getConnection(final String userName, final String userPassword) throws SQLException {
-        if (instanceKey == null) {
-            throw new SQLException("Must set the ConnectionPoolDataSource "
-                    + "through setDataSourceName or setConnectionPoolDataSource" + " before calling getConnection.");
-        }
-        getConnectionCalled = true;
-        PooledConnectionAndInfo info = null;
-        try {
-            info = getPooledConnectionAndInfo(userName, userPassword);
-        } catch (final RuntimeException | SQLException e) {
-            closeDueToException(info);
-            throw e;
-        } catch (final Exception e) {
-            closeDueToException(info);
-            throw new SQLException("Cannot borrow connection from pool", e);
-        }
-
-        // Password on PooledConnectionAndInfo does not match
-        if (!(null == userPassword ? null == info.getPassword() : userPassword.equals(info.getPassword()))) {
-            try { // See if password has changed by attempting connection
-                testCPDS(userName, userPassword);
-            } catch (final SQLException ex) {
-                // Password has not changed, so refuse client, but return connection to the pool
-                closeDueToException(info);
-                throw new SQLException(
-                        "Given password did not match password used" + " to create the PooledConnection.", ex);
-            } catch (final javax.naming.NamingException ne) {
-                throw new SQLException("NamingException encountered connecting to database", ne);
-            }
-            /*
-             * Password must have changed -> destroy connection and keep retrying until we get a new, good one,
-             * destroying any idle connections with the old password as we pull them from the pool.
-             */
-            final UserPassKey upkey = info.getUserPassKey();
-            final PooledConnectionManager manager = getConnectionManager(upkey);
-            // Destroy and remove from pool
-            manager.invalidate(info.getPooledConnection());
-            // Reset the password on the factory if using CPDSConnectionFactory
-            manager.setPassword(upkey.getPassword());
-            info = null;
-            for (int i = 0; i < 10; i++) { // Bound the number of retries - only needed if bad instances return
-                try {
-                    info = getPooledConnectionAndInfo(userName, userPassword);
-                } catch (final RuntimeException | SQLException e) {
-                    closeDueToException(info);
-                    throw e;
-                } catch (final Exception e) {
-                    closeDueToException(info);
-                    throw new SQLException("Cannot borrow connection from pool", e);
-                }
-                if (info != null && userPassword != null && userPassword.equals(info.getPassword())) {
-                    break;
-                }
-                if (info != null) {
-                    manager.invalidate(info.getPooledConnection());
-                }
-                info = null;
-            }
-            if (info == null) {
-                throw new SQLException("Cannot borrow connection from pool - password change failure.");
-            }
-        }
-
-        final Connection con = info.getPooledConnection().getConnection();
-        try {
-            setupDefaults(con, userName);
-            con.clearWarnings();
-            return con;
-        } catch (final SQLException ex) {
-            try {
-                con.close();
-            } catch (final Exception exc) {
-                getLogWriter().println("ignoring exception during close: " + exc);
-            }
-            throw ex;
-        }
-    }
-
-    protected abstract PooledConnectionAndInfo getPooledConnectionAndInfo(String userName, String userPassword)
-            throws SQLException;
-
-    protected abstract void setupDefaults(Connection connection, String userName) throws SQLException;
-
-    private void closeDueToException(final PooledConnectionAndInfo info) {
-        if (info != null) {
-            try {
-                info.getPooledConnection().getConnection().close();
-            } catch (final Exception e) {
-                // do not throw this exception because we are in the middle
-                // of handling another exception. But record it because
-                // it potentially leaks connections from the pool.
-                getLogWriter().println("[ERROR] Could not return connection to " + "pool during exception handling. "
-                        + e.getMessage());
-            }
-        }
+    @Deprecated
+    public void setValidationQueryTimeout(final int validationQueryTimeoutSeconds) {
+        this.validationQueryTimeoutDuration = Duration.ofSeconds(validationQueryTimeoutSeconds);
     }
 
     protected ConnectionPoolDataSource testCPDS(final String userName, final String userPassword)
@@ -1011,12 +1214,11 @@ public abstract class InstanceKeyDataSource implements DataSource, Referenceable
                 ctx = new InitialContext(jndiEnvironment);
             }
             final Object ds = ctx.lookup(dataSourceName);
-            if (ds instanceof ConnectionPoolDataSource) {
-                cpds = (ConnectionPoolDataSource) ds;
-            } else {
+            if (!(ds instanceof ConnectionPoolDataSource)) {
                 throw new SQLException("Illegal configuration: " + "DataSource " + dataSourceName + " ("
                         + ds.getClass().getName() + ")" + " doesn't implement javax.sql.ConnectionPoolDataSource");
             }
+            cpds = (ConnectionPoolDataSource) ds;
         }
 
         // try to get a connection with the supplied userName/password
@@ -1065,8 +1267,8 @@ public abstract class InstanceKeyDataSource implements DataSource, Referenceable
         builder.append(description);
         builder.append(", jndiEnvironment=");
         builder.append(jndiEnvironment);
-        builder.append(", loginTimeout=");
-        builder.append(loginTimeout);
+        builder.append(", loginTimeoutDuration=");
+        builder.append(loginTimeoutDuration);
         builder.append(", logWriter=");
         builder.append(logWriter);
         builder.append(", instanceKey=");
@@ -1081,16 +1283,16 @@ public abstract class InstanceKeyDataSource implements DataSource, Referenceable
         builder.append(defaultMaxIdle);
         builder.append(", defaultMaxTotal=");
         builder.append(defaultMaxTotal);
-        builder.append(", defaultMaxWaitMillis=");
-        builder.append(defaultMaxWaitMillis);
-        builder.append(", defaultMinEvictableIdleTimeMillis=");
-        builder.append(defaultMinEvictableIdleTimeMillis);
+        builder.append(", defaultMaxWaitDuration=");
+        builder.append(defaultMaxWaitDuration);
+        builder.append(", defaultMinEvictableIdleDuration=");
+        builder.append(defaultMinEvictableIdleDuration);
         builder.append(", defaultMinIdle=");
         builder.append(defaultMinIdle);
         builder.append(", defaultNumTestsPerEvictionRun=");
         builder.append(defaultNumTestsPerEvictionRun);
-        builder.append(", defaultSoftMinEvictableIdleTimeMillis=");
-        builder.append(defaultSoftMinEvictableIdleTimeMillis);
+        builder.append(", defaultSoftMinEvictableIdleDuration=");
+        builder.append(defaultSoftMinEvictableIdleDuration);
         builder.append(", defaultTestOnCreate=");
         builder.append(defaultTestOnCreate);
         builder.append(", defaultTestOnBorrow=");
@@ -1099,16 +1301,16 @@ public abstract class InstanceKeyDataSource implements DataSource, Referenceable
         builder.append(defaultTestOnReturn);
         builder.append(", defaultTestWhileIdle=");
         builder.append(defaultTestWhileIdle);
-        builder.append(", defaultTimeBetweenEvictionRunsMillis=");
-        builder.append(defaultTimeBetweenEvictionRunsMillis);
+        builder.append(", defaultDurationBetweenEvictionRuns=");
+        builder.append(defaultDurationBetweenEvictionRuns);
         builder.append(", validationQuery=");
         builder.append(validationQuery);
-        builder.append(", validationQueryTimeoutSeconds=");
-        builder.append(validationQueryTimeoutSeconds);
+        builder.append(", validationQueryTimeoutDuration=");
+        builder.append(validationQueryTimeoutDuration);
         builder.append(", rollbackAfterValidation=");
         builder.append(rollbackAfterValidation);
-        builder.append(", maxConnLifetimeMillis=");
-        builder.append(maxConnLifetimeMillis);
+        builder.append(", maxConnDuration=");
+        builder.append(maxConnDuration);
         builder.append(", defaultAutoCommit=");
         builder.append(defaultAutoCommit);
         builder.append(", defaultTransactionIsolation=");
@@ -1116,4 +1318,14 @@ public abstract class InstanceKeyDataSource implements DataSource, Referenceable
         builder.append(", defaultReadOnly=");
         builder.append(defaultReadOnly);
     }
+
+    @Override
+    @SuppressWarnings("unchecked")
+    public <T> T unwrap(final Class<T> iface) throws SQLException {
+        if (isWrapperFor(iface)) {
+            return (T) this;
+        }
+        throw new SQLException(this + " is not a wrapper for " + iface);
+    }
+    /* JDBC_4_ANT_KEY_END */
 }
diff --git a/java/org/apache/tomcat/dbcp/dbcp2/datasources/InstanceKeyDataSourceFactory.java b/java/org/apache/tomcat/dbcp/dbcp2/datasources/InstanceKeyDataSourceFactory.java
index defa46a..ad01b8a 100644
--- a/java/org/apache/tomcat/dbcp/dbcp2/datasources/InstanceKeyDataSourceFactory.java
+++ b/java/org/apache/tomcat/dbcp/dbcp2/datasources/InstanceKeyDataSourceFactory.java
@@ -19,6 +19,7 @@ package org.apache.tomcat.dbcp.dbcp2.datasources;
 import java.io.ByteArrayInputStream;
 import java.io.IOException;
 import java.io.ObjectInputStream;
+import java.time.Duration;
 import java.util.ArrayList;
 import java.util.Hashtable;
 import java.util.List;
@@ -34,6 +35,7 @@ import javax.naming.Reference;
 import javax.naming.spi.ObjectFactory;
 
 import org.apache.tomcat.dbcp.dbcp2.ListException;
+import org.apache.tomcat.dbcp.dbcp2.Utils;
 
 /**
  * A JNDI ObjectFactory which creates <code>SharedPoolDataSource</code>s or <code>PerUserPoolDataSource</code>s
@@ -42,31 +44,7 @@ import org.apache.tomcat.dbcp.dbcp2.ListException;
  */
 abstract class InstanceKeyDataSourceFactory implements ObjectFactory {
 
-    private static final Map<String, InstanceKeyDataSource> instanceMap = new ConcurrentHashMap<>();
-
-    static synchronized String registerNewInstance(final InstanceKeyDataSource ds) {
-        int max = 0;
-        for (final String s : instanceMap.keySet()) {
-            if (s != null) {
-                try {
-                    max = Math.max(max, Integer.parseInt(s));
-                } catch (final NumberFormatException e) {
-                    // no sweat, ignore those keys
-                }
-            }
-        }
-        final String instanceKey = String.valueOf(max + 1);
-        // Put a placeholder here for now, so other instances will not
-        // take our key. We will replace with a pool when ready.
-        instanceMap.put(instanceKey, ds);
-        return instanceKey;
-    }
-
-    static void removeInstance(final String key) {
-        if (key != null) {
-            instanceMap.remove(key);
-        }
-    }
+    private static final Map<String, InstanceKeyDataSource> INSTANCE_MAP = new ConcurrentHashMap<>();
 
     /**
      * Closes all pools associated with this class.
@@ -80,8 +58,8 @@ abstract class InstanceKeyDataSourceFactory implements ObjectFactory {
      */
     public static void closeAll() throws Exception {
         // Get iterator to loop over all instances of this data source.
-        final List<Throwable> exceptionList = new ArrayList<>(instanceMap.size());
-        for (final Entry<String, InstanceKeyDataSource> next : instanceMap.entrySet()) {
+        final List<Throwable> exceptionList = new ArrayList<>(INSTANCE_MAP.size());
+        for (final Entry<String, InstanceKeyDataSource> next : INSTANCE_MAP.entrySet()) {
             // Bullet-proof to avoid anything else but problems from InstanceKeyDataSource#close().
             if (next != null) {
                 final InstanceKeyDataSource value = next.getValue();
@@ -94,13 +72,75 @@ abstract class InstanceKeyDataSourceFactory implements ObjectFactory {
                 }
             }
         }
-        instanceMap.clear();
+        INSTANCE_MAP.clear();
         if (!exceptionList.isEmpty()) {
             throw new ListException("Could not close all InstanceKeyDataSource instances.", exceptionList);
         }
     }
 
     /**
+     * Deserializes the provided byte array to create an object.
+     *
+     * @param data
+     *            Data to deserialize to create the configuration parameter.
+     *
+     * @return The Object created by deserializing the data.
+     *
+     * @throws ClassNotFoundException
+     *            If a class cannot be found during the deserialization of a configuration parameter.
+     * @throws IOException
+     *            If an I/O error occurs during the deserialization of a configuration parameter.
+     */
+    protected static final Object deserialize(final byte[] data) throws IOException, ClassNotFoundException {
+        ObjectInputStream in = null;
+        try {
+            in = new ObjectInputStream(new ByteArrayInputStream(data));
+            return in.readObject();
+        } finally {
+            Utils.closeQuietly(in);
+        }
+    }
+
+    static synchronized String registerNewInstance(final InstanceKeyDataSource ds) {
+        int max = 0;
+        for (final String s : INSTANCE_MAP.keySet()) {
+            if (s != null) {
+                try {
+                    max = Math.max(max, Integer.parseInt(s));
+                } catch (final NumberFormatException e) {
+                    // no sweat, ignore those keys
+                }
+            }
+        }
+        final String instanceKey = String.valueOf(max + 1);
+        // Put a placeholder here for now, so other instances will not
+        // take our key. We will replace with a pool when ready.
+        INSTANCE_MAP.put(instanceKey, ds);
+        return instanceKey;
+    }
+
+    static void removeInstance(final String key) {
+        if (key != null) {
+            INSTANCE_MAP.remove(key);
+        }
+    }
+
+    /**
+     * Creates an instance of the subclass and sets any properties contained in the Reference.
+     *
+     * @param ref
+     *            The properties to be set on the created DataSource
+     *
+     * @return A configured DataSource of the appropriate type.
+     *
+     * @throws ClassNotFoundException
+     *            If a class cannot be found during the deserialization of a configuration parameter.
+     * @throws IOException
+     *            If an I/O error occurs during the deserialization of a configuration parameter.
+     */
+    protected abstract InstanceKeyDataSource getNewInstance(Reference ref) throws IOException, ClassNotFoundException;
+
+    /**
      * Implements ObjectFactory to create an instance of SharedPoolDataSource or PerUserPoolDataSource
      */
     @Override
@@ -115,7 +155,7 @@ abstract class InstanceKeyDataSourceFactory implements ObjectFactory {
                 final RefAddr refAddr = ref.get("instanceKey");
                 if (refAddr != null && refAddr.getContent() != null) {
                     // object was bound to JNDI via Referenceable API.
-                    obj = instanceMap.get(refAddr.getContent());
+                    obj = INSTANCE_MAP.get(refAddr.getContent());
                 } else {
                     // Tomcat JNDI creates a Reference out of server.xml
                     // <ResourceParam> configuration and passes it to an
@@ -123,14 +163,14 @@ abstract class InstanceKeyDataSourceFactory implements ObjectFactory {
                     String key = null;
                     if (name != null) {
                         key = name.toString();
-                        obj = instanceMap.get(key);
+                        obj = INSTANCE_MAP.get(key);
                     }
                     if (obj == null) {
                         final InstanceKeyDataSource ds = getNewInstance(ref);
                         setCommonProperties(ref, ds);
                         obj = ds;
                         if (key != null) {
-                            instanceMap.put(key, ds);
+                            INSTANCE_MAP.put(key, ds);
                         }
                     }
                 }
@@ -139,17 +179,39 @@ abstract class InstanceKeyDataSourceFactory implements ObjectFactory {
         return obj;
     }
 
+    /**
+     * Tests if className is the value returned from getClass().getName().toString().
+     *
+     * @param className
+     *            The class name to test.
+     *
+     * @return true if and only if className is the value returned from getClass().getName().toString()
+     */
+    protected abstract boolean isCorrectClass(String className);
+
+    boolean parseBoolean(final RefAddr refAddr) {
+        return Boolean.parseBoolean(toString(refAddr));
+    }
+
+    int parseInt(final RefAddr refAddr) {
+        return Integer.parseInt(toString(refAddr));
+    }
+
+    long parseLong(final RefAddr refAddr) {
+        return Long.parseLong(toString(refAddr));
+    }
+
     private void setCommonProperties(final Reference ref, final InstanceKeyDataSource ikds)
             throws IOException, ClassNotFoundException {
 
         RefAddr refAddr = ref.get("dataSourceName");
         if (refAddr != null && refAddr.getContent() != null) {
-            ikds.setDataSourceName(refAddr.getContent().toString());
+            ikds.setDataSourceName(toString(refAddr));
         }
 
         refAddr = ref.get("description");
         if (refAddr != null && refAddr.getContent() != null) {
-            ikds.setDescription(refAddr.getContent().toString());
+            ikds.setDescription(toString(refAddr));
         }
 
         refAddr = ref.get("jndiEnvironment");
@@ -160,175 +222,127 @@ abstract class InstanceKeyDataSourceFactory implements ObjectFactory {
 
         refAddr = ref.get("loginTimeout");
         if (refAddr != null && refAddr.getContent() != null) {
-            ikds.setLoginTimeout(Integer.parseInt(refAddr.getContent().toString()));
+            ikds.setLoginTimeout(Duration.ofSeconds(parseInt(refAddr)));
         }
 
         // Pool properties
         refAddr = ref.get("blockWhenExhausted");
         if (refAddr != null && refAddr.getContent() != null) {
-            ikds.setDefaultBlockWhenExhausted(Boolean.parseBoolean(refAddr.getContent().toString()));
+            ikds.setDefaultBlockWhenExhausted(parseBoolean(refAddr));
         }
 
         refAddr = ref.get("evictionPolicyClassName");
         if (refAddr != null && refAddr.getContent() != null) {
-            ikds.setDefaultEvictionPolicyClassName(refAddr.getContent().toString());
+            ikds.setDefaultEvictionPolicyClassName(toString(refAddr));
         }
 
         // Pool properties
         refAddr = ref.get("lifo");
         if (refAddr != null && refAddr.getContent() != null) {
-            ikds.setDefaultLifo(Boolean.parseBoolean(refAddr.getContent().toString()));
+            ikds.setDefaultLifo(parseBoolean(refAddr));
         }
 
         refAddr = ref.get("maxIdlePerKey");
         if (refAddr != null && refAddr.getContent() != null) {
-            ikds.setDefaultMaxIdle(Integer.parseInt(refAddr.getContent().toString()));
+            ikds.setDefaultMaxIdle(parseInt(refAddr));
         }
 
         refAddr = ref.get("maxTotalPerKey");
         if (refAddr != null && refAddr.getContent() != null) {
-            ikds.setDefaultMaxTotal(Integer.parseInt(refAddr.getContent().toString()));
+            ikds.setDefaultMaxTotal(parseInt(refAddr));
         }
 
         refAddr = ref.get("maxWaitMillis");
         if (refAddr != null && refAddr.getContent() != null) {
-            ikds.setDefaultMaxWaitMillis(Long.parseLong(refAddr.getContent().toString()));
+            ikds.setDefaultMaxWait(Duration.ofMillis(parseLong(refAddr)));
         }
 
         refAddr = ref.get("minEvictableIdleTimeMillis");
         if (refAddr != null && refAddr.getContent() != null) {
-            ikds.setDefaultMinEvictableIdleTimeMillis(Long.parseLong(refAddr.getContent().toString()));
+            ikds.setDefaultMinEvictableIdle(Duration.ofMillis(parseLong(refAddr)));
         }
 
         refAddr = ref.get("minIdlePerKey");
         if (refAddr != null && refAddr.getContent() != null) {
-            ikds.setDefaultMinIdle(Integer.parseInt(refAddr.getContent().toString()));
+            ikds.setDefaultMinIdle(parseInt(refAddr));
         }
 
         refAddr = ref.get("numTestsPerEvictionRun");
         if (refAddr != null && refAddr.getContent() != null) {
-            ikds.setDefaultNumTestsPerEvictionRun(Integer.parseInt(refAddr.getContent().toString()));
+            ikds.setDefaultNumTestsPerEvictionRun(parseInt(refAddr));
         }
 
         refAddr = ref.get("softMinEvictableIdleTimeMillis");
         if (refAddr != null && refAddr.getContent() != null) {
-            ikds.setDefaultSoftMinEvictableIdleTimeMillis(Long.parseLong(refAddr.getContent().toString()));
+            ikds.setDefaultSoftMinEvictableIdle(Duration.ofMillis(parseLong(refAddr)));
         }
 
         refAddr = ref.get("testOnCreate");
         if (refAddr != null && refAddr.getContent() != null) {
-            ikds.setDefaultTestOnCreate(Boolean.parseBoolean(refAddr.getContent().toString()));
+            ikds.setDefaultTestOnCreate(parseBoolean(refAddr));
         }
 
         refAddr = ref.get("testOnBorrow");
         if (refAddr != null && refAddr.getContent() != null) {
-            ikds.setDefaultTestOnBorrow(Boolean.parseBoolean(refAddr.getContent().toString()));
+            ikds.setDefaultTestOnBorrow(parseBoolean(refAddr));
         }
 
         refAddr = ref.get("testOnReturn");
         if (refAddr != null && refAddr.getContent() != null) {
-            ikds.setDefaultTestOnReturn(Boolean.parseBoolean(refAddr.getContent().toString()));
+            ikds.setDefaultTestOnReturn(parseBoolean(refAddr));
         }
 
         refAddr = ref.get("testWhileIdle");
         if (refAddr != null && refAddr.getContent() != null) {
-            ikds.setDefaultTestWhileIdle(Boolean.parseBoolean(refAddr.getContent().toString()));
+            ikds.setDefaultTestWhileIdle(parseBoolean(refAddr));
         }
 
         refAddr = ref.get("timeBetweenEvictionRunsMillis");
         if (refAddr != null && refAddr.getContent() != null) {
-            ikds.setDefaultTimeBetweenEvictionRunsMillis(Long.parseLong(refAddr.getContent().toString()));
+            ikds.setDefaultDurationBetweenEvictionRuns(Duration.ofMillis(parseLong(refAddr)));
         }
 
         // Connection factory properties
 
         refAddr = ref.get("validationQuery");
         if (refAddr != null && refAddr.getContent() != null) {
-            ikds.setValidationQuery(refAddr.getContent().toString());
+            ikds.setValidationQuery(toString(refAddr));
         }
 
         refAddr = ref.get("validationQueryTimeout");
         if (refAddr != null && refAddr.getContent() != null) {
-            ikds.setValidationQueryTimeout(Integer.parseInt(refAddr.getContent().toString()));
+            ikds.setValidationQueryTimeout(Duration.ofSeconds(parseInt(refAddr)));
         }
 
         refAddr = ref.get("rollbackAfterValidation");
         if (refAddr != null && refAddr.getContent() != null) {
-            ikds.setRollbackAfterValidation(Boolean.parseBoolean(refAddr.getContent().toString()));
+            ikds.setRollbackAfterValidation(parseBoolean(refAddr));
         }
 
         refAddr = ref.get("maxConnLifetimeMillis");
         if (refAddr != null && refAddr.getContent() != null) {
-            ikds.setMaxConnLifetimeMillis(Long.parseLong(refAddr.getContent().toString()));
+            ikds.setMaxConnLifetime(Duration.ofMillis(parseLong(refAddr)));
         }
 
         // Connection properties
 
         refAddr = ref.get("defaultAutoCommit");
         if (refAddr != null && refAddr.getContent() != null) {
-            ikds.setDefaultAutoCommit(Boolean.valueOf(refAddr.getContent().toString()));
+            ikds.setDefaultAutoCommit(Boolean.valueOf(toString(refAddr)));
         }
 
         refAddr = ref.get("defaultTransactionIsolation");
         if (refAddr != null && refAddr.getContent() != null) {
-            ikds.setDefaultTransactionIsolation(Integer.parseInt(refAddr.getContent().toString()));
+            ikds.setDefaultTransactionIsolation(parseInt(refAddr));
         }
 
         refAddr = ref.get("defaultReadOnly");
         if (refAddr != null && refAddr.getContent() != null) {
-            ikds.setDefaultReadOnly(Boolean.valueOf(refAddr.getContent().toString()));
+            ikds.setDefaultReadOnly(Boolean.valueOf(toString(refAddr)));
         }
     }
 
-    /**
-     * @param className
-     *            The class name to test.
-     *
-     * @return true if and only if className is the value returned from getClass().getName().toString()
-     */
-    protected abstract boolean isCorrectClass(String className);
-
-    /**
-     * Creates an instance of the subclass and sets any properties contained in the Reference.
-     *
-     * @param ref
-     *            The properties to be set on the created DataSource
-     *
-     * @return A configured DataSource of the appropriate type.
-     *
-     * @throws ClassNotFoundException
-     *            If a class cannot be found during the deserialization of a configuration parameter.
-     * @throws IOException
-     *            If an I/O error occurs during the deserialization of a configuration parameter.
-     */
-    protected abstract InstanceKeyDataSource getNewInstance(Reference ref) throws IOException, ClassNotFoundException;
-
-    /**
-     * Deserializes the provided byte array to create an object.
-     *
-     * @param data
-     *            Data to deserialize to create the configuration parameter.
-     *
-     * @return The Object created by deserializing the data.
-     *
-     * @throws ClassNotFoundException
-     *            If a class cannot be found during the deserialization of a configuration parameter.
-     * @throws IOException
-     *            If an I/O error occurs during the deserialization of a configuration parameter.
-     */
-    protected static final Object deserialize(final byte[] data) throws IOException, ClassNotFoundException {
-        ObjectInputStream in = null;
-        try {
-            in = new ObjectInputStream(new ByteArrayInputStream(data));
-            return in.readObject();
-        } finally {
-            if (in != null) {
-                try {
-                    in.close();
-                } catch (final IOException ex) {
-                    // ignore
-                }
-            }
-        }
+    String toString(final RefAddr refAddr) {
+        return refAddr.getContent().toString();
     }
 }
diff --git a/java/org/apache/tomcat/dbcp/dbcp2/datasources/KeyedCPDSConnectionFactory.java b/java/org/apache/tomcat/dbcp/dbcp2/datasources/KeyedCPDSConnectionFactory.java
index 77108ee..10595ff 100644
--- a/java/org/apache/tomcat/dbcp/dbcp2/datasources/KeyedCPDSConnectionFactory.java
+++ b/java/org/apache/tomcat/dbcp/dbcp2/datasources/KeyedCPDSConnectionFactory.java
@@ -20,6 +20,8 @@ import java.sql.Connection;
 import java.sql.ResultSet;
 import java.sql.SQLException;
 import java.sql.Statement;
+import java.time.Duration;
+import java.time.Instant;
 import java.util.Collections;
 import java.util.Map;
 import java.util.Set;
@@ -50,10 +52,10 @@ class KeyedCPDSConnectionFactory implements KeyedPooledObjectFactory<UserPassKey
 
     private final ConnectionPoolDataSource cpds;
     private final String validationQuery;
-    private final int validationQueryTimeoutSeconds;
+    private final Duration validationQueryTimeoutDuration;
     private final boolean rollbackAfterValidation;
     private KeyedObjectPool<UserPassKey, PooledConnectionAndInfo> pool;
-    private long maxConnLifetimeMillis = -1;
+    private Duration maxConnLifetime = Duration.ofMillis(-1);
 
     /**
      * Map of PooledConnections for which close events are ignored. Connections are muted when they are being validated.
@@ -66,7 +68,7 @@ class KeyedCPDSConnectionFactory implements KeyedPooledObjectFactory<UserPassKey
     private final Map<PooledConnection, PooledConnectionAndInfo> pcMap = new ConcurrentHashMap<>();
 
     /**
-     * Create a new {@code KeyedPoolableConnectionFactory}.
+     * Creates a new {@code KeyedPoolableConnectionFactory}.
      *
      * @param cpds
      *            the ConnectionPoolDataSource from which to obtain PooledConnections
@@ -75,151 +77,58 @@ class KeyedCPDSConnectionFactory implements KeyedPooledObjectFactory<UserPassKey
      *            row. May be {@code null} in which case3 {@link Connection#isValid(int)} will be used to validate
      *            connections.
      * @param validationQueryTimeoutSeconds
-     *            The time, in seconds, to allow for the validation query to complete
+     *            The Duration to allow for the validation query to complete
      * @param rollbackAfterValidation
      *            whether a rollback should be issued after {@link #validateObject validating} {@link Connection}s.
+     * @since 2.10.0
      */
     public KeyedCPDSConnectionFactory(final ConnectionPoolDataSource cpds, final String validationQuery,
-            final int validationQueryTimeoutSeconds, final boolean rollbackAfterValidation) {
+            final Duration validationQueryTimeoutSeconds, final boolean rollbackAfterValidation) {
         this.cpds = cpds;
         this.validationQuery = validationQuery;
-        this.validationQueryTimeoutSeconds = validationQueryTimeoutSeconds;
+        this.validationQueryTimeoutDuration = validationQueryTimeoutSeconds;
         this.rollbackAfterValidation = rollbackAfterValidation;
... 2608 lines suppressed ...

---------------------------------------------------------------------
To unsubscribe, e-mail: dev-unsubscribe@tomcat.apache.org
For additional commands, e-mail: dev-help@tomcat.apache.org