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 2023/01/03 14:44:55 UTC

[tomcat] branch main updated: Update package renamed fork of Commons DBCP

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


The following commit(s) were added to refs/heads/main by this push:
     new aa4f7669bc Update package renamed fork of Commons DBCP
aa4f7669bc is described below

commit aa4f7669bc33bde65f13457599a0dd4fb3747d44
Author: Mark Thomas <ma...@apache.org>
AuthorDate: Tue Jan 3 14:44:40 2023 +0000

    Update package renamed fork of Commons DBCP
---
 MERGE.txt                                          |   4 +-
 .../apache/tomcat/dbcp/dbcp2/AbandonedTrace.java   |  25 +-
 .../apache/tomcat/dbcp/dbcp2/BasicDataSource.java  | 355 ++++------
 .../tomcat/dbcp/dbcp2/BasicDataSourceFactory.java  |  78 ++-
 .../dbcp/dbcp2/ConnectionFactoryFactory.java       |   8 +-
 .../dbcp/dbcp2/DataSourceConnectionFactory.java    |   2 +-
 .../tomcat/dbcp/dbcp2/DelegatingConnection.java    |  43 +-
 .../dbcp/dbcp2/DelegatingPreparedStatement.java    |  23 +
 .../tomcat/dbcp/dbcp2/DelegatingStatement.java     |  35 +-
 .../apache/tomcat/dbcp/dbcp2/DriverFactory.java    |   2 +-
 .../dbcp/dbcp2/DriverManagerConnectionFactory.java |   8 +-
 .../org/apache/tomcat/dbcp/dbcp2/Jdbc41Bridge.java |   4 +-
 .../dbcp/dbcp2/LifetimeExceededException.java      |  15 +-
 .../tomcat/dbcp/dbcp2/LocalStrings.properties      |   2 +-
 .../tomcat/dbcp/dbcp2/ObjectNameWrapper.java       |   2 +-
 java/org/apache/tomcat/dbcp/dbcp2/PStmtKey.java    | 758 +++++++--------------
 .../dbcp/dbcp2/PoolableCallableStatement.java      |  33 +-
 .../tomcat/dbcp/dbcp2/PoolableConnection.java      |  14 +-
 .../dbcp/dbcp2/PoolableConnectionFactory.java      |  58 +-
 .../dbcp/dbcp2/PoolableConnectionMXBean.java       |   5 +-
 .../dbcp/dbcp2/PoolablePreparedStatement.java      |  33 +-
 .../tomcat/dbcp/dbcp2/PoolingConnection.java       |  66 +-
 .../apache/tomcat/dbcp/dbcp2/PoolingDriver.java    |  13 +-
 java/org/apache/tomcat/dbcp/dbcp2/Utils.java       |  61 +-
 .../dbcp/dbcp2/cpdsadapter/ConnectionImpl.java     |  60 +-
 .../dbcp/dbcp2/cpdsadapter/DriverAdapterCPDS.java  |  63 +-
 .../dbcp/dbcp2/cpdsadapter/PStmtKeyCPDS.java       |  22 +-
 .../dbcp2/cpdsadapter/PooledConnectionImpl.java    | 165 ++---
 .../dbcp/dbcp2/cpdsadapter/package-info.java       |   4 +-
 .../dbcp2/datasources/CPDSConnectionFactory.java   |  31 +-
 .../tomcat/dbcp/dbcp2/datasources/CharArray.java   |  14 +-
 .../dbcp2/datasources/InstanceKeyDataSource.java   |  39 +-
 .../datasources/InstanceKeyDataSourceFactory.java  |  26 +-
 .../datasources/KeyedCPDSConnectionFactory.java    |  26 +-
 .../dbcp2/datasources/PerUserPoolDataSource.java   | 294 +++-----
 .../datasources/PerUserPoolDataSourceFactory.java  |   2 +-
 .../tomcat/dbcp/dbcp2/datasources/PoolKey.java     |   2 +-
 .../dbcp2/datasources/PooledConnectionManager.java |  20 +-
 .../dbcp2/datasources/SharedPoolDataSource.java    |  10 +-
 .../tomcat/dbcp/dbcp2/datasources/UserPassKey.java |   2 +-
 .../dbcp/dbcp2/datasources/package-info.java       |  18 +-
 .../managed/DataSourceXAConnectionFactory.java     |  50 +-
 .../dbcp2/managed/LocalXAConnectionFactory.java    |  34 +-
 .../dbcp/dbcp2/managed/ManagedConnection.java      |   8 +-
 .../dbcp/dbcp2/managed/ManagedDataSource.java      |   2 +-
 .../managed/PoolableManagedConnectionFactory.java  |   5 +-
 .../SynchronizationAdapter.java}                   |  31 +-
 .../dbcp/dbcp2/managed/TransactionContext.java     |  18 +-
 .../dbcp/dbcp2/managed/TransactionRegistry.java    |  13 +-
 webapps/docs/changelog.xml                         |   4 +
 50 files changed, 1044 insertions(+), 1566 deletions(-)

diff --git a/MERGE.txt b/MERGE.txt
index 5673ec8d95..63075f061d 100644
--- a/MERGE.txt
+++ b/MERGE.txt
@@ -54,7 +54,7 @@ Unused code is removed
 Sub-tree:
 src/main/java/org/apache/commons/fileupload2
 The SHA1 ID / tag for the most recent commit to be merged to Tomcat is:
-34eb241c051b02eca3b0b1b04f67b3b4e6c3a24d (2023-02-03)
+34eb241c051b02eca3b0b1b04f67b3b4e6c3a24d (2023-01-03)
 
 Note: Tomcat's copy of fileupload also includes classes copied manually from
       Commons IO.
@@ -77,4 +77,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:
-rel/commons-dbcp-2.9.0 (2021-08-03)
+f13128604536e78bb1b45b44f74128e93cfbb7cc (2023-01-03)
diff --git a/java/org/apache/tomcat/dbcp/dbcp2/AbandonedTrace.java b/java/org/apache/tomcat/dbcp/dbcp2/AbandonedTrace.java
index c10758017e..a094f16d31 100644
--- a/java/org/apache/tomcat/dbcp/dbcp2/AbandonedTrace.java
+++ b/java/org/apache/tomcat/dbcp/dbcp2/AbandonedTrace.java
@@ -17,11 +17,13 @@
 package org.apache.tomcat.dbcp.dbcp2;
 
 import java.lang.ref.WeakReference;
+import java.sql.SQLException;
 import java.time.Instant;
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.Iterator;
 import java.util.List;
+import java.util.function.Consumer;
 
 import org.apache.tomcat.dbcp.pool2.TrackedUse;
 
@@ -33,7 +35,7 @@ import org.apache.tomcat.dbcp.pool2.TrackedUse;
  *
  * @since 2.0
  */
-public class AbandonedTrace implements TrackedUse {
+public class AbandonedTrace implements TrackedUse, AutoCloseable {
 
     /** A list of objects created by children of this object. */
     private final List<WeakReference<AbandonedTrace>> traceList = new ArrayList<>();
@@ -80,6 +82,27 @@ public class AbandonedTrace implements TrackedUse {
         }
     }
 
+    /**
+     * Subclasses can implement this nop.
+     *
+     * @throws SQLException Ignored here, for subclasses.
+     * @since 2.10.0
+     */
+    @Override
+    public void close() throws SQLException {
+        // nop
+    }
+
+    /**
+     * Closes this resource and if an exception is caught, then calls {@code exceptionHandler}.
+     *
+     * @param exceptionHandler Consumes exception thrown closing this resource.
+     * @since 2.10.0
+     */
+    protected void close(final Consumer<Exception> exceptionHandler) {
+        Utils.close(this, exceptionHandler);
+    }
+
     /**
      * Gets the last time this object was used in milliseconds.
      *
diff --git a/java/org/apache/tomcat/dbcp/dbcp2/BasicDataSource.java b/java/org/apache/tomcat/dbcp/dbcp2/BasicDataSource.java
index f260a536c4..a9cf96761c 100644
--- a/java/org/apache/tomcat/dbcp/dbcp2/BasicDataSource.java
+++ b/java/org/apache/tomcat/dbcp/dbcp2/BasicDataSource.java
@@ -28,15 +28,16 @@ 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;
-import java.util.HashSet;
 import java.util.List;
 import java.util.Objects;
 import java.util.Properties;
 import java.util.Set;
+import java.util.function.BiConsumer;
 import java.util.logging.Logger;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
 
 import javax.management.MBeanRegistration;
 import javax.management.MBeanServer;
@@ -56,7 +57,7 @@ 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.
+ * Basic implementation of {@code javax.sql.DataSource} that is configured via JavaBeans properties.
  *
  * <p>
  * This is not the only way to combine the <em>commons-dbcp2</em> and <em>commons-pool2</em> packages, but provides a
@@ -102,9 +103,9 @@ public class BasicDataSource implements DataSource, BasicDataSourceMXBean, MBean
      * Validates the given factory.
      *
      * @param connectionFactory the factory
-     * @throws Exception Thrown by one of the factory methods while managing a temporary pooled object.
+     * @throws SQLException Thrown by one of the factory methods while managing a temporary pooled object.
      */
-    protected static void validateConnectionFactory(final PoolableConnectionFactory connectionFactory) throws Exception {
+    protected static void validateConnectionFactory(final PoolableConnectionFactory connectionFactory) throws SQLException {
         PoolableConnection conn = null;
         PooledObject<PoolableConnection> p = null;
         try {
@@ -212,7 +213,7 @@ public class BasicDataSource implements DataSource, BasicDataSourceMXBean, MBean
     private Duration maxWaitDuration = BaseObjectPoolConfig.DEFAULT_MAX_WAIT;
 
     /**
-     * Prepared statement pooling for this pool. When this property is set to <code>true</code> both PreparedStatements
+     * Prepared statement pooling for this pool. When this property is set to {@code true} both PreparedStatements
      * and CallableStatements are pooled.
      */
     private boolean poolPreparedStatements;
@@ -228,7 +229,7 @@ public class BasicDataSource implements DataSource, BasicDataSourceMXBean, MBean
      * <p>
      * Note: As of version 1.3, CallableStatements (those produced by {@link Connection#prepareCall}) are pooled along
      * with PreparedStatements (produced by {@link Connection#prepareStatement}) and
-     * <code>maxOpenPreparedStatements</code> limits the total number of prepared or callable statements that may be in
+     * {@code maxOpenPreparedStatements} limits the total number of prepared or callable statements that may be in
      * use at a given time.
      * </p>
      */
@@ -290,9 +291,9 @@ public class BasicDataSource implements DataSource, BasicDataSourceMXBean, MBean
     private volatile String password;
 
     /**
-     * The connection URL to be passed to our JDBC driver to establish a connection.
+     * The connection string to be passed to our JDBC driver to establish a connection.
      */
-    private String url;
+    private String connectionString;
 
     /**
      * The connection user name to be passed to our JDBC driver to establish a connection.
@@ -336,6 +337,8 @@ public class BasicDataSource implements DataSource, BasicDataSourceMXBean, MBean
 
     private String jmxName;
 
+    private boolean registerConnectionMBean = true;
+
     private boolean autoCommitOnReturn = true;
 
     private boolean rollbackOnReturn = true;
@@ -358,7 +361,7 @@ public class BasicDataSource implements DataSource, BasicDataSourceMXBean, MBean
 
     /**
      * The data source we will use to manage connections. This object should be acquired <strong>ONLY</strong> by calls
-     * to the <code>createDataSource()</code> method.
+     * to the {@code createDataSource()} method.
      */
     private volatile DataSource dataSource;
 
@@ -447,7 +450,7 @@ public class BasicDataSource implements DataSource, BasicDataSourceMXBean, MBean
      * with the specified {@link ClassLoader}.</li>
      * <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 {code url}.
+     * <li>If a driver still isn't loaded one is loaded via the {@link DriverManager} using the specified {code connectionString}.
      * </ol>
      * <p>
      * This method exists so subclasses can replace the implementation class.
@@ -505,7 +508,7 @@ public class BasicDataSource implements DataSource, BasicDataSourceMXBean, MBean
      * @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.
      */
-    protected DataSource createDataSource() throws SQLException {
+    protected synchronized DataSource createDataSource() throws SQLException {
         if (closed) {
             throw new SQLException("Data source is closed");
         }
@@ -526,56 +529,28 @@ public class BasicDataSource implements DataSource, BasicDataSourceMXBean, MBean
             final ConnectionFactory driverConnectionFactory = createConnectionFactory();
 
             // Set up the poolable connection factory
-            boolean success = false;
             final PoolableConnectionFactory poolableConnectionFactory;
             try {
                 poolableConnectionFactory = createPoolableConnectionFactory(driverConnectionFactory);
                 poolableConnectionFactory.setPoolStatements(poolPreparedStatements);
                 poolableConnectionFactory.setMaxOpenPreparedStatements(maxOpenPreparedStatements);
-                success = true;
-            } catch (final SQLException | RuntimeException se) {
-                throw se;
-            } catch (final Exception ex) {
-                throw new SQLException("Error creating connection factory", ex);
-            }
-
-            if (success) {
                 // create a pool for our connections
                 createConnectionPool(poolableConnectionFactory);
-            }
-
-            // Create the pooling data source to manage connections
-            DataSource newDataSource;
-            success = false;
-            try {
-                newDataSource = createDataSourceInstance();
+                final DataSource newDataSource = createDataSourceInstance();
                 newDataSource.setLogWriter(logWriter);
-                success = true;
+                connectionPool.addObjects(initialSize);
+                // If timeBetweenEvictionRunsMillis > 0, start the pool's evictor
+                // task
+                startPoolMaintenance();
+                dataSource = newDataSource;
             } catch (final SQLException | RuntimeException se) {
+                closeConnectionPool();
                 throw se;
             } catch (final Exception ex) {
-                throw new SQLException("Error creating datasource", ex);
-            } finally {
-                if (!success) {
-                    closeConnectionPool();
-                }
-            }
-
-            // If initialSize > 0, preload the pool
-            try {
-                for (int i = 0; i < initialSize; i++) {
-                    connectionPool.addObject();
-                }
-            } catch (final Exception e) {
                 closeConnectionPool();
-                throw new SQLException("Error preloading the connection pool", e);
+                throw new SQLException("Error creating connection factory", ex);
             }
 
-            // If timeBetweenEvictionRunsMillis > 0, start the pool's evictor
-            // task
-            startPoolMaintenance();
-
-            dataSource = newDataSource;
             return dataSource;
         }
     }
@@ -627,7 +602,11 @@ public class BasicDataSource implements DataSource, BasicDataSourceMXBean, MBean
             throws SQLException {
         PoolableConnectionFactory connectionFactory = null;
         try {
-            connectionFactory = new PoolableConnectionFactory(driverConnectionFactory, ObjectNameWrapper.unwrap(registeredJmxObjectName));
+            if (registerConnectionMBean) {
+                connectionFactory = new PoolableConnectionFactory(driverConnectionFactory, ObjectNameWrapper.unwrap(registeredJmxObjectName));
+            } else {
+                connectionFactory = new PoolableConnectionFactory(driverConnectionFactory, null);
+            }
             connectionFactory.setValidationQuery(validationQuery);
             connectionFactory.setValidationQueryTimeout(validationQueryTimeoutDuration);
             connectionFactory.setConnectionInitSql(connectionInitSqls);
@@ -680,7 +659,7 @@ public class BasicDataSource implements DataSource, BasicDataSourceMXBean, MBean
      * connection pool record a stack trace every time a method is called on a pooled connection and retain the most
      * recent stack trace to aid debugging of abandoned connections?
      *
-     * @return <code>true</code> if usage tracking is enabled
+     * @return {@code true} if usage tracking is enabled
      */
     @Override
     public boolean getAbandonedUsageTracking() {
@@ -690,7 +669,7 @@ public class BasicDataSource implements DataSource, BasicDataSourceMXBean, MBean
     /**
      * 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.
+     * setting is {@code false} when the connection is returned. It is {@code true} by default.
      *
      * @return Whether or not connections being returned to the pool will be checked and configured with auto-commit.
      */
@@ -815,7 +794,7 @@ public class BasicDataSource implements DataSource, BasicDataSourceMXBean, MBean
 
     /**
      * 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.
+     * connection. {@code null} means that the driver default will be used.
      *
      * @return The default query timeout in seconds.
      * @deprecated Use {@link #getDefaultQueryTimeoutDuration()}.
@@ -827,7 +806,7 @@ public class BasicDataSource implements DataSource, BasicDataSourceMXBean, MBean
 
     /**
      * 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.
+     * connection. {@code null} means that the driver default will be used.
      *
      * @return The default query timeout Duration.
      * @since 2.10.0
@@ -904,7 +883,7 @@ public class BasicDataSource implements DataSource, BasicDataSourceMXBean, MBean
     }
 
     /**
-     * Gets 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} 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
@@ -934,7 +913,7 @@ public class BasicDataSource implements DataSource, BasicDataSourceMXBean, MBean
     /**
      * 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.
+     * setting is {@code false} when the connection is returned. It is {@code true} by default.
      *
      * @return Whether or not connections being returned to the pool will be checked and configured with auto-commit.
      * @deprecated Use {@link #getAutoCommitOnReturn()}.
@@ -1093,7 +1072,7 @@ public class BasicDataSource implements DataSource, BasicDataSourceMXBean, MBean
     }
 
     /**
-     * Gets the value of the <code>maxOpenPreparedStatements</code> property.
+     * Gets the value of the {@code maxOpenPreparedStatements} property.
      *
      * @return the maximum number of open statements
      */
@@ -1446,13 +1425,13 @@ public class BasicDataSource implements DataSource, BasicDataSourceMXBean, MBean
     }
 
     /**
-     * Gets the JDBC connection {code url} property.
+     * Gets the JDBC connection {code connectionString} property.
      *
-     * @return the {code url} passed to the JDBC driver to establish connections
+     * @return the {code connectionString} passed to the JDBC driver to establish connections
      */
     @Override
     public synchronized String getUrl() {
-        return this.url;
+        return this.connectionString;
     }
 
     /**
@@ -1703,39 +1682,42 @@ public class BasicDataSource implements DataSource, BasicDataSourceMXBean, MBean
         start();
     }
 
-    /**
-     * Sets the print writer to be used by this configuration to log information on abandoned objects.
-     *
-     * @param logWriter The new log writer
-     */
-    public void setAbandonedLogWriter(final PrintWriter logWriter) {
+    private <T> void setAbandoned(final BiConsumer<AbandonedConfig, T> consumer, final T object) {
         if (abandonedConfig == null) {
             abandonedConfig = new AbandonedConfig();
         }
-        abandonedConfig.setLogWriter(logWriter);
+        consumer.accept(abandonedConfig, object);
         final GenericObjectPool<?> gop = this.connectionPool;
         if (gop != null) {
             gop.setAbandonedConfig(abandonedConfig);
         }
     }
 
+    private <T> void setConnectionPool(final BiConsumer<GenericObjectPool<PoolableConnection>, T> consumer, final T object) {
+        if (connectionPool != null) {
+            consumer.accept(connectionPool, object);
+        }
+    }
+
+    /**
+     * Sets the print writer to be used by this configuration to log information on abandoned objects.
+     *
+     * @param logWriter The new log writer
+     */
+    public void setAbandonedLogWriter(final PrintWriter logWriter) {
+        setAbandoned(AbandonedConfig::setLogWriter, logWriter);
+    }
+
     /**
      * If the connection pool implements {@link org.apache.tomcat.dbcp.pool2.UsageTracking UsageTracking}, configure whether
      * the connection pool should record a stack trace every time a method is called on a pooled connection and retain
      * the most recent stack trace to aid debugging of abandoned connections.
      *
-     * @param usageTracking A value of <code>true</code> will enable the recording of a stack trace on every use of a
+     * @param usageTracking A value of {@code true} will enable the recording of a stack trace on every use of a
      *                      pooled connection
      */
     public void setAbandonedUsageTracking(final boolean usageTracking) {
-        if (abandonedConfig == null) {
-            abandonedConfig = new AbandonedConfig();
-        }
-        abandonedConfig.setUseUsageTracking(usageTracking);
-        final GenericObjectPool<?> gop = this.connectionPool;
-        if (gop != null) {
-            gop.setAbandonedConfig(abandonedConfig);
-        }
+        setAbandoned(AbandonedConfig::setUseUsageTracking, Boolean.valueOf(usageTracking));
     }
 
     /**
@@ -1756,7 +1738,7 @@ public class BasicDataSource implements DataSource, BasicDataSourceMXBean, MBean
     /**
      * Sets 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.
+     * setting is {@code false} when the connection is returned. It is {@code true} by default.
      *
      * @param autoCommitOnReturn Whether or not connections being returned to the pool will be checked and configured
      *                           with auto-commit.
@@ -1807,20 +1789,23 @@ 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.isEmpty()) {
-            ArrayList<String> newVal = null;
-            for (final String s : connectionInitSqls) {
-                if (!isEmpty(s)) {
-                    if (newVal == null) {
-                        newVal = new ArrayList<>();
-                    }
-                    newVal.add(s);
-                }
-            }
-            this.connectionInitSqls = newVal;
-        } else {
-            this.connectionInitSqls = null;
-        }
+//        if (connectionInitSqls != null && !connectionInitSqls.isEmpty()) {
+//            ArrayList<String> newVal = null;
+//            for (final String s : connectionInitSqls) {
+//                if (!isEmpty(s)) {
+//                    if (newVal == null) {
+//                        newVal = new ArrayList<>();
+//                    }
+//                    newVal.add(s);
+//                }
+//            }
+//            this.connectionInitSqls = newVal;
+//        } else {
+//            this.connectionInitSqls = null;
+//        }
+        final List<String> collect = Utils.isEmpty(connectionInitSqls) ? null
+                : connectionInitSqls.stream().filter(s -> !isEmpty(s)).collect(Collectors.toList());
+        this.connectionInitSqls = Utils.isEmpty(collect) ? null : collect;
     }
 
     /**
@@ -1835,23 +1820,21 @@ public class BasicDataSource implements DataSource, BasicDataSourceMXBean, MBean
      * @param connectionProperties the connection properties used to create new connections
      */
     public void setConnectionProperties(final String connectionProperties) {
-        Objects.requireNonNull(connectionProperties, "connectionProperties is null");
+        Objects.requireNonNull(connectionProperties, "connectionProperties");
         final String[] entries = connectionProperties.split(";");
         final Properties properties = new Properties();
-        for (final String entry : entries) {
-            if (!entry.isEmpty()) {
-                final int index = entry.indexOf('=');
-                if (index > 0) {
-                    final String name = entry.substring(0, index);
-                    final String value = entry.substring(index + 1);
-                    properties.setProperty(name, value);
-                } else {
-                    // no value is empty string which is how
-                    // java.util.Properties works
-                    properties.setProperty(entry, "");
-                }
+        Stream.of(entries).filter(e -> !e.isEmpty()).forEach(entry -> {
+            final int index = entry.indexOf('=');
+            if (index > 0) {
+                final String name = entry.substring(0, index);
+                final String value = entry.substring(index + 1);
+                properties.setProperty(name, value);
+            } else {
+                // no value is empty string which is how
+                // java.util.Properties works
+                properties.setProperty(entry, "");
             }
-        }
+        });
         this.connectionProperties = properties;
     }
 
@@ -1885,7 +1868,7 @@ 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.
+     * connection. {@code null} means that the driver default will be used.
      *
      * @param defaultQueryTimeoutDuration The default query timeout Duration.
      * @since 2.10.0
@@ -1896,7 +1879,7 @@ 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.
+     * connection. {@code null} means that the driver default will be used.
      *
      * @param defaultQueryTimeoutSeconds The default query timeout in seconds.
      * @deprecated Use {@link #setDefaultQueryTimeout(Duration)}.
@@ -1953,7 +1936,7 @@ public class BasicDataSource implements DataSource, BasicDataSourceMXBean, MBean
     /**
      * Sets the SQL_STATE codes considered to signal fatal conditions.
      * <p>
-     * Overrides the defaults in {@link Utils#DISCONNECTION_SQL_CODES} (plus anything starting with
+     * Overrides the defaults in {@link Utils#getDisconnectionSqlCodes()} (plus anything starting with
      * {@link Utils#DISCONNECTION_SQL_CODE_PREFIX}). If this property is non-null and {@link #getFastFailValidation()}
      * is {@code true}, whenever connections created by this datasource generate exceptions with SQL_STATE codes in this
      * list, they will be marked as "fatally disconnected" and subsequent validations will fail fast (no attempt at
@@ -1972,20 +1955,23 @@ public class BasicDataSource implements DataSource, BasicDataSourceMXBean, MBean
      * @since 2.1
      */
     public void setDisconnectionSqlCodes(final Collection<String> disconnectionSqlCodes) {
-        if (disconnectionSqlCodes != null && !disconnectionSqlCodes.isEmpty()) {
-            HashSet<String> newVal = null;
-            for (final String s : disconnectionSqlCodes) {
-                if (!isEmpty(s)) {
-                    if (newVal == null) {
-                        newVal = new HashSet<>();
-                    }
-                    newVal.add(s);
-                }
-            }
-            this.disconnectionSqlCodes = newVal;
-        } else {
-            this.disconnectionSqlCodes = null;
-        }
+//        if (disconnectionSqlCodes != null && !disconnectionSqlCodes.isEmpty()) {
+//            HashSet<String> newVal = null;
+//            for (final String s : disconnectionSqlCodes) {
+//                if (!isEmpty(s)) {
+//                    if (newVal == null) {
+//                        newVal = new HashSet<>();
+//                    }
+//                    newVal.add(s);
+//                }
+//            }
+//            this.disconnectionSqlCodes = newVal;
+//        } else {
+//            this.disconnectionSqlCodes = null;
+//        }
+        final Set<String> collect = Utils.isEmpty(disconnectionSqlCodes) ? null
+                : disconnectionSqlCodes.stream().filter(s -> !isEmpty(s)).collect(Collectors.toSet());
+        this.disconnectionSqlCodes = Utils.isEmpty(collect) ? null : collect;
     }
 
     /**
@@ -2033,7 +2019,7 @@ public class BasicDataSource implements DataSource, BasicDataSourceMXBean, MBean
     /**
      * Sets 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.
+     * setting is {@code false} when the connection is returned. It is {@code true} by default.
      *
      * @param autoCommitOnReturn Whether or not connections being returned to the pool will be checked and configured
      *                           with auto-commit.
@@ -2050,9 +2036,7 @@ public class BasicDataSource implements DataSource, BasicDataSourceMXBean, MBean
      * @param evictionPolicyClassName The fully qualified class name of the EvictionPolicy implementation
      */
     public synchronized void setEvictionPolicyClassName(final String evictionPolicyClassName) {
-        if (connectionPool != null) {
-            connectionPool.setEvictionPolicyClassName(evictionPolicyClassName);
-        }
+        setConnectionPool(GenericObjectPool::setEvictionPolicyClassName, evictionPolicyClassName);
         this.evictionPolicyClassName = evictionPolicyClassName;
     }
 
@@ -2091,6 +2075,16 @@ public class BasicDataSource implements DataSource, BasicDataSourceMXBean, MBean
         this.jmxName = jmxName;
     }
 
+    /**
+     * Sets if connection level JMX tracking is requested for this DataSource. If true, each connection will be
+     * registered for tracking with JMX.
+     *
+     * @param registerConnectionMBean connection tracking requested for this DataSource.
+     */
+    public void setRegisterConnectionMBean(final boolean registerConnectionMBean) {
+        this.registerConnectionMBean = registerConnectionMBean;
+    }
+
     /**
      * Sets the LIFO property. True means the pool behaves as a LIFO queue; false means FIFO.
      *
@@ -2098,23 +2092,14 @@ public class BasicDataSource implements DataSource, BasicDataSourceMXBean, MBean
      */
     public synchronized void setLifo(final boolean lifo) {
         this.lifo = lifo;
-        if (connectionPool != null) {
-            connectionPool.setLifo(lifo);
-        }
+        setConnectionPool(GenericObjectPool::setLifo, Boolean.valueOf(lifo));
     }
 
     /**
      * @param logAbandoned new logAbandoned property value
      */
     public void setLogAbandoned(final boolean logAbandoned) {
-        if (abandonedConfig == null) {
-            abandonedConfig = new AbandonedConfig();
-        }
-        abandonedConfig.setLogAbandoned(logAbandoned);
-        final GenericObjectPool<?> gop = this.connectionPool;
-        if (gop != null) {
-            gop.setAbandonedConfig(abandonedConfig);
-        }
+        setAbandoned(AbandonedConfig::setLogAbandoned, Boolean.valueOf(logAbandoned));
     }
 
     /**
@@ -2208,13 +2193,11 @@ public class BasicDataSource implements DataSource, BasicDataSourceMXBean, MBean
      */
     public synchronized void setMaxIdle(final int maxIdle) {
         this.maxIdle = maxIdle;
-        if (connectionPool != null) {
-            connectionPool.setMaxIdle(maxIdle);
-        }
+        setConnectionPool(GenericObjectPool::setMaxIdle, Integer.valueOf(maxIdle));
     }
 
     /**
-     * Sets the value of the <code>maxOpenPreparedStatements</code> property.
+     * Sets the value of the {@code maxOpenPreparedStatements} property.
      * <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,
@@ -2236,9 +2219,7 @@ public class BasicDataSource implements DataSource, BasicDataSourceMXBean, MBean
      */
     public synchronized void setMaxTotal(final int maxTotal) {
         this.maxTotal = maxTotal;
-        if (connectionPool != null) {
-            connectionPool.setMaxTotal(maxTotal);
-        }
+        setConnectionPool(GenericObjectPool::setMaxTotal, Integer.valueOf(maxTotal));
     }
 
     /**
@@ -2250,9 +2231,7 @@ public class BasicDataSource implements DataSource, BasicDataSourceMXBean, MBean
      */
     public synchronized void setMaxWait(final Duration maxWaitDuration) {
         this.maxWaitDuration = maxWaitDuration;
-        if (connectionPool != null) {
-            connectionPool.setMaxWait(maxWaitDuration);
-        }
+        setConnectionPool(GenericObjectPool::setMaxWait, maxWaitDuration);
     }
 
     /**
@@ -2276,9 +2255,7 @@ public class BasicDataSource implements DataSource, BasicDataSourceMXBean, MBean
      */
     public synchronized void setMinEvictableIdle(final Duration minEvictableIdleDuration) {
         this.minEvictableIdleDuration = minEvictableIdleDuration;
-        if (connectionPool != null) {
-            connectionPool.setMinEvictableIdle(minEvictableIdleDuration);
-        }
+        setConnectionPool(GenericObjectPool::setMinEvictableIdle, minEvictableIdleDuration);
     }
 
     /**
@@ -2303,9 +2280,7 @@ public class BasicDataSource implements DataSource, BasicDataSourceMXBean, MBean
      */
     public synchronized void setMinIdle(final int minIdle) {
         this.minIdle = minIdle;
-        if (connectionPool != null) {
-            connectionPool.setMinIdle(minIdle);
-        }
+        setConnectionPool(GenericObjectPool::setMinIdle, Integer.valueOf(minIdle));
     }
 
     /**
@@ -2316,9 +2291,7 @@ public class BasicDataSource implements DataSource, BasicDataSourceMXBean, MBean
      */
     public synchronized void setNumTestsPerEvictionRun(final int numTestsPerEvictionRun) {
         this.numTestsPerEvictionRun = numTestsPerEvictionRun;
-        if (connectionPool != null) {
-            connectionPool.setNumTestsPerEvictionRun(numTestsPerEvictionRun);
-        }
+        setConnectionPool(GenericObjectPool::setNumTestsPerEvictionRun, Integer.valueOf(numTestsPerEvictionRun));
     }
 
     /**
@@ -2355,14 +2328,7 @@ public class BasicDataSource implements DataSource, BasicDataSourceMXBean, MBean
      * @see #getRemoveAbandonedOnBorrow()
      */
     public void setRemoveAbandonedOnBorrow(final boolean removeAbandonedOnBorrow) {
-        if (abandonedConfig == null) {
-            abandonedConfig = new AbandonedConfig();
-        }
-        abandonedConfig.setRemoveAbandonedOnBorrow(removeAbandonedOnBorrow);
-        final GenericObjectPool<?> gop = this.connectionPool;
-        if (gop != null) {
-            gop.setAbandonedConfig(abandonedConfig);
-        }
+        setAbandoned(AbandonedConfig::setRemoveAbandonedOnBorrow, Boolean.valueOf(removeAbandonedOnBorrow));
     }
 
     /**
@@ -2370,14 +2336,7 @@ public class BasicDataSource implements DataSource, BasicDataSourceMXBean, MBean
      * @see #getRemoveAbandonedOnMaintenance()
      */
     public void setRemoveAbandonedOnMaintenance(final boolean removeAbandonedOnMaintenance) {
-        if (abandonedConfig == null) {
-            abandonedConfig = new AbandonedConfig();
-        }
-        abandonedConfig.setRemoveAbandonedOnMaintenance(removeAbandonedOnMaintenance);
-        final GenericObjectPool<?> gop = this.connectionPool;
-        if (gop != null) {
-            gop.setAbandonedConfig(abandonedConfig);
-        }
+        setAbandoned(AbandonedConfig::setRemoveAbandonedOnMaintenance, Boolean.valueOf(removeAbandonedOnMaintenance));
     }
 
     /**
@@ -2394,14 +2353,7 @@ public class BasicDataSource implements DataSource, BasicDataSourceMXBean, MBean
      * @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);
-        }
+        setAbandoned(AbandonedConfig::setRemoveAbandonedTimeout, removeAbandonedTimeout);
     }
 
     /**
@@ -2419,14 +2371,7 @@ public class BasicDataSource implements DataSource, BasicDataSourceMXBean, MBean
      */
     @Deprecated
     public void setRemoveAbandonedTimeout(final int removeAbandonedTimeout) {
-        if (abandonedConfig == null) {
-            abandonedConfig = new AbandonedConfig();
-        }
-        abandonedConfig.setRemoveAbandonedTimeout(Duration.ofSeconds(removeAbandonedTimeout));
-        final GenericObjectPool<?> gop = this.connectionPool;
-        if (gop != null) {
-            gop.setAbandonedConfig(abandonedConfig);
-        }
+        setAbandoned(AbandonedConfig::setRemoveAbandonedTimeout, Duration.ofSeconds(removeAbandonedTimeout));
     }
 
     /**
@@ -2451,9 +2396,7 @@ public class BasicDataSource implements DataSource, BasicDataSourceMXBean, MBean
      */
     public synchronized void setSoftMinEvictableIdle(final Duration softMinEvictableIdleTimeMillis) {
         this.softMinEvictableIdleDuration = softMinEvictableIdleTimeMillis;
-        if (connectionPool != null) {
-            connectionPool.setSoftMinEvictableIdle(softMinEvictableIdleTimeMillis);
-        }
+        setConnectionPool(GenericObjectPool::setSoftMinEvictableIdle, softMinEvictableIdleTimeMillis);
     }
 
     /**
@@ -2479,9 +2422,7 @@ public class BasicDataSource implements DataSource, BasicDataSourceMXBean, MBean
      */
     public synchronized void setTestOnBorrow(final boolean testOnBorrow) {
         this.testOnBorrow = testOnBorrow;
-        if (connectionPool != null) {
-            connectionPool.setTestOnBorrow(testOnBorrow);
-        }
+        setConnectionPool(GenericObjectPool::setTestOnBorrow, Boolean.valueOf(testOnBorrow));
     }
 
     /**
@@ -2492,35 +2433,29 @@ public class BasicDataSource implements DataSource, BasicDataSourceMXBean, MBean
      */
     public synchronized void setTestOnCreate(final boolean testOnCreate) {
         this.testOnCreate = testOnCreate;
-        if (connectionPool != null) {
-            connectionPool.setTestOnCreate(testOnCreate);
-        }
+        setConnectionPool(GenericObjectPool::setTestOnCreate, Boolean.valueOf(testOnCreate));
     }
 
     /**
-     * Sets the <code>testOnReturn</code> property. This property determines whether or not the pool will validate
+     * Sets the {@code testOnReturn} property. This property determines whether or not the pool will validate
      * objects before they are returned to the pool.
      *
      * @param testOnReturn new value for testOnReturn property
      */
     public synchronized void setTestOnReturn(final boolean testOnReturn) {
         this.testOnReturn = testOnReturn;
-        if (connectionPool != null) {
-            connectionPool.setTestOnReturn(testOnReturn);
-        }
+        setConnectionPool(GenericObjectPool::setTestOnReturn, Boolean.valueOf(testOnReturn));
     }
 
     /**
-     * Sets the <code>testWhileIdle</code> property. This property determines whether or not the idle object evictor
+     * Sets the {@code testWhileIdle} property. This property determines whether or not the idle object evictor
      * will validate connections.
      *
      * @param testWhileIdle new value for testWhileIdle property
      */
     public synchronized void setTestWhileIdle(final boolean testWhileIdle) {
         this.testWhileIdle = testWhileIdle;
-        if (connectionPool != null) {
-            connectionPool.setTestWhileIdle(testWhileIdle);
-        }
+        setConnectionPool(GenericObjectPool::setTestWhileIdle, Boolean.valueOf(testWhileIdle));
     }
 
     /**
@@ -2532,9 +2467,7 @@ public class BasicDataSource implements DataSource, BasicDataSourceMXBean, MBean
      */
     public synchronized void setDurationBetweenEvictionRuns(final Duration timeBetweenEvictionRunsMillis) {
         this.durationBetweenEvictionRuns = timeBetweenEvictionRunsMillis;
-        if (connectionPool != null) {
-            connectionPool.setTimeBetweenEvictionRuns(timeBetweenEvictionRunsMillis);
-        }
+        setConnectionPool(GenericObjectPool::setTimeBetweenEvictionRuns, timeBetweenEvictionRunsMillis);
     }
 
     /**
@@ -2550,17 +2483,17 @@ public class BasicDataSource implements DataSource, BasicDataSourceMXBean, MBean
     }
 
     /**
-     * Sets the {code url}.
+     * Sets the {code connection string}.
      * <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 url the new value for the JDBC connection url
+     * @param connectionString the new value for the JDBC connection connectionString
      */
-    public synchronized void setUrl(final String url) {
-        this.url = url;
+    public synchronized void setUrl(final String connectionString) {
+        this.connectionString = connectionString;
     }
 
     /**
diff --git a/java/org/apache/tomcat/dbcp/dbcp2/BasicDataSourceFactory.java b/java/org/apache/tomcat/dbcp/dbcp2/BasicDataSourceFactory.java
index 7a77f40cfb..daa7cf0091 100644
--- a/java/org/apache/tomcat/dbcp/dbcp2/BasicDataSourceFactory.java
+++ b/java/org/apache/tomcat/dbcp/dbcp2/BasicDataSourceFactory.java
@@ -20,6 +20,7 @@ import java.io.ByteArrayInputStream;
 import java.io.IOException;
 import java.nio.charset.StandardCharsets;
 import java.sql.Connection;
+import java.sql.SQLException;
 import java.time.Duration;
 import java.util.ArrayList;
 import java.util.Arrays;
@@ -49,12 +50,12 @@ import org.apache.tomcat.dbcp.pool2.impl.BaseObjectPoolConfig;
 import org.apache.tomcat.dbcp.pool2.impl.GenericObjectPoolConfig;
 
 /**
- * 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:
+ * JNDI object factory that creates an instance of {@code BasicDataSource} that has been configured based on the
+ * {@code RefAddr} values of the specified {@code Reference}, which must match the names and data types of the
+ * {@code BasicDataSource} bean properties with the following exceptions:
  * <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>
+ * <li>{@code connectionInitSqls} must be passed to this factory as a single String using semicolon to delimit the
+ * statements whereas {@code BasicDataSource} requires a collection of Strings.</li>
  * </ul>
  *
  * @since 2.0
@@ -85,12 +86,13 @@ public class BasicDataSourceFactory implements ObjectFactory {
     private static final String PROP_SOFT_MIN_EVICTABLE_IDLE_TIME_MILLIS = "softMinEvictableIdleTimeMillis";
     private static final String PROP_EVICTION_POLICY_CLASS_NAME = "evictionPolicyClassName";
     private static final String PROP_TEST_WHILE_IDLE = "testWhileIdle";
-    private static final String PROP_PASSWORD = "password";
+    private static final String PROP_PASSWORD = Constants.KEY_PASSWORD;
     private static final String PROP_URL = "url";
     private static final String PROP_USER_NAME = "username";
     private static final String PROP_VALIDATION_QUERY = "validationQuery";
     private static final String PROP_VALIDATION_QUERY_TIMEOUT = "validationQueryTimeout";
     private static final String PROP_JMX_NAME = "jmxName";
+    private static final String PROP_REGISTER_CONNECTION_MBEAN = "registerConnectionMBean";
     private static final String PROP_CONNECTION_FACTORY_CLASS_NAME = "connectionFactoryClassName";
 
     /**
@@ -136,7 +138,7 @@ public class BasicDataSourceFactory implements ObjectFactory {
     private static final String SILENT_PROP_SINGLETON = "singleton";
     private static final String SILENT_PROP_AUTH = "auth";
 
-    private static final String[] ALL_PROPERTIES = {PROP_DEFAULT_AUTO_COMMIT, PROP_DEFAULT_READ_ONLY,
+    private static final List<String> ALL_PROPERTY_NAMES = Arrays.asList(PROP_DEFAULT_AUTO_COMMIT, PROP_DEFAULT_READ_ONLY,
             PROP_DEFAULT_TRANSACTION_ISOLATION, PROP_DEFAULT_CATALOG, PROP_DEFAULT_SCHEMA, PROP_CACHE_STATE,
             PROP_DRIVER_CLASS_NAME, PROP_LIFO, PROP_MAX_TOTAL, PROP_MAX_IDLE, PROP_MIN_IDLE, PROP_INITIAL_SIZE,
             PROP_MAX_WAIT_MILLIS, PROP_TEST_ON_CREATE, PROP_TEST_ON_BORROW, PROP_TEST_ON_RETURN,
@@ -149,7 +151,7 @@ public class BasicDataSourceFactory implements ObjectFactory {
             PROP_MAX_OPEN_PREPARED_STATEMENTS, PROP_CONNECTION_PROPERTIES, PROP_MAX_CONN_LIFETIME_MILLIS,
             PROP_LOG_EXPIRED_CONNECTIONS, PROP_ROLLBACK_ON_RETURN, PROP_ENABLE_AUTO_COMMIT_ON_RETURN,
             PROP_DEFAULT_QUERY_TIMEOUT, PROP_FAST_FAIL_VALIDATION, PROP_DISCONNECTION_SQL_CODES, PROP_JMX_NAME,
-            PROP_CONNECTION_FACTORY_CLASS_NAME };
+            PROP_REGISTER_CONNECTION_MBEAN, PROP_CONNECTION_FACTORY_CLASS_NAME);
 
     /**
      * Obsolete properties from DBCP 1.x. with warning strings suggesting new properties. LinkedHashMap will guarantee
@@ -215,10 +217,10 @@ public class BasicDataSourceFactory implements ObjectFactory {
      * @param properties
      *            The data source configuration properties.
      * @return A new a {@link BasicDataSource} instance based on the given properties.
-     * @throws Exception
+     * @throws SQLException
      *             Thrown when an error occurs creating the data source.
      */
-    public static BasicDataSource createDataSource(final Properties properties) throws Exception {
+    public static BasicDataSource createDataSource(final Properties properties) throws SQLException {
         final BasicDataSource dataSource = new BasicDataSource();
         acceptBoolean(properties, PROP_DEFAULT_AUTO_COMMIT, dataSource::setDefaultAutoCommit);
         acceptBoolean(properties, PROP_DEFAULT_READ_ONLY, dataSource::setDefaultReadOnly);
@@ -295,6 +297,7 @@ public class BasicDataSourceFactory implements ObjectFactory {
         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_REGISTER_CONNECTION_MBEAN, dataSource::setRegisterConnectionMBean);
         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);
@@ -319,14 +322,18 @@ public class BasicDataSourceFactory implements ObjectFactory {
     /**
      * Parse properties from the string. Format of the string must be [propertyName=property;]*
      *
-     * @param propText The string containing the properties
-     * @return Properties
-     * @throws IOException If the properties string is not correctly formatted
+     * @param propText The source text
+     * @return Properties A new Properties instance
+     * @throws SQLException When a paring exception occurs
      */
-    private static Properties getProperties(final String propText) throws IOException {
+    private static Properties getProperties(final String propText) throws SQLException {
         final Properties p = new Properties();
         if (propText != null) {
-            p.load(new ByteArrayInputStream(propText.replace(';', '\n').getBytes(StandardCharsets.ISO_8859_1)));
+            try {
+                p.load(new ByteArrayInputStream(propText.replace(';', '\n').getBytes(StandardCharsets.ISO_8859_1)));
+            } catch (IOException e) {
+                throw new SQLException(propText, e);
+            }
         }
         return p;
     }
@@ -350,28 +357,28 @@ public class BasicDataSourceFactory implements ObjectFactory {
     }
 
     /**
-     * Creates and return a new <code>BasicDataSource</code> instance. If no instance can be created, return
-     * <code>null</code> instead.
+     * Creates and return a new {@code BasicDataSource} instance. If no instance can be created, return
+     * {@code null} instead.
      *
      * @param obj
      *            The possibly null object containing location or reference information that can be used in creating an
      *            object
      * @param name
-     *            The name of this object relative to <code>nameCtx</code>
+     *            The name of this object relative to {@code nameCtx}
      * @param nameCtx
-     *            The context relative to which the <code>name</code> parameter is specified, or <code>null</code> if
-     *            <code>name</code> is relative to the default initial context
+     *            The context relative to which the {@code name} parameter is specified, or {@code null} if
+     *            {@code name} is relative to the default initial context
      * @param environment
      *            The possibly null environment that is used in creating this object
      *
-     * @throws Exception
+     * @throws SQLException
      *             if an exception occurs creating the instance
      */
     @Override
     public Object getObjectInstance(final Object obj, final Name name, final Context nameCtx,
-            final Hashtable<?, ?> environment) throws Exception {
+            final Hashtable<?, ?> environment) throws SQLException {
 
-        // We only know how to deal with <code>javax.naming.Reference</code>s
+        // We only know how to deal with {@code javax.naming.Reference}s
         // that specify a class name of "javax.sql.DataSource"
         if (obj == null || !(obj instanceof Reference)) {
             return null;
@@ -389,12 +396,12 @@ public class BasicDataSourceFactory implements ObjectFactory {
         infoMessages.forEach(log::info);
 
         final Properties properties = new Properties();
-        for (final String propertyName : ALL_PROPERTIES) {
+        ALL_PROPERTY_NAMES.forEach(propertyName -> {
             final RefAddr ra = ref.get(propertyName);
             if (ra != null) {
                 properties.setProperty(propertyName, Objects.toString(ra.getContent(), null));
             }
-        }
+        });
 
         return createDataSource(properties);
     }
@@ -413,21 +420,19 @@ public class BasicDataSourceFactory implements ObjectFactory {
      *            container for info messages
      */
     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 List<String> infoMessages) {
         final String nameString = name != null ? "Name = " + name.toString() + " " : "";
         if (NUPROP_WARNTEXT != null && !NUPROP_WARNTEXT.isEmpty()) {
-            for (final String propertyName : NUPROP_WARNTEXT.keySet()) {
+            NUPROP_WARNTEXT.forEach((propertyName, value) -> {
                 final RefAddr ra = ref.get(propertyName);
-                if (ra != null && !allPropsAsList.contains(ra.getType())) {
+                if (ra != null && !ALL_PROPERTY_NAMES.contains(ra.getType())) {
                     final StringBuilder stringBuilder = new StringBuilder(nameString);
                     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.");
+                    stringBuilder.append(value).append(" You have set value of \"").append(propertyValue).append("\" for \"").append(propertyName)
+                        .append("\" property, which is being ignored.");
                     warnMessages.add(stringBuilder.toString());
                 }
-            }
+            });
         }
 
         final Enumeration<RefAddr> allRefAddrs = ref.getAll();
@@ -436,12 +441,11 @@ public class BasicDataSourceFactory implements ObjectFactory {
             final String propertyName = ra.getType();
             // If property name is not in the properties list, we haven't warned on it
             // 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))) {
+            if (!(ALL_PROPERTY_NAMES.contains(propertyName) || NUPROP_WARNTEXT.containsKey(propertyName) || SILENT_PROPERTIES.contains(propertyName))) {
                 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");
+                stringBuilder.append("Ignoring unknown property: ").append("value of \"").append(propertyValue).append("\" for \"").append(propertyName)
+                    .append("\" property");
                 infoMessages.add(stringBuilder.toString());
             }
         }
diff --git a/java/org/apache/tomcat/dbcp/dbcp2/ConnectionFactoryFactory.java b/java/org/apache/tomcat/dbcp/dbcp2/ConnectionFactoryFactory.java
index bd5c244b3a..01d5894c79 100644
--- a/java/org/apache/tomcat/dbcp/dbcp2/ConnectionFactoryFactory.java
+++ b/java/org/apache/tomcat/dbcp/dbcp2/ConnectionFactoryFactory.java
@@ -25,7 +25,7 @@ import java.util.Properties;
  *
  * @since 2.7.0
  */
-class ConnectionFactoryFactory {
+final class ConnectionFactoryFactory {
 
     /**
      * Creates a new {@link DriverConnectionFactory} allowing for an override through
@@ -46,14 +46,14 @@ class ConnectionFactoryFactory {
         if (user != null) {
             connectionProperties.put(Constants.KEY_USER, user);
         } else {
-            basicDataSource.log("DBCP DataSource configured without a 'username'");
+            basicDataSource.log(String.format("DBCP DataSource configured without a '%s'", Constants.KEY_USER));
         }
 
         final String pwd = basicDataSource.getPassword();
         if (pwd != null) {
-            connectionProperties.put("password", pwd);
+            connectionProperties.put(Constants.KEY_PASSWORD, pwd);
         } else {
-            basicDataSource.log("DBCP DataSource configured without a 'password'");
+            basicDataSource.log(String.format("DBCP DataSource configured without a '%s'", Constants.KEY_PASSWORD));
         }
         final String connectionFactoryClassName = basicDataSource.getConnectionFactoryClassName();
         if (connectionFactoryClassName != null) {
diff --git a/java/org/apache/tomcat/dbcp/dbcp2/DataSourceConnectionFactory.java b/java/org/apache/tomcat/dbcp/dbcp2/DataSourceConnectionFactory.java
index 3ab0a7a4e6..334bfbf5b2 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 == null ? null : userPassword.clone();
+        return Utils.clone(userPassword);
     }
 }
diff --git a/java/org/apache/tomcat/dbcp/dbcp2/DelegatingConnection.java b/java/org/apache/tomcat/dbcp/dbcp2/DelegatingConnection.java
index ebac9834cd..6394875366 100644
--- a/java/org/apache/tomcat/dbcp/dbcp2/DelegatingConnection.java
+++ b/java/org/apache/tomcat/dbcp/dbcp2/DelegatingConnection.java
@@ -25,7 +25,6 @@ import java.sql.Connection;
 import java.sql.DatabaseMetaData;
 import java.sql.NClob;
 import java.sql.PreparedStatement;
-import java.sql.ResultSet;
 import java.sql.SQLClientInfoException;
 import java.sql.SQLException;
 import java.sql.SQLWarning;
@@ -108,7 +107,7 @@ public class DelegatingConnection<C extends Connection> extends AbandonedTrace i
                 String label = "";
                 try {
                     label = connection.toString();
-                } catch (final Exception ex) {
+                } catch (final Exception ignored) {
                     // ignore, leave label empty
                 }
                 throw new SQLException("Connection " + label + " is closed.");
@@ -118,7 +117,7 @@ public class DelegatingConnection<C extends Connection> extends AbandonedTrace i
     }
 
     /**
-     * Can be used to clear cached state when it is known that the underlying connection may have been accessed
+     * Clears the cached state. Call when you known that the underlying connection may have been accessed
      * directly.
      */
     public void clearCachedState() {
@@ -145,9 +144,9 @@ public class DelegatingConnection<C extends Connection> extends AbandonedTrace i
      * Closes the underlying connection, and close any Statements that were not explicitly closed. Sub-classes that
      * override this method must:
      * <ol>
-     * <li>Call passivate()</li>
+     * <li>Call {@link #passivate()}</li>
      * <li>Call close (or the equivalent appropriate action) on the wrapped connection</li>
-     * <li>Set _closed to <code>false</code></li>
+     * <li>Set {@code closed} to {@code false}</li>
      * </ol>
      */
     @Override
@@ -359,7 +358,7 @@ public class DelegatingConnection<C extends Connection> extends AbandonedTrace i
 
     /**
      * 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.
+     * {@code null} means that the driver default will be used.
      *
      * @return query timeout limit in seconds; zero means there is no limit.
      * @deprecated Use {@link #getDefaultQueryTimeoutDuration()}.
@@ -371,7 +370,7 @@ public class DelegatingConnection<C extends Connection> extends AbandonedTrace i
 
     /**
      * 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.
+     * {@code null} means that the driver default will be used.
      *
      * @return query timeout limit; zero means there is no limit.
      * @since 2.10.0
@@ -556,7 +555,7 @@ public class DelegatingConnection<C extends Connection> extends AbandonedTrace i
      *
      * @param c
      *            connection to compare innermost delegate with
-     * @return true if innermost delegate equals <code>c</code>
+     * @return true if innermost delegate equals {@code c}
      */
     public boolean innermostDelegateEquals(final Connection c) {
         final Connection innerCon = getInnermostDelegateInternal();
@@ -646,26 +645,10 @@ public class DelegatingConnection<C extends Connection> extends AbandonedTrace i
         // The JDBC specification requires that a Connection close any open
         // Statement's when it is closed.
         // DBCP-288. Not all the traced objects will be statements
-        final List<AbandonedTrace> traces = getTrace();
-        if (traces != null && !traces.isEmpty()) {
+        final List<AbandonedTrace> traceList = getTrace();
+        if (!Utils.isEmpty(traceList)) {
             final List<Exception> thrownList = new ArrayList<>();
-            for (final Object trace : traces) {
-                if (trace instanceof Statement) {
-                    try {
-                        ((Statement) trace).close();
-                    } catch (final Exception e) {
-                        thrownList.add(e);
-                    }
-                } else if (trace instanceof ResultSet) {
-                    // DBCP-265: Need to close the result sets that are
-                    // generated via DatabaseMetaData
-                    try {
-                        ((ResultSet) trace).close();
-                    } catch (final Exception e) {
-                        thrownList.add(e);
-                    }
-                }
-            }
+            traceList.forEach(trace -> trace.close(thrownList::add));
             clearTrace();
             if (!thrownList.isEmpty()) {
                 throw new SQLExceptionList(thrownList);
@@ -879,7 +862,7 @@ public class DelegatingConnection<C extends Connection> extends AbandonedTrace i
 
     /**
      * 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.
+     * {@code null} means that the driver default will be used.
      *
      * @param defaultQueryTimeoutDuration
      *            the new query timeout limit Duration; zero means there is no limit.
@@ -891,7 +874,7 @@ public class DelegatingConnection<C extends Connection> extends AbandonedTrace i
 
     /**
      * 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.
+     * {@code null} means that the driver default will be used.
      *
      * @param defaultQueryTimeoutSeconds
      *            the new query timeout limit in seconds; zero means there is no limit.
@@ -1026,7 +1009,7 @@ public class DelegatingConnection<C extends Connection> extends AbandonedTrace i
                         str = sb.toString();
                     }
                 }
-            } catch (final SQLException ex) {
+            } catch (final SQLException ignored) {
                 // Ignore
             }
         }
diff --git a/java/org/apache/tomcat/dbcp/dbcp2/DelegatingPreparedStatement.java b/java/org/apache/tomcat/dbcp/dbcp2/DelegatingPreparedStatement.java
index 38502503fc..4ecf26f891 100644
--- a/java/org/apache/tomcat/dbcp/dbcp2/DelegatingPreparedStatement.java
+++ b/java/org/apache/tomcat/dbcp/dbcp2/DelegatingPreparedStatement.java
@@ -35,7 +35,9 @@ import java.sql.SQLXML;
 import java.sql.Statement;
 import java.sql.Time;
 import java.sql.Timestamp;
+import java.util.ArrayList;
 import java.util.Calendar;
+import java.util.List;
 
 /**
  * A base delegating implementation of {@link PreparedStatement}.
@@ -690,4 +692,25 @@ public class DelegatingPreparedStatement extends DelegatingStatement implements
         final Statement statement = getDelegate();
         return statement == null ? "NULL" : statement.toString();
     }
+
+    protected void prepareToReturn() throws SQLException {
+        setClosedInternal(true);
+        removeThisTrace(getConnectionInternal());
+
+        // The JDBC spec requires that a statement close any open
+        // ResultSet's when it is closed.
+        // FIXME The PreparedStatement we're wrapping should handle this for us.
+        // See DBCP-10 for what could happen when ResultSets are closed twice.
+        final List<AbandonedTrace> traceList = getTrace();
+        if (traceList != null) {
+            final List<Exception> thrownList = new ArrayList<>();
+            traceList.forEach(trace -> trace.close(thrownList::add));
+            clearTrace();
+            if (!thrownList.isEmpty()) {
+                throw new SQLExceptionList(thrownList);
+            }
+        }
+
+        super.passivate();
+    }
 }
diff --git a/java/org/apache/tomcat/dbcp/dbcp2/DelegatingStatement.java b/java/org/apache/tomcat/dbcp/dbcp2/DelegatingStatement.java
index 3f3481ba68..642d633b2c 100644
--- a/java/org/apache/tomcat/dbcp/dbcp2/DelegatingStatement.java
+++ b/java/org/apache/tomcat/dbcp/dbcp2/DelegatingStatement.java
@@ -137,35 +137,24 @@ public class DelegatingStatement extends AbandonedTrace implements Statement {
             // ResultSet's when it is closed.
             // FIXME The PreparedStatement we're wrapping should handle this for us.
             // See bug 17301 for what could happen when ResultSets are closed twice.
-            final List<AbandonedTrace> resultSetList = getTrace();
-            if (resultSetList != null) {
-                final ResultSet[] resultSets = resultSetList.toArray(Utils.EMPTY_RESULT_SET_ARRAY);
-                for (final ResultSet resultSet : resultSets) {
-                    if (resultSet != null) {
-                        try {
-                            resultSet.close();
-                        } catch (final Exception e) {
-                            if (connection != null) {
-                                // Does not rethrow e.
-                                connection.handleExceptionNoThrow(e);
-                            }
-                            thrownList.add(e);
-                        }
-                    }
-                }
-                clearTrace();
-            }
-            if (statement != null) {
-                try {
-                    statement.close();
-                } catch (final Exception e) {
+            final List<AbandonedTrace> traceList = getTrace();
+            if (traceList != null) {
+                traceList.forEach(trace -> trace.close(e -> {
                     if (connection != null) {
                         // Does not rethrow e.
                         connection.handleExceptionNoThrow(e);
                     }
                     thrownList.add(e);
-                }
+                }));
+                clearTrace();
             }
+            Utils.close(statement, e -> {
+                if (connection != null) {
+                    // Does not rethrow e.
+                    connection.handleExceptionNoThrow(e);
+                }
+                thrownList.add(e);
+            });
         } finally {
             closed = true;
             statement = null;
diff --git a/java/org/apache/tomcat/dbcp/dbcp2/DriverFactory.java b/java/org/apache/tomcat/dbcp/dbcp2/DriverFactory.java
index daea05de80..bacc31f41c 100644
--- a/java/org/apache/tomcat/dbcp/dbcp2/DriverFactory.java
+++ b/java/org/apache/tomcat/dbcp/dbcp2/DriverFactory.java
@@ -25,7 +25,7 @@ import java.sql.SQLException;
  *
  * @since 2.7.0
  */
-class DriverFactory {
+final class DriverFactory {
 
     static Driver createDriver(final BasicDataSource basicDataSource) throws SQLException {
         // Load the JDBC driver class
diff --git a/java/org/apache/tomcat/dbcp/dbcp2/DriverManagerConnectionFactory.java b/java/org/apache/tomcat/dbcp/dbcp2/DriverManagerConnectionFactory.java
index 3f92125234..56075855f0 100644
--- a/java/org/apache/tomcat/dbcp/dbcp2/DriverManagerConnectionFactory.java
+++ b/java/org/apache/tomcat/dbcp/dbcp2/DriverManagerConnectionFactory.java
@@ -49,7 +49,7 @@ public class DriverManagerConnectionFactory implements ConnectionFactory {
      * Constructor for DriverManagerConnectionFactory.
      *
      * @param connectionUri
-     *            a database url of the form <code> jdbc:<em>subprotocol</em>:<em>subname</em></code>
+     *            a database connection string of the form {@code  jdbc:<em>subprotocol</em>:<em>subname</em>}
      * @since 2.2
      */
     public DriverManagerConnectionFactory(final String connectionUri) {
@@ -63,7 +63,7 @@ public class DriverManagerConnectionFactory implements ConnectionFactory {
      * Constructor for DriverManagerConnectionFactory.
      *
      * @param connectionUri
-     *            a database url of the form <code> jdbc:<em>subprotocol</em>:<em>subname</em></code>
+     *            a database connection string of the form {@code  jdbc:<em>subprotocol</em>:<em>subname</em>}
      * @param properties
      *            a list of arbitrary string tag/value pairs as connection arguments; normally at least a "user" and
      *            "password" property should be included.
@@ -79,7 +79,7 @@ public class DriverManagerConnectionFactory implements ConnectionFactory {
      * Constructor for DriverManagerConnectionFactory.
      *
      * @param connectionUri
-     *            a database url of the form <code>jdbc:<em>subprotocol</em>:<em>subname</em></code>
+     *            a database connection string of the form {@code jdbc:<em>subprotocol</em>:<em>subname</em>}
      * @param userName
      *            the database user
      * @param userPassword
@@ -97,7 +97,7 @@ public class DriverManagerConnectionFactory implements ConnectionFactory {
      * Constructor for DriverManagerConnectionFactory.
      *
      * @param connectionUri
-     *            a database url of the form <code>jdbc:<em>subprotocol</em>:<em>subname</em></code>
+     *            a database connection string of the form {@code jdbc:<em>subprotocol</em>:<em>subname</em>}
      * @param userName
      *            the database user
      * @param userPassword
diff --git a/java/org/apache/tomcat/dbcp/dbcp2/Jdbc41Bridge.java b/java/org/apache/tomcat/dbcp/dbcp2/Jdbc41Bridge.java
index 42f29c450d..baf1a2a596 100644
--- a/java/org/apache/tomcat/dbcp/dbcp2/Jdbc41Bridge.java
+++ b/java/org/apache/tomcat/dbcp/dbcp2/Jdbc41Bridge.java
@@ -458,7 +458,7 @@ public class Jdbc41Bridge {
             throws SQLException {
         try {
             connection.setNetworkTimeout(executor, milliseconds);
-        } catch (final AbstractMethodError e) {
+        } catch (final AbstractMethodError ignored) {
             // do nothing
         }
     }
@@ -480,7 +480,7 @@ public class Jdbc41Bridge {
     public static void setSchema(final Connection connection, final String schema) throws SQLException {
         try {
             connection.setSchema(schema);
-        } catch (final AbstractMethodError e) {
+        } catch (final AbstractMethodError ignored) {
             // do nothing
         }
     }
diff --git a/java/org/apache/tomcat/dbcp/dbcp2/LifetimeExceededException.java b/java/org/apache/tomcat/dbcp/dbcp2/LifetimeExceededException.java
index dd2bbdd7e1..24c0e8ba1e 100644
--- a/java/org/apache/tomcat/dbcp/dbcp2/LifetimeExceededException.java
+++ b/java/org/apache/tomcat/dbcp/dbcp2/LifetimeExceededException.java
@@ -16,28 +16,29 @@
  */
 package org.apache.tomcat.dbcp.dbcp2;
 
+import java.sql.SQLException;
+
 /**
  * Exception thrown when a connection's maximum lifetime has been exceeded.
  *
  * @since 2.1
  */
-class LifetimeExceededException extends Exception {
+final class LifetimeExceededException extends SQLException {
 
     private static final long serialVersionUID = -3783783104516492659L;
 
     /**
-     * Create a LifetimeExceededException.
+     * Constructs a new instance.
      */
     public LifetimeExceededException() {
     }
 
     /**
-     * Create a LifetimeExceededException with the given message.
+     * Constructs a new instance with the given message.
      *
-     * @param message
-     *            The message with which to create the exception
+     * @param reason a description of the exception
      */
-    public LifetimeExceededException(final String message) {
-        super(message);
+    public LifetimeExceededException(final String reason) {
+        super(reason);
     }
 }
diff --git a/java/org/apache/tomcat/dbcp/dbcp2/LocalStrings.properties b/java/org/apache/tomcat/dbcp/dbcp2/LocalStrings.properties
index 3aaa50bc31..cf0efed352 100644
--- a/java/org/apache/tomcat/dbcp/dbcp2/LocalStrings.properties
+++ b/java/org/apache/tomcat/dbcp/dbcp2/LocalStrings.properties
@@ -13,7 +13,7 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
-connectionFactory.lifetimeExceeded=The lifetime of the connection [{0}] milliseconds exceeds the maximum permitted value of [{1}] milliseconds
+connectionFactory.lifetimeExceeded=The lifetime of the connection [{0}] exceeds the maximum permitted value of [{1}].
 
 pool.close.fail=Cannot close connection pool.
 
diff --git a/java/org/apache/tomcat/dbcp/dbcp2/ObjectNameWrapper.java b/java/org/apache/tomcat/dbcp/dbcp2/ObjectNameWrapper.java
index 340517d9bd..f882245fb7 100644
--- a/java/org/apache/tomcat/dbcp/dbcp2/ObjectNameWrapper.java
+++ b/java/org/apache/tomcat/dbcp/dbcp2/ObjectNameWrapper.java
@@ -31,7 +31,7 @@ import org.apache.juli.logging.LogFactory;
  *
  * @since 2.2.1
  */
-class ObjectNameWrapper {
+final class ObjectNameWrapper {
 
     private static final Log log = LogFactory.getLog(ObjectNameWrapper.class);
 
diff --git a/java/org/apache/tomcat/dbcp/dbcp2/PStmtKey.java b/java/org/apache/tomcat/dbcp/dbcp2/PStmtKey.java
index 87991e7e7c..64e261b7f0 100644
--- a/java/org/apache/tomcat/dbcp/dbcp2/PStmtKey.java
+++ b/java/org/apache/tomcat/dbcp/dbcp2/PStmtKey.java
@@ -21,6 +21,7 @@ import java.sql.SQLException;
 import java.sql.Statement;
 import java.util.Arrays;
 import java.util.Objects;
+import java.util.function.Function;
 
 import org.apache.tomcat.dbcp.dbcp2.PoolingConnection.StatementType;
 
@@ -31,101 +32,34 @@ import org.apache.tomcat.dbcp.dbcp2.PoolingConnection.StatementType;
  */
 public class PStmtKey {
 
-    /**
-     * Builder for prepareCall(String sql).
-     */
-    private class PreparedCallSQL implements StatementBuilder {
-        @Override
-        public Statement createStatement(final Connection connection) throws SQLException {
-            return connection.prepareCall(sql);
-        }
-    }
-
-    /**
-     * Builder for prepareCall(String sql, int resultSetType, int resultSetConcurrency).
-     */
-    private class PreparedCallWithResultSetConcurrency implements StatementBuilder {
-        @Override
-        public Statement createStatement(final Connection connection) throws SQLException {
-            return connection.prepareCall(sql, resultSetType.intValue(), resultSetConcurrency.intValue());
-        }
-    }
-
-    /**
-     * Builder for prepareCall(String sql, int resultSetType, int resultSetConcurrency, int resultSetHoldability).
-     */
-    private class PreparedCallWithResultSetHoldability implements StatementBuilder {
-        @Override
-        public Statement createStatement(final Connection connection) throws SQLException {
-            return connection.prepareCall(sql, resultSetType.intValue(), resultSetConcurrency.intValue(), resultSetHoldability.intValue());
-        }
-    }
-
-    /**
-     * Builder for prepareStatement(String sql).
-     */
-    private class PreparedStatementSQL implements StatementBuilder {
-        @Override
-        public Statement createStatement(final Connection connection) throws SQLException {
-            return connection.prepareStatement(sql);
-        }
-    }
-
-    /**
-     * Builder for prepareStatement(String sql, int autoGeneratedKeys).
-     */
-    private class PreparedStatementWithAutoGeneratedKeys implements StatementBuilder {
-        @Override
-        public Statement createStatement(final Connection connection) throws SQLException {
-            return connection.prepareStatement(sql, autoGeneratedKeys.intValue());
-        }
-    }
-
-    /**
-     * Builder for prepareStatement(String sql, int[] columnIndexes).
-     */
-    private class PreparedStatementWithColumnIndexes implements StatementBuilder {
-        @Override
-        public Statement createStatement(final Connection connection) throws SQLException {
-            return connection.prepareStatement(sql, columnIndexes);
-        }
-    }
-
-    /**
-     * Builder for prepareStatement(String sql, String[] columnNames).
-     */
-    private class PreparedStatementWithColumnNames implements StatementBuilder {
-        @Override
-        public Statement createStatement(final Connection connection) throws SQLException {
-            return connection.prepareStatement(sql, columnNames);
-        }
-    }
-
-    /**
-     * Builder for prepareStatement(String sql, int resultSetType, int resultSetConcurrency).
-     */
-    private class PreparedStatementWithResultSetConcurrency implements StatementBuilder {
-        @Override
-        public Statement createStatement(final Connection connection) throws SQLException {
-            return connection.prepareStatement(sql, resultSetType.intValue(), resultSetConcurrency.intValue());
-        }
-    }
-
-    /**
-     * Builder for prepareStatement(String sql, int resultSetType, int resultSetConcurrency, int resultSetHoldability).
-     */
-    private class PreparedStatementWithResultSetHoldability implements StatementBuilder {
-        @Override
-        public Statement createStatement(final Connection connection) throws SQLException {
-            return connection.prepareStatement(sql, resultSetType.intValue(), resultSetConcurrency.intValue(), resultSetHoldability.intValue());
-        }
-    }
-
     /**
      * Interface for Prepared or Callable Statement.
      */
+    @FunctionalInterface
     private interface StatementBuilder {
-        Statement createStatement(Connection connection) throws SQLException;
+        Statement createStatement(Connection connection, PStmtKey key) throws SQLException;
+    }
+
+    private static final StatementBuilder CallConcurrency = (c, k) -> c.prepareCall(k.sql, k.resultSetType.intValue(), k.resultSetConcurrency.intValue());
+    private static final StatementBuilder CallHoldability = (c, k) -> c.prepareCall(k.sql, k.resultSetType.intValue(), k.resultSetConcurrency.intValue(), k.resultSetHoldability.intValue());
+    private static final StatementBuilder CallSQL = (c, k) -> c.prepareCall(k.sql);
+    private static final StatementBuilder StatementAutoGeneratedKeys = (c, k) -> c.prepareStatement(k.sql, k.autoGeneratedKeys.intValue());
+    private static final StatementBuilder StatementColumnIndexes = (c, k) -> c.prepareStatement(k.sql, k.columnIndexes);
+    private static final StatementBuilder StatementColumnNames = (c, k) -> c.prepareStatement(k.sql, k.columnNames);
+    private static final StatementBuilder StatementConcurrency = (c, k) -> c.prepareStatement(k.sql, k.resultSetType.intValue(), k.resultSetConcurrency.intValue());
+    private static final StatementBuilder StatementHoldability = (c, k) -> c.prepareStatement(k.sql, k.resultSetType.intValue(), k.resultSetConcurrency.intValue(),
+        k.resultSetHoldability.intValue());
+    private static final StatementBuilder StatementSQL = (c, k) -> c.prepareStatement(k.sql);
+
+    private static StatementBuilder match(final StatementType statementType, final StatementBuilder prep, final StatementBuilder call) {
+        switch (Objects.requireNonNull(statementType, "statementType")) {
+        case PREPARED_STATEMENT:
+            return prep;
+        case CALLABLE_STATEMENT:
+            return call;
+        default:
+            throw new IllegalArgumentException(statementType.toString());
+        }
     }
 
     /**
@@ -134,32 +68,36 @@ public class PStmtKey {
     private final String sql;
 
     /**
-     * Result set type; one of <code>ResultSet.TYPE_FORWARD_ONLY</code>, <code>ResultSet.TYPE_SCROLL_INSENSITIVE</code>,
-     * or <code>ResultSet.TYPE_SCROLL_SENSITIVE</code>.
+     * Result set type; one of {@code ResultSet.TYPE_FORWARD_ONLY}, {@code ResultSet.TYPE_SCROLL_INSENSITIVE}, or
+     * {@code ResultSet.TYPE_SCROLL_SENSITIVE}.
      */
     private final Integer resultSetType;
 
     /**
-     * Result set concurrency. A concurrency type; one of <code>ResultSet.CONCUR_READ_ONLY</code> or
-     * <code>ResultSet.CONCUR_UPDATABLE</code>.
+     * Result set concurrency. A concurrency type; one of {@code ResultSet.CONCUR_READ_ONLY} or
+     * {@code ResultSet.CONCUR_UPDATABLE}.
      */
     private final Integer resultSetConcurrency;
 
     /**
-     * Result set holdability. One of the following <code>ResultSet</code> constants:
-     * <code>ResultSet.HOLD_CURSORS_OVER_COMMIT</code> or <code>ResultSet.CLOSE_CURSORS_AT_COMMIT</code>.
+     * Result set holdability. One of the following {@code ResultSet} constants: {@code ResultSet.HOLD_CURSORS_OVER_COMMIT}
+     * or {@code ResultSet.CLOSE_CURSORS_AT_COMMIT}.
      */
     private final Integer resultSetHoldability;
 
-    /** Database catalog. */
+    /**
+     * Database catalog.
+     */
     private final String catalog;
 
-    /** Database schema. */
+    /**
+     * Database schema.
+     */
     private final String schema;
 
     /**
-     * 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>.
+     * A flag indicating whether auto-generated keys should be returned; one of {@code Statement.RETURN_GENERATED_KEYS} or
+     * {@code Statement.NO_GENERATED_KEYS}.
      */
     private final Integer autoGeneratedKeys;
 
@@ -173,19 +111,20 @@ public class PStmtKey {
      */
     private final String[] columnNames;
 
+    /**
+     * Statement builder.
+     */
+    private final transient StatementBuilder statementBuilder;
+
     /**
      * Statement type, prepared or callable.
      */
     private final StatementType statementType;
 
-    /** Statement builder */
-    private transient StatementBuilder builder;
-
     /**
      * Constructs a key to uniquely identify a prepared statement.
      *
-     * @param sql
-     *            The SQL statement.
+     * @param sql The SQL statement.
      * @deprecated Use {@link #PStmtKey(String, String, String)}.
      */
     @Deprecated
@@ -196,14 +135,11 @@ public class PStmtKey {
     /**
      * Constructs a key to uniquely identify a prepared statement.
      *
-     * @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>.
-     * @param resultSetConcurrency
-     *            A concurrency type; one of <code>ResultSet.CONCUR_READ_ONLY</code> or
-     *            <code>ResultSet.CONCUR_UPDATABLE</code>.
+     * @param sql The SQL statement.
+     * @param resultSetType A result set type; one of {@code ResultSet.TYPE_FORWARD_ONLY},
+     *        {@code ResultSet.TYPE_SCROLL_INSENSITIVE}, or {@code ResultSet.TYPE_SCROLL_SENSITIVE}.
+     * @param resultSetConcurrency A concurrency type; one of {@code ResultSet.CONCUR_READ_ONLY} or
+     *        {@code ResultSet.CONCUR_UPDATABLE}.
      * @deprecated Use {@link #PStmtKey(String, String, String, int, int)}.
      */
     @Deprecated
@@ -214,10 +150,8 @@ public class PStmtKey {
     /**
      * Constructs a key to uniquely identify a prepared statement.
      *
-     * @param sql
-     *            The SQL statement.
-     * @param catalog
-     *            The catalog.
+     * @param sql The SQL statement.
+     * @param catalog The catalog.
      * @deprecated Use {@link #PStmtKey(String, String, String)}.
      */
     @Deprecated
@@ -228,13 +162,10 @@ public class PStmtKey {
     /**
      * Constructs a key to uniquely identify a prepared statement.
      *
-     * @param sql
-     *            The SQL statement.
-     * @param catalog
-     *            The catalog.
-     * @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>.
+     * @param sql The SQL statement.
+     * @param catalog The catalog.
+     * @param autoGeneratedKeys A flag indicating whether auto-generated keys should be returned; one of
+     *        {@code Statement.RETURN_GENERATED_KEYS} or {@code Statement.NO_GENERATED_KEYS}.
      * @deprecated Use {@link #PStmtKey(String, String, String, int)}.
      */
     @Deprecated
@@ -245,16 +176,12 @@ public class PStmtKey {
     /**
      * Constructs a key to uniquely identify a prepared statement.
      *
-     * @param sql
-     *            The SQL statement.
-     * @param catalog
-     *            The catalog.
-     * @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>.
-     * @param resultSetConcurrency
-     *            A concurrency type; one of <code>ResultSet.CONCUR_READ_ONLY</code> or
-     *            <code>ResultSet.CONCUR_UPDATABLE</code>.
+     * @param sql The SQL statement.
+     * @param catalog The catalog.
+     * @param resultSetType A result set type; one of {@code ResultSet.TYPE_FORWARD_ONLY},
+     *        {@code ResultSet.TYPE_SCROLL_INSENSITIVE}, or {@code ResultSet.TYPE_SCROLL_SENSITIVE}.
+     * @param resultSetConcurrency A concurrency type; one of {@code ResultSet.CONCUR_READ_ONLY} or
+     *        {@code ResultSet.CONCUR_UPDATABLE}.
      * @deprecated Use @link {@link #PStmtKey(String, String, String, int, int)}.
      */
     @Deprecated
@@ -265,209 +192,109 @@ public class PStmtKey {
     /**
      * Constructs a key to uniquely identify a prepared statement.
      *
-     * @param sql
-     *            The SQL statement.
-     * @param catalog
-     *            The catalog.
-     * @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>.
-     * @param resultSetConcurrency
-     *            A concurrency type; one of <code>ResultSet.CONCUR_READ_ONLY</code> or
-     *            <code>ResultSet.CONCUR_UPDATABLE</code>
-     * @param resultSetHoldability
-     *            One of the following <code>ResultSet</code> constants: <code>ResultSet.HOLD_CURSORS_OVER_COMMIT</code>
-     *            or <code>ResultSet.CLOSE_CURSORS_AT_COMMIT</code>.
+     * @param sql The SQL statement.
+     * @param catalog The catalog.
+     * @param resultSetType a result set type; one of {@code ResultSet.TYPE_FORWARD_ONLY},
+     *        {@code ResultSet.TYPE_SCROLL_INSENSITIVE}, or {@code ResultSet.TYPE_SCROLL_SENSITIVE}.
+     * @param resultSetConcurrency A concurrency type; one of {@code ResultSet.CONCUR_READ_ONLY} or
+     *        {@code ResultSet.CONCUR_UPDATABLE}
+     * @param resultSetHoldability One of the following {@code ResultSet} constants:
+     *        {@code ResultSet.HOLD_CURSORS_OVER_COMMIT} or {@code ResultSet.CLOSE_CURSORS_AT_COMMIT}.
      * @deprecated Use {@link #PStmtKey(String, String, String, int, int, int)}.
      */
     @Deprecated
-    public PStmtKey(final String sql, final String catalog, final int resultSetType, final int resultSetConcurrency,
-            final int resultSetHoldability) {
+    public PStmtKey(final String sql, final String catalog, final int resultSetType, final int resultSetConcurrency, final int resultSetHoldability) {
         this(sql, catalog, resultSetType, resultSetConcurrency, resultSetHoldability, StatementType.PREPARED_STATEMENT);
     }
 
     /**
      * Constructs a key to uniquely identify a prepared statement.
      *
-     * @param sql
-     *            The SQL statement.
-     * @param catalog
-     *            The catalog.
-     * @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>
-     * @param resultSetConcurrency
-     *            A concurrency type; one of <code>ResultSet.CONCUR_READ_ONLY</code> or
-     *            <code>ResultSet.CONCUR_UPDATABLE</code>.
-     * @param resultSetHoldability
-     *            One of the following <code>ResultSet</code> constants: <code>ResultSet.HOLD_CURSORS_OVER_COMMIT</code>
-     *            or <code>ResultSet.CLOSE_CURSORS_AT_COMMIT</code>.
-     * @param statementType
-     *            The SQL statement type, prepared or callable.
+     * @param sql The SQL statement.
+     * @param catalog The catalog.
+     * @param resultSetType a result set type; one of {@code ResultSet.TYPE_FORWARD_ONLY},
+     *        {@code ResultSet.TYPE_SCROLL_INSENSITIVE}, or {@code ResultSet.TYPE_SCROLL_SENSITIVE}
+     * @param resultSetConcurrency A concurrency type; one of {@code ResultSet.CONCUR_READ_ONLY} or
+     *        {@code ResultSet.CONCUR_UPDATABLE}.
+     * @param resultSetHoldability One of the following {@code ResultSet} constants:
+     *        {@code ResultSet.HOLD_CURSORS_OVER_COMMIT} or {@code ResultSet.CLOSE_CURSORS_AT_COMMIT}.
+     * @param statementType The SQL statement type, prepared or callable.
      * @deprecated Use {@link #PStmtKey(String, String, String, int, int, int, PoolingConnection.StatementType)}
      */
     @Deprecated
-    public PStmtKey(final String sql, final String catalog, final int resultSetType, final int resultSetConcurrency,
-            final int resultSetHoldability, final StatementType statementType) {
-        this.sql = sql;
-        this.catalog = catalog;
-        this.schema = null;
-        this.resultSetType = Integer.valueOf(resultSetType);
-        this.resultSetConcurrency = Integer.valueOf(resultSetConcurrency);
-        this.resultSetHoldability = Integer.valueOf(resultSetHoldability);
-        this.statementType = statementType;
-        this.autoGeneratedKeys = null;
-        this.columnIndexes = null;
-        this.columnNames = null;
-        // create builder
-        if (statementType == StatementType.PREPARED_STATEMENT) {
-            this.builder = new PreparedStatementWithResultSetHoldability();
-        } else if (statementType == StatementType.CALLABLE_STATEMENT) {
-            this.builder = new PreparedCallWithResultSetHoldability();
-        }
+    public PStmtKey(final String sql, final String catalog, final int resultSetType, final int resultSetConcurrency, final int resultSetHoldability,
+            final StatementType statementType) {
+        this(sql, catalog, null, Integer.valueOf(resultSetType), Integer.valueOf(resultSetConcurrency), Integer.valueOf(resultSetHoldability), null, null, null, statementType,
+            k -> match(statementType, StatementHoldability, CallHoldability));
     }
 
     /**
      * Constructs a key to uniquely identify a prepared statement.
      *
-     * @param sql
-     *            The SQL statement.
-     * @param catalog
-     *            The catalog.
-     * @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>.
-     * @param resultSetConcurrency
-     *            A concurrency type; one of <code>ResultSet.CONCUR_READ_ONLY</code> or
-     *            <code>ResultSet.CONCUR_UPDATABLE</code>.
-     * @param statementType
-     *            The SQL statement type, prepared or callable.
+     * @param sql The SQL statement.
+     * @param catalog The catalog.
+     * @param resultSetType A result set type; one of {@code ResultSet.TYPE_FORWARD_ONLY},
+     *        {@code ResultSet.TYPE_SCROLL_INSENSITIVE}, or {@code ResultSet.TYPE_SCROLL_SENSITIVE}.
+     * @param resultSetConcurrency A concurrency type; one of {@code ResultSet.CONCUR_READ_ONLY} or
+     *        {@code ResultSet.CONCUR_UPDATABLE}.
+     * @param statementType The SQL statement type, prepared or callable.
      * @deprecated Use {@link #PStmtKey(String, String, String, int, int, PoolingConnection.StatementType)}.
      */
     @Deprecated
-    public PStmtKey(final String sql, final String catalog, final int resultSetType, final int resultSetConcurrency,
-            final StatementType statementType) {
-        this.sql = sql;
-        this.catalog = catalog;
-        this.schema = null;
-        this.resultSetType = Integer.valueOf(resultSetType);
-        this.resultSetConcurrency = Integer.valueOf(resultSetConcurrency);
-        this.resultSetHoldability = null;
-        this.statementType = statementType;
-        this.autoGeneratedKeys = null;
-        this.columnIndexes = null;
-        this.columnNames = null;
-        // create builder
-        if (statementType == StatementType.PREPARED_STATEMENT) {
-            this.builder = new PreparedStatementWithResultSetConcurrency();
-        } else if (statementType == StatementType.CALLABLE_STATEMENT) {
-            this.builder = new PreparedCallWithResultSetConcurrency();
-        }
+    public PStmtKey(final String sql, final String catalog, final int resultSetType, final int resultSetConcurrency, final StatementType statementType) {
+        this(sql, catalog, null, Integer.valueOf(resultSetType), Integer.valueOf(resultSetConcurrency), null, null, null, null, statementType,
+            k -> match(statementType, StatementConcurrency, CallConcurrency));
     }
 
     /**
      * Constructs a key to uniquely identify a prepared statement.
      *
-     * @param sql
-     *            The SQL statement.
-     * @param catalog
-     *            The catalog.
-     * @param columnIndexes
-     *            An array of column indexes indicating the columns that should be returned from the inserted row or
-     *            rows.
+     * @param sql The SQL statement.
+     * @param catalog The catalog.
+     * @param columnIndexes An array of column indexes indicating the columns that should be returned from the inserted row
+     *        or rows.
      * @deprecated Use {@link #PStmtKey(String, String, String, int[])}.
      */
     @Deprecated
     public PStmtKey(final String sql, final String catalog, final int[] columnIndexes) {
-        this.sql = sql;
-        this.catalog = catalog;
-        this.schema = null;
-        this.statementType = StatementType.PREPARED_STATEMENT;
-        this.autoGeneratedKeys = null;
-        this.columnIndexes = columnIndexes == null ? null : Arrays.copyOf(columnIndexes, columnIndexes.length);
-        this.columnNames = null;
-        this.resultSetType = null;
-        this.resultSetConcurrency = null;
-        this.resultSetHoldability = null;
-        // create builder
-        this.builder = new PreparedStatementWithColumnIndexes();
+        this(sql, catalog, null, null, null, null, null, columnIndexes, null, StatementType.PREPARED_STATEMENT, StatementColumnIndexes);
     }
 
     /**
      * Constructs a key to uniquely identify a prepared statement.
      *
-     * @param sql
-     *            The SQL statement.
-     * @param catalog
-     *            The catalog.
-     * @param statementType
-     *            The SQL statement type, prepared or callable.
+     * @param sql The SQL statement.
+     * @param catalog The catalog.
+     * @param statementType The SQL statement type, prepared or callable.
      * @deprecated Use {@link #PStmtKey(String, String, String, PoolingConnection.StatementType)}.
      */
     @Deprecated
     public PStmtKey(final String sql, final String catalog, final StatementType statementType) {
-        this.sql = sql;
-        this.catalog = catalog;
-        this.schema = null;
-        this.statementType = statementType;
-        this.autoGeneratedKeys = null;
-        this.columnIndexes = null;
-        this.columnNames = null;
-        this.resultSetType = null;
-        this.resultSetConcurrency = null;
-        this.resultSetHoldability = null;
-        // create builder
-        if (statementType == StatementType.PREPARED_STATEMENT) {
-            this.builder = new PreparedStatementSQL();
-        } else if (statementType == StatementType.CALLABLE_STATEMENT) {
-            this.builder = new PreparedCallSQL();
-        }
+        this(sql, catalog, null, null, null, null, null, null, null, statementType, k -> match(statementType, StatementSQL, CallSQL));
     }
 
     /**
      * Constructs a key to uniquely identify a prepared statement.
      *
-     * @param sql
-     *            The SQL statement.
-     * @param catalog
-     *            The catalog.
-     * @param statementType
-     *            The SQL statement type, prepared or callable.
-     * @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>.
+     * @param sql The SQL statement.
+     * @param catalog The catalog.
+     * @param statementType The SQL statement type, prepared or callable.
+     * @param autoGeneratedKeys A flag indicating whether auto-generated keys should be returned; one of
+     *        {@code Statement.RETURN_GENERATED_KEYS} or {@code Statement.NO_GENERATED_KEYS}.
      * @deprecated Use {@link #PStmtKey(String, String, String, PoolingConnection.StatementType, Integer)}
      */
     @Deprecated
-    public PStmtKey(final String sql, final String catalog, final StatementType statementType,
-            final Integer autoGeneratedKeys) {
-        this.sql = sql;
-        this.catalog = catalog;
-        this.schema = null;
-        this.statementType = statementType;
-        this.autoGeneratedKeys = autoGeneratedKeys;
-        this.columnIndexes = null;
-        this.columnNames = null;
-        this.resultSetType = null;
-        this.resultSetConcurrency = null;
-        this.resultSetHoldability = null;
-        // create builder
-        if (statementType == StatementType.PREPARED_STATEMENT) {
-            this.builder = new PreparedStatementWithAutoGeneratedKeys();
-        } else if (statementType == StatementType.CALLABLE_STATEMENT) {
-            this.builder = new PreparedCallSQL();
-        }
+    public PStmtKey(final String sql, final String catalog, final StatementType statementType, final Integer autoGeneratedKeys) {
+        this(sql, catalog, null, null, null, null, autoGeneratedKeys, null, null, statementType,
+            k -> match(statementType, StatementAutoGeneratedKeys, CallSQL));
     }
 
     /**
      * Constructs a key to uniquely identify a prepared statement.
      *
-     * @param sql
-     *            The SQL statement.
-     * @param catalog
-     *            The catalog.
-     * @param schema
-     *            The schema
+     * @param sql The SQL statement.
+     * @param catalog The catalog.
+     * @param schema The schema
      * @since 2.5.0
      */
     public PStmtKey(final String sql, final String catalog, final String schema) {
@@ -477,15 +304,11 @@ public class PStmtKey {
     /**
      * Constructs a key to uniquely identify a prepared statement.
      *
-     * @param sql
-     *            The SQL statement.
-     * @param catalog
-     *            The catalog.
-     * @param schema
-     *            The schema
-     * @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>.
+     * @param sql The SQL statement.
+     * @param catalog The catalog.
+     * @param schema The schema
+     * @param autoGeneratedKeys A flag indicating whether auto-generated keys should be returned; one of
+     *        {@code Statement.RETURN_GENERATED_KEYS} or {@code Statement.NO_GENERATED_KEYS}.
      * @since 2.5.0
      */
     public PStmtKey(final String sql, final String catalog, final String schema, final int autoGeneratedKeys) {
@@ -495,18 +318,13 @@ public class PStmtKey {
     /**
      * Constructs a key to uniquely identify a prepared statement.
      *
-     * @param sql
-     *            The SQL statement.
-     * @param catalog
-     *            The catalog.
-     * @param schema
-     *            The schema
-     * @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>.
-     * @param resultSetConcurrency
-     *            A concurrency type; one of <code>ResultSet.CONCUR_READ_ONLY</code> or
-     *            <code>ResultSet.CONCUR_UPDATABLE</code>.
+     * @param sql The SQL statement.
+     * @param catalog The catalog.
+     * @param schema The schema
+     * @param resultSetType A result set type; one of {@code ResultSet.TYPE_FORWARD_ONLY},
+     *        {@code ResultSet.TYPE_SCROLL_INSENSITIVE}, or {@code ResultSet.TYPE_SCROLL_SENSITIVE}.
+     * @param resultSetConcurrency A concurrency type; one of {@code ResultSet.CONCUR_READ_ONLY} or
+     *        {@code ResultSet.CONCUR_UPDATABLE}.
      */
     public PStmtKey(final String sql, final String catalog, final String schema, final int resultSetType, final int resultSetConcurrency) {
         this(sql, catalog, schema, resultSetType, resultSetConcurrency, StatementType.PREPARED_STATEMENT);
@@ -515,21 +333,15 @@ public class PStmtKey {
     /**
      * Constructs a key to uniquely identify a prepared statement.
      *
-     * @param sql
-     *            The SQL statement.
-     * @param catalog
-     *            The catalog.
-     * @param schema
-     *            The schema
-     * @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>.
-     * @param resultSetConcurrency
-     *            A concurrency type; one of <code>ResultSet.CONCUR_READ_ONLY</code> or
-     *            <code>ResultSet.CONCUR_UPDATABLE</code>
-     * @param resultSetHoldability
-     *            One of the following <code>ResultSet</code> constants: <code>ResultSet.HOLD_CURSORS_OVER_COMMIT</code>
-     *            or <code>ResultSet.CLOSE_CURSORS_AT_COMMIT</code>.
+     * @param sql The SQL statement.
+     * @param catalog The catalog.
+     * @param schema The schema
+     * @param resultSetType a result set type; one of {@code ResultSet.TYPE_FORWARD_ONLY},
+     *        {@code ResultSet.TYPE_SCROLL_INSENSITIVE}, or {@code ResultSet.TYPE_SCROLL_SENSITIVE}.
+     * @param resultSetConcurrency A concurrency type; one of {@code ResultSet.CONCUR_READ_ONLY} or
+     *        {@code ResultSet.CONCUR_UPDATABLE}
+     * @param resultSetHoldability One of the following {@code ResultSet} constants:
+     *        {@code ResultSet.HOLD_CURSORS_OVER_COMMIT} or {@code ResultSet.CLOSE_CURSORS_AT_COMMIT}.
      * @since 2.5.0
      */
     public PStmtKey(final String sql, final String catalog, final String schema, final int resultSetType, final int resultSetConcurrency,
@@ -540,249 +352,163 @@ public class PStmtKey {
     /**
      * Constructs a key to uniquely identify a prepared statement.
      *
-     * @param sql
-     *            The SQL statement.
-     * @param catalog
-     *            The catalog.
-     * @param schema
-     *            The schema.
-     * @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>
-     * @param resultSetConcurrency
-     *            A concurrency type; one of <code>ResultSet.CONCUR_READ_ONLY</code> or
-     *            <code>ResultSet.CONCUR_UPDATABLE</code>.
-     * @param resultSetHoldability
-     *            One of the following <code>ResultSet</code> constants: <code>ResultSet.HOLD_CURSORS_OVER_COMMIT</code>
-     *            or <code>ResultSet.CLOSE_CURSORS_AT_COMMIT</code>.
-     * @param statementType
-     *            The SQL statement type, prepared or callable.
+     * @param sql The SQL statement.
+     * @param catalog The catalog.
+     * @param schema The schema.
+     * @param resultSetType a result set type; one of {@code ResultSet.TYPE_FORWARD_ONLY},
+     *        {@code ResultSet.TYPE_SCROLL_INSENSITIVE}, or {@code ResultSet.TYPE_SCROLL_SENSITIVE}
+     * @param resultSetConcurrency A concurrency type; one of {@code ResultSet.CONCUR_READ_ONLY} or
+     *        {@code ResultSet.CONCUR_UPDATABLE}.
+     * @param resultSetHoldability One of the following {@code ResultSet} constants:
+     *        {@code ResultSet.HOLD_CURSORS_OVER_COMMIT} or {@code ResultSet.CLOSE_CURSORS_AT_COMMIT}.
+     * @param statementType The SQL statement type, prepared or callable.
      * @since 2.5.0
      */
     public PStmtKey(final String sql, final String catalog, final String schema, final int resultSetType, final int resultSetConcurrency,
             final int resultSetHoldability, final StatementType statementType) {
-        this.sql = sql;
-        this.catalog = catalog;
-        this.schema = schema;
-        this.resultSetType = Integer.valueOf(resultSetType);
-        this.resultSetConcurrency = Integer.valueOf(resultSetConcurrency);
-        this.resultSetHoldability = Integer.valueOf(resultSetHoldability);
-        this.statementType = statementType;
-        this.autoGeneratedKeys = null;
-        this.columnIndexes = null;
-        this.columnNames = null;
-        // create builder
-        if (statementType == StatementType.PREPARED_STATEMENT) {
-            this.builder = new PreparedStatementWithResultSetHoldability();
-        } else if (statementType == StatementType.CALLABLE_STATEMENT) {
-            this.builder = new PreparedCallWithResultSetHoldability();
-        }
+        this(sql, catalog, schema, Integer.valueOf(resultSetType), Integer.valueOf(resultSetConcurrency), Integer.valueOf(resultSetHoldability), null, null, null, statementType,
+            k -> match(statementType, StatementHoldability, CallHoldability));
     }
 
     /**
      * Constructs a key to uniquely identify a prepared statement.
      *
-     * @param sql
-     *            The SQL statement.
-     * @param catalog
-     *            The catalog.
-     * @param schema
-     *            The schema.
-     * @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>.
-     * @param resultSetConcurrency
-     *            A concurrency type; one of <code>ResultSet.CONCUR_READ_ONLY</code> or
-     *            <code>ResultSet.CONCUR_UPDATABLE</code>.
-     * @param statementType
-     *            The SQL statement type, prepared or callable.
+     * @param sql The SQL statement.
+     * @param catalog The catalog.
+     * @param schema The schema.
+     * @param resultSetType A result set type; one of {@code ResultSet.TYPE_FORWARD_ONLY},
+     *        {@code ResultSet.TYPE_SCROLL_INSENSITIVE}, or {@code ResultSet.TYPE_SCROLL_SENSITIVE}.
+     * @param resultSetConcurrency A concurrency type; one of {@code ResultSet.CONCUR_READ_ONLY} or
+     *        {@code ResultSet.CONCUR_UPDATABLE}.
+     * @param statementType The SQL statement type, prepared or callable.
      * @since 2.5.0
      */
     public PStmtKey(final String sql, final String catalog, final String schema, final int resultSetType, final int resultSetConcurrency,
             final StatementType statementType) {
-        this.sql = sql;
-        this.catalog = catalog;
-        this.schema = schema;
-        this.resultSetType = Integer.valueOf(resultSetType);
-        this.resultSetConcurrency = Integer.valueOf(resultSetConcurrency);
-        this.resultSetHoldability = null;
-        this.statementType = statementType;
-        this.autoGeneratedKeys = null;
-        this.columnIndexes = null;
-        this.columnNames = null;
-        // create builder
-        if (statementType == StatementType.PREPARED_STATEMENT) {
-            this.builder = new PreparedStatementWithResultSetConcurrency();
-        } else if (statementType == StatementType.CALLABLE_STATEMENT) {
-            this.builder = new PreparedCallWithResultSetConcurrency();
-        }
+        this(sql, catalog, schema, Integer.valueOf(resultSetType), Integer.valueOf(resultSetConcurrency), null, null, null, null, statementType,
+            k -> match(statementType, StatementConcurrency, CallConcurrency));
     }
 
     /**
      * Constructs a key to uniquely identify a prepared statement.
      *
-     * @param sql
-     *            The SQL statement.
-     * @param catalog
-     *            The catalog.
-     * @param schema
-     *            The schema.
-     * @param columnIndexes
-     *            An array of column indexes indicating the columns that should be returned from the inserted row or
-     *            rows.
+     * @param sql The SQL statement.
+     * @param catalog The catalog.
+     * @param schema The schema.
+     * @param columnIndexes An array of column indexes indicating the columns that should be returned from the inserted row
+     *        or rows.
      */
     public PStmtKey(final String sql, final String catalog, final String schema, final int[] columnIndexes) {
+        this(sql, catalog, schema, null, null, null, null, columnIndexes, null, StatementType.PREPARED_STATEMENT, StatementColumnIndexes);
+    }
+
+    private PStmtKey(final String sql, final String catalog, final String schema, final Integer resultSetType, final Integer resultSetConcurrency,
+        final Integer resultSetHoldability, final Integer autoGeneratedKeys, final int[] columnIndexes, final String[] columnNames,
+        final StatementType statementType, final Function<PStmtKey, StatementBuilder> statementBuilder) {
+        this.sql = Objects.requireNonNull(sql, "sql").trim();
+        this.catalog = catalog;
+        this.schema = schema;
+        this.resultSetType = resultSetType;
+        this.resultSetConcurrency = resultSetConcurrency;
+        this.resultSetHoldability = resultSetHoldability;
+        this.autoGeneratedKeys = autoGeneratedKeys;
+        this.columnIndexes = clone(columnIndexes);
+        this.columnNames = clone(columnNames);
+        this.statementBuilder = Objects.requireNonNull(Objects.requireNonNull(statementBuilder, "statementBuilder").apply(this), "statementBuilder");
+        this.statementType = statementType;
+    }
+
+    // Root constructor.
+    private PStmtKey(final String sql, final String catalog, final String schema, final Integer resultSetType, final Integer resultSetConcurrency,
+        final Integer resultSetHoldability, final Integer autoGeneratedKeys, final int[] columnIndexes, final String[] columnNames,
+        final StatementType statementType, final StatementBuilder statementBuilder) {
         this.sql = sql;
         this.catalog = catalog;
         this.schema = schema;
-        this.statementType = StatementType.PREPARED_STATEMENT;
-        this.autoGeneratedKeys = null;
-        this.columnIndexes = columnIndexes == null ? null : Arrays.copyOf(columnIndexes, columnIndexes.length);
-        this.columnNames = null;
-        this.resultSetType = null;
-        this.resultSetConcurrency = null;
-        this.resultSetHoldability = null;
-        // create builder
-        this.builder = new PreparedStatementWithColumnIndexes();
+        this.resultSetType = resultSetType;
+        this.resultSetConcurrency = resultSetConcurrency;
+        this.resultSetHoldability = resultSetHoldability;
+        this.autoGeneratedKeys = autoGeneratedKeys;
+        this.columnIndexes = clone(columnIndexes);
+        this.columnNames = clone(columnNames);
+        this.statementBuilder = Objects.requireNonNull(statementBuilder, "statementBuilder");
+        this.statementType = statementType;
     }
 
     /**
      * Constructs a key to uniquely identify a prepared statement.
      *
-     * @param sql
-     *            The SQL statement.
-     * @param catalog
-     *            The catalog.
-     * @param schema
-     *            The schema.
-     * @param statementType
-     *            The SQL statement type, prepared or callable.
+     * @param sql The SQL statement.
+     * @param catalog The catalog.
+     * @param schema The schema.
+     * @param statementType The SQL statement type, prepared or callable.
      * @since 2.5.0
      */
     public PStmtKey(final String sql, final String catalog, final String schema, final StatementType statementType) {
-        this.sql = sql;
-        this.catalog = catalog;
-        this.schema = schema;
-        this.statementType = statementType;
-        this.autoGeneratedKeys = null;
-        this.columnIndexes = null;
-        this.columnNames = null;
-        this.resultSetType = null;
-        this.resultSetConcurrency = null;
-        this.resultSetHoldability = null;
-        // create builder
-        if (statementType == StatementType.PREPARED_STATEMENT) {
-            this.builder = new PreparedStatementSQL();
-        } else if (statementType == StatementType.CALLABLE_STATEMENT) {
-            this.builder = new PreparedCallSQL();
-        }
+        this(sql, catalog, schema, null, null, null, null, null, null, statementType, k -> match(statementType, StatementSQL, CallSQL));
     }
 
     /**
      * Constructs a key to uniquely identify a prepared statement.
      *
-     * @param sql
-     *            The SQL statement.
-     * @param catalog
-     *            The catalog.
-     * @param schema
-     *            The schema.
-     * @param statementType
-     *            The SQL statement type, prepared or callable.
-     * @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>.
+     * @param sql The SQL statement.
+     * @param catalog The catalog.
+     * @param schema The schema.
+     * @param statementType The SQL statement type, prepared or callable.
+     * @param autoGeneratedKeys A flag indicating whether auto-generated keys should be returned; one of
+     *        {@code Statement.RETURN_GENERATED_KEYS} or {@code Statement.NO_GENERATED_KEYS}.
      * @since 2.5.0
      */
-    public PStmtKey(final String sql, final String catalog, final String schema, final StatementType statementType,
-            final Integer autoGeneratedKeys) {
-        this.sql = sql;
-        this.catalog = catalog;
-        this.schema = schema;
-        this.statementType = statementType;
-        this.autoGeneratedKeys = autoGeneratedKeys;
-        this.columnIndexes = null;
-        this.columnNames = null;
-        this.resultSetType = null;
-        this.resultSetConcurrency = null;
-        this.resultSetHoldability = null;
-        // create builder
-        if (statementType == StatementType.PREPARED_STATEMENT) {
-            this.builder = new PreparedStatementWithAutoGeneratedKeys();
-        } else if (statementType == StatementType.CALLABLE_STATEMENT) {
-            this.builder = new PreparedCallSQL();
-        }
+    public PStmtKey(final String sql, final String catalog, final String schema, final StatementType statementType, final Integer autoGeneratedKeys) {
+        this(sql, catalog, schema, null, null, null, autoGeneratedKeys, null, null, statementType,
+            k -> match(statementType, StatementAutoGeneratedKeys, CallSQL));
     }
 
     /**
      * Constructs a key to uniquely identify a prepared statement.
      *
-     * @param sql
-     *            The SQL statement.
-     * @param catalog
-     *            The catalog.
-     * @param schema
-     *            The schema.
-     * @param columnNames
-     *            An array of column names indicating the columns that should be returned from the inserted row or rows.
+     * @param sql The SQL statement.
+     * @param catalog The catalog.
+     * @param schema The schema.
+     * @param columnNames An array of column names indicating the columns that should be returned from the inserted row or
+     *        rows.
      * @since 2.5.0
      */
     public PStmtKey(final String sql, final String catalog, final String schema, final String[] columnNames) {
-        this.sql = sql;
-        this.catalog = catalog;
-        this.schema = schema;
-        this.statementType = StatementType.PREPARED_STATEMENT;
-        this.autoGeneratedKeys = null;
-        this.columnIndexes = null;
-        this.columnNames = columnNames == null ? null : Arrays.copyOf(columnNames, columnNames.length);
-        this.resultSetType = null;
-        this.resultSetConcurrency = null;
-        this.resultSetHoldability = null;
-        // create builder
-        builder = new PreparedStatementWithColumnNames();
+        this(sql, catalog, schema, null, null, null, null, null, columnNames, StatementType.PREPARED_STATEMENT, StatementColumnNames);
     }
 
     /**
      * Constructs a key to uniquely identify a prepared statement.
      *
-     * @param sql
-     *            The SQL statement.
-     * @param catalog
-     *            The catalog.
-     * @param columnNames
-     *            An array of column names indicating the columns that should be returned from the inserted row or rows.
+     * @param sql The SQL statement.
+     * @param catalog The catalog.
+     * @param columnNames An array of column names indicating the columns that should be returned from the inserted row or
+     *        rows.
      * @deprecated Use {@link #PStmtKey(String, String, String, String[])}.
      */
     @Deprecated
     public PStmtKey(final String sql, final String catalog, final String[] columnNames) {
-        this.sql = sql;
-        this.catalog = catalog;
-        this.schema = null;
-        this.statementType = StatementType.PREPARED_STATEMENT;
-        this.autoGeneratedKeys = null;
-        this.columnIndexes = null;
-        this.columnNames = columnNames == null ? null : Arrays.copyOf(columnNames, columnNames.length);
-        this.resultSetType = null;
-        this.resultSetConcurrency = null;
-        this.resultSetHoldability = null;
-        // create builder
-        builder = new PreparedStatementWithColumnNames();
+        this(sql, catalog, null, null, null, null, null, null, columnNames, StatementType.PREPARED_STATEMENT, StatementColumnNames);
+    }
+
+    private int[] clone(final int[] array) {
+        return array == null ? null : array.clone();
+    }
+
+    private String[] clone(final String[] array) {
+        return array == null ? null : array.clone();
     }
 
     /**
      * Creates a new Statement from the given Connection.
      *
-     * @param connection
-     *            The Connection to use to create the statement.
+     * @param connection The Connection to use to create the statement.
      * @return The statement.
-     * @throws SQLException
-     *             Thrown when there is a problem creating the statement.
+     * @throws SQLException Thrown when there is a problem creating the statement.
      */
     public Statement createStatement(final Connection connection) throws SQLException {
-        if (builder == null) {
-            throw new IllegalStateException("Prepared statement key is invalid.");
-        }
-        return builder.createStatement(connection);
+        return statementBuilder.createStatement(connection, this);
     }
 
     @Override
@@ -828,8 +554,8 @@ public class PStmtKey {
     }
 
     /**
-     * Gets 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>.
+     * Gets a flag indicating whether auto-generated keys should be returned; one of {@code Statement.RETURN_GENERATED_KEYS}
+     * or {@code Statement.NO_GENERATED_KEYS}.
      *
      * @return a flag indicating whether auto-generated keys should be returned.
      */
@@ -838,7 +564,7 @@ public class PStmtKey {
     }
 
     /**
-     * The catalog.
+     * Gets the catalog.
      *
      * @return The catalog.
      */
@@ -852,7 +578,7 @@ public class PStmtKey {
      * @return An array of column indexes.
      */
     public int[] getColumnIndexes() {
-        return columnIndexes == null ? null : columnIndexes.clone();
+        return clone(columnIndexes);
     }
 
     /**
@@ -861,12 +587,12 @@ public class PStmtKey {
      * @return An array of column names.
      */
     public String[] getColumnNames() {
-        return columnNames == null ? null : columnNames.clone();
+        return clone(columnNames);
     }
 
     /**
-     * Gets the result set concurrency type; one of <code>ResultSet.CONCUR_READ_ONLY</code> or
-     * <code>ResultSet.CONCUR_UPDATABLE</code>.
+     * Gets the result set concurrency type; one of {@code ResultSet.CONCUR_READ_ONLY} or
+     * {@code ResultSet.CONCUR_UPDATABLE}.
      *
      * @return The result set concurrency type.
      */
@@ -875,8 +601,8 @@ public class PStmtKey {
     }
 
     /**
-     * Gets the result set holdability, one of the following <code>ResultSet</code> constants:
-     * <code>ResultSet.HOLD_CURSORS_OVER_COMMIT</code> or <code>ResultSet.CLOSE_CURSORS_AT_COMMIT</code>.
+     * Gets the result set holdability, one of the following {@code ResultSet} constants:
+     * {@code ResultSet.HOLD_CURSORS_OVER_COMMIT} or {@code ResultSet.CLOSE_CURSORS_AT_COMMIT}.
      *
      * @return The result set holdability.
      */
@@ -885,8 +611,8 @@ public class PStmtKey {
     }
 
     /**
-     * Gets the result set type, one of <code>ResultSet.TYPE_FORWARD_ONLY</code>,
-     * <code>ResultSet.TYPE_SCROLL_INSENSITIVE</code>, or <code>ResultSet.TYPE_SCROLL_SENSITIVE</code>.
+     * Gets the result set type, one of {@code ResultSet.TYPE_FORWARD_ONLY}, {@code ResultSet.TYPE_SCROLL_INSENSITIVE}, or
+     * {@code ResultSet.TYPE_SCROLL_SENSITIVE}.
      *
      * @return the result set type.
      */
@@ -895,7 +621,7 @@ public class PStmtKey {
     }
 
     /**
-     * The schema.
+     * Gets the schema.
      *
      * @return The catalog.
      */
@@ -913,7 +639,7 @@ public class PStmtKey {
     }
 
     /**
-     * The SQL statement type.
+     * Gets the SQL statement type.
      *
      * @return The SQL statement type.
      */
diff --git a/java/org/apache/tomcat/dbcp/dbcp2/PoolableCallableStatement.java b/java/org/apache/tomcat/dbcp/dbcp2/PoolableCallableStatement.java
index 16e27a18c8..447aa22264 100644
--- a/java/org/apache/tomcat/dbcp/dbcp2/PoolableCallableStatement.java
+++ b/java/org/apache/tomcat/dbcp/dbcp2/PoolableCallableStatement.java
@@ -18,10 +18,7 @@ package org.apache.tomcat.dbcp.dbcp2;
 
 import java.sql.CallableStatement;
 import java.sql.Connection;
-import java.sql.ResultSet;
 import java.sql.SQLException;
-import java.util.ArrayList;
-import java.util.List;
 
 import org.apache.tomcat.dbcp.pool2.KeyedObjectPool;
 
@@ -67,7 +64,7 @@ public class PoolableCallableStatement extends DelegatingCallableStatement {
 
         // Remove from trace now because this statement will be
         // added by the activate method.
-        removeThisTrace(getConnectionInternal());
+        removeThisTrace(connection);
     }
 
     /**
@@ -110,33 +107,7 @@ public class PoolableCallableStatement extends DelegatingCallableStatement {
      */
     @Override
     public void passivate() throws SQLException {
-        setClosedInternal(true);
-        removeThisTrace(getConnectionInternal());
-
-        // The JDBC spec requires that a statement close any open
-        // ResultSet's when it is closed.
-        // FIXME The PreparedStatement we're wrapping should handle this for us.
-        // See DBCP-10 for what could happen when ResultSets are closed twice.
-        final List<AbandonedTrace> resultSetList = getTrace();
-        if (resultSetList != null) {
-            final List<Exception> thrownList = new ArrayList<>();
-            final ResultSet[] resultSets = resultSetList.toArray(Utils.EMPTY_RESULT_SET_ARRAY);
-            for (final ResultSet resultSet : resultSets) {
-                if (resultSet != null) {
-                    try {
-                        resultSet.close();
-                    } catch (final Exception e) {
-                        thrownList.add(e);
-                    }
-                }
-            }
-            clearTrace();
-            if (!thrownList.isEmpty()) {
-                throw new SQLExceptionList(thrownList);
-            }
-        }
-
-        super.passivate();
+        prepareToReturn();
     }
 
 }
diff --git a/java/org/apache/tomcat/dbcp/dbcp2/PoolableConnection.java b/java/org/apache/tomcat/dbcp/dbcp2/PoolableConnection.java
index 52243c58c9..ee277db250 100644
--- a/java/org/apache/tomcat/dbcp/dbcp2/PoolableConnection.java
+++ b/java/org/apache/tomcat/dbcp/dbcp2/PoolableConnection.java
@@ -46,7 +46,7 @@ public class PoolableConnection extends DelegatingConnection<Connection> impleme
     static {
         try {
             MBEAN_SERVER = ManagementFactory.getPlatformMBeanServer();
-        } catch (final NoClassDefFoundError | Exception ex) {
+        } catch (final NoClassDefFoundError | Exception ignored) {
             // ignore - JMX not available
         }
     }
@@ -69,7 +69,7 @@ public class PoolableConnection extends DelegatingConnection<Connection> impleme
 
     /**
      * SQL_STATE codes considered to signal fatal conditions. Overrides the defaults in
-     * {@link Utils#DISCONNECTION_SQL_CODES} (plus anything starting with {@link Utils#DISCONNECTION_SQL_CODE_PREFIX}).
+     * {@link Utils#getDisconnectionSqlCodes()} (plus anything starting with {@link Utils#DISCONNECTION_SQL_CODE_PREFIX}).
      */
     private final Collection<String> disconnectionSqlCodes;
 
@@ -116,7 +116,7 @@ public class PoolableConnection extends DelegatingConnection<Connection> impleme
         if (jmxObjectName != null) {
             try {
                 MBEAN_SERVER.registerMBean(this, jmxObjectName);
-            } catch (InstanceAlreadyExistsException | MBeanRegistrationException | NotCompliantMBeanException e) {
+            } catch (InstanceAlreadyExistsException | MBeanRegistrationException | NotCompliantMBeanException ignored) {
                 // For now, simply skip registration
             }
         }
@@ -155,7 +155,7 @@ public class PoolableConnection extends DelegatingConnection<Connection> impleme
                 // pool is closed, so close the connection
                 passivate();
                 getInnermostDelegate().close();
-            } catch (final Exception ie) {
+            } catch (final Exception ignored) {
                 // DO NOTHING the original exception will be rethrown
             }
             throw new SQLException("Cannot close connection (isClosed check failed)", e);
@@ -246,7 +246,7 @@ public class PoolableConnection extends DelegatingConnection<Connection> impleme
      * <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
+     * {@link Utils#getDisconnectionSqlCodes()} and in this case anything starting with #{link
      * Utils.DISCONNECTION_SQL_CODE_PREFIX} is considered a disconnection.
      * </p>
      *
@@ -258,7 +258,7 @@ public class PoolableConnection extends DelegatingConnection<Connection> impleme
         final String sqlState = e.getSQLState();
         if (sqlState != null) {
             fatalException = disconnectionSqlCodes == null
-                ? sqlState.startsWith(Utils.DISCONNECTION_SQL_CODE_PREFIX) || Utils.DISCONNECTION_SQL_CODES.contains(sqlState)
+                ? sqlState.startsWith(Utils.DISCONNECTION_SQL_CODE_PREFIX) || Utils.getDisconnectionSqlCodes().contains(sqlState)
                 : disconnectionSqlCodes.contains(sqlState);
         }
         return fatalException;
@@ -269,7 +269,7 @@ public class PoolableConnection extends DelegatingConnection<Connection> impleme
      * <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
+     * codes in {@link Utils#getDisconnectionSqlCodes()} and in this case anything starting with #{link
      * Utils.DISCONNECTION_SQL_CODE_PREFIX} is considered a disconnection.
      * </p>
      *
diff --git a/java/org/apache/tomcat/dbcp/dbcp2/PoolableConnectionFactory.java b/java/org/apache/tomcat/dbcp/dbcp2/PoolableConnectionFactory.java
index f95c262f0a..b0c9ef9501 100644
--- a/java/org/apache/tomcat/dbcp/dbcp2/PoolableConnectionFactory.java
+++ b/java/org/apache/tomcat/dbcp/dbcp2/PoolableConnectionFactory.java
@@ -20,11 +20,11 @@ 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;
 
+import javax.management.MalformedObjectNameException;
 import javax.management.ObjectName;
 
 import org.apache.juli.logging.Log;
@@ -110,7 +110,7 @@ public class PoolableConnectionFactory implements PooledObjectFactory<PoolableCo
     }
 
     @Override
-    public void activateObject(final PooledObject<PoolableConnection> p) throws Exception {
+    public void activateObject(final PooledObject<PoolableConnection> p) throws SQLException {
 
         validateLifetime(p);
 
@@ -137,7 +137,7 @@ public class PoolableConnectionFactory implements PooledObjectFactory<PoolableCo
     }
 
     @Override
-    public void destroyObject(final PooledObject<PoolableConnection> p) throws Exception {
+    public void destroyObject(final PooledObject<PoolableConnection> p) throws SQLException {
         p.getObject().reallyClose();
     }
 
@@ -145,7 +145,7 @@ public class PoolableConnectionFactory implements PooledObjectFactory<PoolableCo
      * @since 2.9.0
      */
     @Override
-    public void destroyObject(final PooledObject<PoolableConnection> p, final DestroyMode mode) throws Exception {
+    public void destroyObject(final PooledObject<PoolableConnection> p, final DestroyMode mode) throws SQLException {
         if (mode == DestroyMode.ABANDONED) {
             p.getObject().getInnermostDelegate().abort(Runnable::run);
         } else {
@@ -157,7 +157,7 @@ public class PoolableConnectionFactory implements PooledObjectFactory<PoolableCo
      * Gets the cache state.
      *
      * @return The cache state.
-     * @since Made public in 2.6.0.
+     * @since 2.6.0.
      */
     public boolean getCacheState() {
         return cacheState;
@@ -167,7 +167,7 @@ public class PoolableConnectionFactory implements PooledObjectFactory<PoolableCo
      * Gets the connection factory.
      *
      * @return The connection factory.
-     * @since Made public in 2.6.0.
+     * @since 2.6.0.
      */
     public ConnectionFactory getConnectionFactory() {
         return connectionFactory;
@@ -187,7 +187,7 @@ public class PoolableConnectionFactory implements PooledObjectFactory<PoolableCo
 
     /**
      * @return The data source JMX ObjectName
-     * @since Made public in 2.6.0.
+     * @since 2.6.0.
      */
     public ObjectName getDataSourceJmxName() {
         return dataSourceJmxObjectName;
@@ -273,7 +273,7 @@ public class PoolableConnectionFactory implements PooledObjectFactory<PoolableCo
     /**
      * SQL_STATE codes considered to signal fatal conditions.
      * <p>
-     * Overrides the defaults in {@link Utils#DISCONNECTION_SQL_CODES} (plus anything starting with
+     * Overrides the defaults in {@link Utils#getDisconnectionSqlCodes()} (plus anything starting with
      * {@link Utils#DISCONNECTION_SQL_CODE_PREFIX}). If this property is non-null and {@link #isFastFailValidation()} is
      * {@code true}, whenever connections created by this factory generate exceptions with SQL_STATE codes in this list,
      * they will be marked as "fatally disconnected" and subsequent validations will fail fast (no attempt at isValid or
@@ -323,7 +323,7 @@ public class PoolableConnectionFactory implements PooledObjectFactory<PoolableCo
     }
     /**
      * @return Whether to pool statements.
-     * @since Made public in 2.6.0.
+     * @since 2.6.0.
      */
     public boolean getPoolStatements() {
         return poolStatements;
@@ -363,11 +363,10 @@ public class PoolableConnectionFactory implements PooledObjectFactory<PoolableCo
         if (conn.isClosed()) {
             throw new SQLException("initializeConnection: connection closed");
         }
-        if (null != sqls) {
-            try (Statement stmt = conn.createStatement()) {
+        if (!Utils.isEmpty(sqls)) {
+            try (Statement statement = conn.createStatement()) {
                 for (final String sql : sqls) {
-                    Objects.requireNonNull(sql, "null connectionInitSqls element");
-                    stmt.execute(sql);
+                    statement.execute(Objects.requireNonNull(sql, "null connectionInitSqls element"));
                 }
             }
         }
@@ -411,18 +410,18 @@ public class PoolableConnectionFactory implements PooledObjectFactory<PoolableCo
     }
 
     @Override
-    public PooledObject<PoolableConnection> makeObject() throws Exception {
+    public PooledObject<PoolableConnection> makeObject() throws SQLException {
         Connection conn = connectionFactory.createConnection();
         if (conn == null) {
             throw new IllegalStateException("Connection factory returned null from createConnection");
         }
         try {
             initializeConnection(conn);
-        } catch (final SQLException sqle) {
+        } catch (final SQLException e) {
             // Make sure the connection is closed
             Utils.closeQuietly((AutoCloseable) conn);
             // Rethrow original exception so it is visible to caller
-            throw sqle;
+            throw e;
         }
 
         final long connIndex = connectionIndex.getAndIncrement();
@@ -445,8 +444,7 @@ public class PoolableConnectionFactory implements PooledObjectFactory<PoolableCo
                 config.setJmxEnabled(false);
             }
             final PoolingConnection poolingConn = (PoolingConnection) conn;
-            final KeyedObjectPool<PStmtKey, DelegatingPreparedStatement> stmtPool = new GenericKeyedObjectPool<>(
-                    poolingConn, config);
+            final KeyedObjectPool<PStmtKey, DelegatingPreparedStatement> stmtPool = new GenericKeyedObjectPool<>(poolingConn, config);
             poolingConn.setStatementPool(stmtPool);
             poolingConn.setClearStatementPoolOnReturn(clearStatementPoolOnReturn);
             poolingConn.setCacheState(cacheState);
@@ -457,19 +455,23 @@ public class PoolableConnectionFactory implements PooledObjectFactory<PoolableCo
         if (dataSourceJmxObjectName == null) {
             connJmxName = null;
         } else {
-            connJmxName = new ObjectName(
-                    dataSourceJmxObjectName.toString() + Constants.JMX_CONNECTION_BASE_EXT + connIndex);
+            final String name = dataSourceJmxObjectName.toString() + Constants.JMX_CONNECTION_BASE_EXT + connIndex;
+            try {
+                connJmxName = new ObjectName(name);
+            } catch (MalformedObjectNameException e) {
+                Utils.closeQuietly((AutoCloseable) conn);
+                throw new SQLException(name, e);
+            }
         }
 
-        final PoolableConnection pc = new PoolableConnection(conn, pool, connJmxName, disconnectionSqlCodes,
-                fastFailValidation);
+        final PoolableConnection pc = new PoolableConnection(conn, pool, connJmxName, disconnectionSqlCodes, fastFailValidation);
         pc.setCacheState(cacheState);
 
         return new DefaultPooledObject<>(pc);
     }
 
     @Override
-    public void passivateObject(final PooledObject<PoolableConnection> p) throws Exception {
+    public void passivateObject(final PooledObject<PoolableConnection> p) throws SQLException {
 
         validateLifetime(p);
 
@@ -744,20 +746,14 @@ public class PoolableConnectionFactory implements PooledObjectFactory<PoolableCo
         conn.validate(validationQuery, validationQueryTimeoutDuration);
     }
 
-    private void validateLifetime(final PooledObject<PoolableConnection> p) throws Exception {
-        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));
-            }
-        }
+    private void validateLifetime(final PooledObject<PoolableConnection> p) throws LifetimeExceededException {
+        Utils.validateLifetime(p, maxConnDuration);
     }
 
     @Override
     public boolean validateObject(final PooledObject<PoolableConnection> p) {
         try {
             validateLifetime(p);
-
             validateConnection(p.getObject());
             return true;
         } catch (final Exception e) {
diff --git a/java/org/apache/tomcat/dbcp/dbcp2/PoolableConnectionMXBean.java b/java/org/apache/tomcat/dbcp/dbcp2/PoolableConnectionMXBean.java
index 75db37ddd8..f72c565607 100644
--- a/java/org/apache/tomcat/dbcp/dbcp2/PoolableConnectionMXBean.java
+++ b/java/org/apache/tomcat/dbcp/dbcp2/PoolableConnectionMXBean.java
@@ -24,14 +24,13 @@ import java.sql.SQLException;
  * @since 2.0
  */
 public interface PoolableConnectionMXBean {
-    // Methods
+
     void clearCachedState();
 
     void clearWarnings() throws SQLException;
 
     void close() throws SQLException;
 
-    // Read-write properties
     boolean getAutoCommit() throws SQLException;
 
     boolean getCacheState();
@@ -42,12 +41,10 @@ public interface PoolableConnectionMXBean {
 
     String getSchema() throws SQLException;
 
-    // SQLWarning getWarnings() throws SQLException;
     String getToString();
 
     int getTransactionIsolation() throws SQLException;
 
-    // Read-only properties
     boolean isClosed() throws SQLException;
 
     boolean isReadOnly() throws SQLException;
diff --git a/java/org/apache/tomcat/dbcp/dbcp2/PoolablePreparedStatement.java b/java/org/apache/tomcat/dbcp/dbcp2/PoolablePreparedStatement.java
index 4cedf14dd8..1babf76d4d 100644
--- a/java/org/apache/tomcat/dbcp/dbcp2/PoolablePreparedStatement.java
+++ b/java/org/apache/tomcat/dbcp/dbcp2/PoolablePreparedStatement.java
@@ -17,10 +17,7 @@
 package org.apache.tomcat.dbcp.dbcp2;
 
 import java.sql.PreparedStatement;
-import java.sql.ResultSet;
 import java.sql.SQLException;
-import java.util.ArrayList;
-import java.util.List;
 
 import org.apache.tomcat.dbcp.pool2.KeyedObjectPool;
 
@@ -71,7 +68,7 @@ public class PoolablePreparedStatement<K> extends DelegatingPreparedStatement {
 
         // Remove from trace now because this statement will be
         // added by the activate method.
-        removeThisTrace(getConnectionInternal());
+        removeThisTrace(conn);
     }
 
     @Override
@@ -125,32 +122,6 @@ public class PoolablePreparedStatement<K> extends DelegatingPreparedStatement {
         if (batchAdded) {
             clearBatch();
         }
-        setClosedInternal(true);
-        removeThisTrace(getConnectionInternal());
-
-        // The JDBC spec requires that a statement closes any open
-        // ResultSet's when it is closed.
-        // FIXME The PreparedStatement we're wrapping should handle this for us.
-        // See bug 17301 for what could happen when ResultSets are closed twice.
-        final List<AbandonedTrace> resultSetList = getTrace();
-        if (resultSetList != null) {
-            final List<Exception> thrownList = new ArrayList<>();
-            final ResultSet[] resultSets = resultSetList.toArray(Utils.EMPTY_RESULT_SET_ARRAY);
-            for (final ResultSet resultSet : resultSets) {
-                if (resultSet != null) {
-                    try {
-                        resultSet.close();
-                    } catch (final Exception e) {
-                        thrownList.add(e);
-                    }
-                }
-            }
-            clearTrace();
-            if (!thrownList.isEmpty()) {
-                throw new SQLExceptionList(thrownList);
-            }
-        }
-
-        super.passivate();
+        prepareToReturn();
     }
 }
diff --git a/java/org/apache/tomcat/dbcp/dbcp2/PoolingConnection.java b/java/org/apache/tomcat/dbcp/dbcp2/PoolingConnection.java
index 6586a49dbd..b35e031833 100644
--- a/java/org/apache/tomcat/dbcp/dbcp2/PoolingConnection.java
+++ b/java/org/apache/tomcat/dbcp/dbcp2/PoolingConnection.java
@@ -21,6 +21,7 @@ import java.sql.Connection;
 import java.sql.PreparedStatement;
 import java.sql.SQLException;
 import java.util.NoSuchElementException;
+import java.util.Objects;
 
 import org.apache.tomcat.dbcp.pool2.KeyedObjectPool;
 import org.apache.tomcat.dbcp.pool2.KeyedPooledObjectFactory;
@@ -62,7 +63,7 @@ public class PoolingConnection extends DelegatingConnection<Connection>
     }
 
     /** Pool of {@link PreparedStatement}s. and {@link CallableStatement}s */
-    private KeyedObjectPool<PStmtKey, DelegatingPreparedStatement> pstmtPool;
+    private KeyedObjectPool<PStmtKey, DelegatingPreparedStatement> pStmtPool;
 
     private boolean clearStatementPoolOnReturn;
 
@@ -86,7 +87,7 @@ public class PoolingConnection extends DelegatingConnection<Connection>
      */
     @Override
     public void activateObject(final PStmtKey key, final PooledObject<DelegatingPreparedStatement> pooledObject)
-            throws Exception {
+            throws SQLException {
         pooledObject.getObject().activate();
     }
 
@@ -97,11 +98,11 @@ public class PoolingConnection extends DelegatingConnection<Connection>
     @Override
     public synchronized void close() throws SQLException {
         try {
-            if (null != pstmtPool) {
-                final KeyedObjectPool<PStmtKey, DelegatingPreparedStatement> oldpool = pstmtPool;
-                pstmtPool = null;
+            if (null != pStmtPool) {
+                final KeyedObjectPool<PStmtKey, DelegatingPreparedStatement> oldPool = pStmtPool;
+                pStmtPool = null;
                 try {
-                    oldpool.close();
+                    oldPool.close();
                 } catch (final RuntimeException e) {
                     throw e;
                 } catch (final Exception e) {
@@ -120,14 +121,14 @@ public class PoolingConnection extends DelegatingConnection<Connection>
     /**
      * Notification from {@link PoolableConnection} that we returned to the pool.
      *
-     * @throws SQLException when <code>clearStatementPoolOnReturn</code> is true and the statement pool could not be
+     * @throws SQLException when {@code clearStatementPoolOnReturn} is true and the statement pool could not be
      *                      cleared
      * @since 2.8.0
      */
     public void connectionReturnedToPool() throws SQLException {
-        if (pstmtPool != null && clearStatementPoolOnReturn) {
+        if (pStmtPool != null && clearStatementPoolOnReturn) {
             try {
-                pstmtPool.clear();
+                pStmtPool.clear();
             } catch (final Exception e) {
                 throw new SQLException("Error clearing statement pool", e);
             }
@@ -153,7 +154,7 @@ public class PoolingConnection extends DelegatingConnection<Connection>
      *            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>.
+     *            {@code Statement.RETURN_GENERATED_KEYS} or {@code Statement.NO_GENERATED_KEYS}.
      *
      * @return the PStmtKey created for the given arguments.
      */
@@ -292,43 +293,39 @@ public class PoolingConnection extends DelegatingConnection<Connection>
      */
     @Override
     public void destroyObject(final PStmtKey key, final PooledObject<DelegatingPreparedStatement> pooledObject)
-            throws Exception {
+            throws SQLException {
         pooledObject.getObject().getInnermostDelegate().close();
     }
 
     private String getCatalogOrNull() {
-        String catalog = null;
         try {
-            catalog = getCatalog();
-        } catch (final SQLException e) {
-            // Ignored
+            return getCatalog();
+        } catch (final SQLException ignored) {
+            return null;
         }
-        return catalog;
     }
 
     private String getSchemaOrNull() {
-        String schema = null;
         try {
-            schema = getSchema();
-        } catch (final SQLException e) {
-            // Ignored
+            return getSchema();
+        } catch (final SQLException ignored) {
+            return null;
         }
-        return schema;
     }
 
     /**
-     * Returns the prepared statement pool we're using.
+     * Gets the prepared statement pool.
      *
      * @return statement pool
      * @since 2.8.0
      */
     public KeyedObjectPool<PStmtKey, DelegatingPreparedStatement> getStatementPool() {
-        return pstmtPool;
+        return pStmtPool;
     }
 
     /**
      * {@link KeyedPooledObjectFactory} method for creating {@link PoolablePreparedStatement}s or
-     * {@link PoolableCallableStatement}s. The <code>stmtType</code> field in the key determines whether a
+     * {@link PoolableCallableStatement}s. The {@code stmtType} field in the key determines whether a
      * PoolablePreparedStatement or PoolableCallableStatement is created.
      *
      * @param key
@@ -336,18 +333,18 @@ public class PoolingConnection extends DelegatingConnection<Connection>
      * @see #createKey(String, int, int, StatementType)
      */
     @Override
-    public PooledObject<DelegatingPreparedStatement> makeObject(final PStmtKey key) throws Exception {
+    public PooledObject<DelegatingPreparedStatement> makeObject(final PStmtKey key) throws SQLException {
         if (null == key) {
             throw new IllegalArgumentException("Prepared statement key is null or invalid.");
         }
         if (key.getStmtType() == StatementType.PREPARED_STATEMENT) {
             final PreparedStatement statement = (PreparedStatement) key.createStatement(getDelegate());
             @SuppressWarnings({"rawtypes", "unchecked" }) // Unable to find way to avoid this
-            final PoolablePreparedStatement pps = new PoolablePreparedStatement(statement, key, pstmtPool, this);
+            final PoolablePreparedStatement pps = new PoolablePreparedStatement(statement, key, pStmtPool, this);
             return new DefaultPooledObject<>(pps);
         }
         final CallableStatement statement = (CallableStatement) key.createStatement(getDelegate());
-        final PoolableCallableStatement pcs = new PoolableCallableStatement(statement, key, pstmtPool, this);
+        final PoolableCallableStatement pcs = new PoolableCallableStatement(statement, key, pStmtPool, this);
         return new DefaultPooledObject<>(pcs);
     }
 
@@ -373,7 +370,7 @@ public class PoolingConnection extends DelegatingConnection<Connection>
      */
     @Override
     public void passivateObject(final PStmtKey key, final PooledObject<DelegatingPreparedStatement> pooledObject)
-            throws Exception {
+            throws SQLException {
         final DelegatingPreparedStatement dps = pooledObject.getObject();
         dps.clearParameters();
         dps.passivate();
@@ -457,11 +454,11 @@ public class PoolingConnection extends DelegatingConnection<Connection>
      *             Wraps an underlying exception.
      */
     private PreparedStatement prepareStatement(final PStmtKey key) throws SQLException {
-        if (null == pstmtPool) {
+        if (null == pStmtPool) {
             throw new SQLException("Statement pool is null - closed or invalid PoolingConnection.");
         }
         try {
-            return pstmtPool.borrowObject(key);
+            return pStmtPool.borrowObject(key);
         } catch (final NoSuchElementException e) {
             throw new SQLException("MaxOpenPreparedStatements limit reached", e);
         } catch (final RuntimeException e) {
@@ -492,7 +489,7 @@ public class PoolingConnection extends DelegatingConnection<Connection>
      *            the SQL string used to define the PreparedStatement
      * @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>.
+     *            {@code Statement.RETURN_GENERATED_KEYS} or {@code Statement.NO_GENERATED_KEYS}.
      * @return a {@link PoolablePreparedStatement}
      * @throws SQLException
      *             Wraps an underlying exception.
@@ -594,15 +591,12 @@ public class PoolingConnection extends DelegatingConnection<Connection>
      *            the prepared statement pool.
      */
     public void setStatementPool(final KeyedObjectPool<PStmtKey, DelegatingPreparedStatement> pool) {
-        pstmtPool = pool;
+        pStmtPool = pool;
     }
 
     @Override
     public synchronized String toString() {
-        if (pstmtPool != null) {
-            return "PoolingConnection: " + pstmtPool.toString();
-        }
-        return "PoolingConnection: null";
+        return "PoolingConnection: " + Objects.toString(pStmtPool);
     }
 
     /**
diff --git a/java/org/apache/tomcat/dbcp/dbcp2/PoolingDriver.java b/java/org/apache/tomcat/dbcp/dbcp2/PoolingDriver.java
index 5f793f8fe1..9b4c878e6e 100644
--- a/java/org/apache/tomcat/dbcp/dbcp2/PoolingDriver.java
+++ b/java/org/apache/tomcat/dbcp/dbcp2/PoolingDriver.java
@@ -79,8 +79,8 @@ public class PoolingDriver implements Driver {
     static {
         try {
             DriverManager.registerDriver(new PoolingDriver());
-        } catch (final Exception e) {
-            // ignore
+        } catch (final Exception ignored) {
+            // Ignored
         }
     }
 
@@ -101,7 +101,7 @@ public class PoolingDriver implements Driver {
     private final boolean accessToUnderlyingConnectionAllowed;
 
     /**
-     * Constructs a new driver with <code>accessToUnderlyingConnectionAllowed</code> enabled.
+     * Constructs a new driver with {@code accessToUnderlyingConnectionAllowed} enabled.
      */
     public PoolingDriver() {
         this(true);
@@ -215,7 +215,7 @@ public class PoolingDriver implements Driver {
      * @param conn
      *            connection to invalidate
      * @throws SQLException
-     *             if the connection is not a <code>PoolGuardConnectionWrapper</code> or an error occurs invalidating
+     *             if the connection is not a {@code PoolGuardConnectionWrapper} or an error occurs invalidating
      *             the connection
      */
     public void invalidateConnection(final Connection conn) throws SQLException {
@@ -227,8 +227,8 @@ public class PoolingDriver implements Driver {
         final ObjectPool<Connection> pool = (ObjectPool<Connection>) pgconn.pool;
         try {
             pool.invalidateObject(pgconn.getDelegateInternal());
-        } catch (final Exception e) {
-            // Ignore.
+        } catch (final Exception ignored) {
+            // Ignored.
         }
     }
 
@@ -240,6 +240,7 @@ public class PoolingDriver implements Driver {
     protected boolean isAccessToUnderlyingConnectionAllowed() {
         return accessToUnderlyingConnectionAllowed;
     }
+
     @Override
     public boolean jdbcCompliant() {
         return true;
diff --git a/java/org/apache/tomcat/dbcp/dbcp2/Utils.java b/java/org/apache/tomcat/dbcp/dbcp2/Utils.java
index d4c4aa79b2..7b44dcdcb0 100644
--- a/java/org/apache/tomcat/dbcp/dbcp2/Utils.java
+++ b/java/org/apache/tomcat/dbcp/dbcp2/Utils.java
@@ -21,10 +21,16 @@ import java.sql.Connection;
 import java.sql.ResultSet;
 import java.sql.Statement;
 import java.text.MessageFormat;
+import java.time.Duration;
+import java.time.Instant;
+import java.util.Collection;
 import java.util.HashSet;
 import java.util.Properties;
 import java.util.ResourceBundle;
 import java.util.Set;
+import java.util.function.Consumer;
+
+import org.apache.tomcat.dbcp.pool2.PooledObject;
 
 /**
  * Utility methods.
@@ -57,7 +63,9 @@ public final class Utils {
      * <li>JZ0C0 (Sybase disconnect error)</li>
      * <li>JZ0C1 (Sybase disconnect error)</li>
      * </ul>
+     * @deprecated Use {@link #getDisconnectionSqlCodes()}.
      */
+    @Deprecated
     public static final Set<String> DISCONNECTION_SQL_CODES;
 
     static final ResultSet[] EMPTY_RESULT_SET_ARRAY = {};
@@ -101,21 +109,34 @@ public final class Utils {
     }
 
     /**
-     * Closes the AutoCloseable (which may be null).
+     * Closes the given {@link AutoCloseable} and if an exception is caught, then calls {@code exceptionHandler}.
      *
-     * @param autoCloseable an AutoCloseable, may be {@code null}
-     * @since 2.6.0
+     * @param autoCloseable The resource to close.
+     * @param exceptionHandler Consumes exception thrown closing this resource.
+     * @since 2.10.0
      */
-    public static void closeQuietly(final AutoCloseable autoCloseable) {
+    public static void close(AutoCloseable autoCloseable, final Consumer<Exception> exceptionHandler) {
         if (autoCloseable != null) {
             try {
                 autoCloseable.close();
             } catch (final Exception e) {
-                // ignored
+                if (exceptionHandler != null) {
+                    exceptionHandler.accept(e);
+                }
             }
         }
     }
 
+    /**
+     * Closes the AutoCloseable (which may be null).
+     *
+     * @param autoCloseable an AutoCloseable, may be {@code null}
+     * @since 2.6.0
+     */
+    public static void closeQuietly(final AutoCloseable autoCloseable) {
+        close(autoCloseable, null);
+    }
+
     /**
      * Closes the Connection (which may be null).
      *
@@ -149,6 +170,23 @@ public final class Utils {
         closeQuietly((AutoCloseable) statement);
     }
 
+    /**
+     * Gets a copy of SQL codes of fatal connection errors.
+     * <ul>
+     * <li>57P01 (Admin shutdown)</li>
+     * <li>57P02 (Crash shutdown)</li>
+     * <li>57P03 (Cannot connect now)</li>
+     * <li>01002 (SQL92 disconnect error)</li>
+     * <li>JZ0C0 (Sybase disconnect error)</li>
+     * <li>JZ0C1 (Sybase disconnect error)</li>
+     * </ul>
+     * @return SQL codes of fatal connection errors.
+     * @since 2.10.0
+     */
+    public static Set<String> getDisconnectionSqlCodes() {
+        return new HashSet<>(DISCONNECTION_SQL_CODES);
+    }
+
     /**
      * Gets the correct i18n message for the given key.
      *
@@ -175,6 +213,10 @@ public final class Utils {
         return mf.format(args, new StringBuffer(), null).toString();
     }
 
+    static boolean isEmpty(final Collection<?> collection) {
+        return collection == null || collection.isEmpty();
+    }
+
     static boolean isSecurityEnabled() {
         return System.getSecurityManager() != null;
     }
@@ -199,6 +241,15 @@ public final class Utils {
         return value == null ? null : String.valueOf(value);
     }
 
+    public static void validateLifetime(final PooledObject<?> p, final Duration maxDuration) throws LifetimeExceededException {
+        if (maxDuration.compareTo(Duration.ZERO) > 0) {
+            final Duration lifetimeDuration = Duration.between(p.getCreateInstant(), Instant.now());
+            if (lifetimeDuration.compareTo(maxDuration) > 0) {
+                throw new LifetimeExceededException(Utils.getMessage("connectionFactory.lifetimeExceeded", lifetimeDuration, maxDuration));
+            }
+        }
+    }
+
     private Utils() {
         // not instantiable
     }
diff --git a/java/org/apache/tomcat/dbcp/dbcp2/cpdsadapter/ConnectionImpl.java b/java/org/apache/tomcat/dbcp/dbcp2/cpdsadapter/ConnectionImpl.java
index 9c820a3708..6b323dd6ec 100644
--- a/java/org/apache/tomcat/dbcp/dbcp2/cpdsadapter/ConnectionImpl.java
+++ b/java/org/apache/tomcat/dbcp/dbcp2/cpdsadapter/ConnectionImpl.java
@@ -26,9 +26,9 @@ import org.apache.tomcat.dbcp.dbcp2.DelegatingConnection;
 import org.apache.tomcat.dbcp.dbcp2.DelegatingPreparedStatement;
 
 /**
- * This class is the <code>Connection</code> that will be returned from
- * <code>PooledConnectionImpl.getConnection()</code>. Most methods are wrappers around the JDBC 1.x
- * <code>Connection</code>. A few exceptions include preparedStatement and close. In accordance with the JDBC
+ * This class is the {@code Connection} that will be returned from
+ * {@code PooledConnectionImpl.getConnection()}. Most methods are wrappers around the JDBC 1.x
+ * {@code Connection}. A few exceptions include preparedStatement and close. In accordance with the JDBC
  * specification this Connection cannot be used after closed() is called. Any further usage will result in an
  * SQLException.
  * <p>
@@ -37,7 +37,7 @@ import org.apache.tomcat.dbcp.dbcp2.DelegatingPreparedStatement;
  *
  * @since 2.0
  */
-class ConnectionImpl extends DelegatingConnection<Connection> {
+final class ConnectionImpl extends DelegatingConnection<Connection> {
 
     private final boolean accessToUnderlyingConnectionAllowed;
 
@@ -45,7 +45,7 @@ class ConnectionImpl extends DelegatingConnection<Connection> {
     private final PooledConnectionImpl pooledConnection;
 
     /**
-     * Creates a <code>ConnectionImpl</code>.
+     * Creates a {@code ConnectionImpl}.
      *
      * @param pooledConnection
      *            The PooledConnection that is calling the ctor.
@@ -122,14 +122,14 @@ class ConnectionImpl extends DelegatingConnection<Connection> {
     }
 
     /**
-     * If pooling of <code>CallableStatement</code>s is turned on in the {@link DriverAdapterCPDS}, a pooled object may
+     * If pooling of {@code CallableStatement}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}.
      *
      * @param sql
      *            an SQL statement that may contain one or more '?' parameter placeholders. Typically this statement is
      *            specified using JDBC call escape syntax.
-     * @return a default <code>CallableStatement</code> object containing the pre-compiled SQL statement.
-     * @exception SQLException
+     * @return a default {@code CallableStatement} object containing the pre-compiled SQL statement.
+     * @throws SQLException
      *                Thrown if a database access error occurs or this method is called on a closed connection.
      * @since 2.4.0
      */
@@ -145,23 +145,23 @@ class ConnectionImpl extends DelegatingConnection<Connection> {
     }
 
     /**
-     * If pooling of <code>CallableStatement</code>s is turned on in the {@link DriverAdapterCPDS}, a pooled object may
+     * If pooling of {@code CallableStatement}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}.
      *
      * @param sql
-     *            a <code>String</code> object that is the SQL statement to be sent to the database; may contain on or
+     *            a {@code String} object that is the SQL statement to be sent to the database; may contain on or
      *            more '?' parameters.
      * @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>.
+     *            a result set type; one of {@code ResultSet.TYPE_FORWARD_ONLY},
+     *            {@code ResultSet.TYPE_SCROLL_INSENSITIVE}, or {@code ResultSet.TYPE_SCROLL_SENSITIVE}.
      * @param resultSetConcurrency
-     *            a concurrency type; one of <code>ResultSet.CONCUR_READ_ONLY</code> or
-     *            <code>ResultSet.CONCUR_UPDATABLE</code>.
-     * @return a <code>CallableStatement</code> object containing the pre-compiled SQL statement that will produce
-     *         <code>ResultSet</code> objects with the given type and concurrency.
+     *            a concurrency type; one of {@code ResultSet.CONCUR_READ_ONLY} or
+     *            {@code ResultSet.CONCUR_UPDATABLE}.
+     * @return a {@code CallableStatement} object containing the pre-compiled SQL statement that will produce
+     *         {@code ResultSet} objects with the given type and concurrency.
      * @throws SQLException
      *             Thrown if a database access error occurs, this method is called on a closed connection or the given
-     *             parameters are not <code>ResultSet</code> constants indicating type and concurrency.
+     *             parameters are not {@code ResultSet} constants indicating type and concurrency.
      * @since 2.4.0
      */
     @Override
@@ -178,26 +178,26 @@ class ConnectionImpl extends DelegatingConnection<Connection> {
     }
 
     /**
-     * If pooling of <code>CallableStatement</code>s is turned on in the {@link DriverAdapterCPDS}, a pooled object may
+     * If pooling of {@code CallableStatement}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}.
      *
      * @param sql
-     *            a <code>String</code> object that is the SQL statement to be sent to the database; may contain on or
+     *            a {@code String} object that is the SQL statement to be sent to the database; may contain on or
      *            more '?' parameters.
      * @param resultSetType
-     *            one of the following <code>ResultSet</code> constants: <code>ResultSet.TYPE_FORWARD_ONLY</code>,
-     *            <code>ResultSet.TYPE_SCROLL_INSENSITIVE</code>, or <code>ResultSet.TYPE_SCROLL_SENSITIVE</code>.
+     *            one of the following {@code ResultSet} constants: {@code ResultSet.TYPE_FORWARD_ONLY},
+     *            {@code ResultSet.TYPE_SCROLL_INSENSITIVE}, or {@code ResultSet.TYPE_SCROLL_SENSITIVE}.
      * @param resultSetConcurrency
-     *            one of the following <code>ResultSet</code> constants: <code>ResultSet.CONCUR_READ_ONLY</code> or
-     *            <code>ResultSet.CONCUR_UPDATABLE</code>.
+     *            one of the following {@code ResultSet} constants: {@code ResultSet.CONCUR_READ_ONLY} or
+     *            {@code ResultSet.CONCUR_UPDATABLE}.
      * @param resultSetHoldability
-     *            one of the following <code>ResultSet</code> constants: <code>ResultSet.HOLD_CURSORS_OVER_COMMIT</code>
-     *            or <code>ResultSet.CLOSE_CURSORS_AT_COMMIT</code>.
-     * @return a new <code>CallableStatement</code> object, containing the pre-compiled SQL statement, that will
-     *         generate <code>ResultSet</code> objects with the given type, concurrency, and holdability.
+     *            one of the following {@code ResultSet} constants: {@code ResultSet.HOLD_CURSORS_OVER_COMMIT}
+     *            or {@code ResultSet.CLOSE_CURSORS_AT_COMMIT}.
+     * @return a new {@code CallableStatement} object, containing the pre-compiled SQL statement, that will
+     *         generate {@code ResultSet} objects with the given type, concurrency, and holdability.
      * @throws SQLException
      *             Thrown if a database access error occurs, this method is called on a closed connection or the given
-     *             parameters are not <code>ResultSet</code> constants indicating type, concurrency, and holdability.
+     *             parameters are not {@code ResultSet} constants indicating type, concurrency, and holdability.
      * @since 2.4.0
      */
     @Override
@@ -214,7 +214,7 @@ class ConnectionImpl extends DelegatingConnection<Connection> {
     }
 
     /**
-     * If pooling of <code>PreparedStatement</code>s is turned on in the {@link DriverAdapterCPDS}, a pooled object may
+     * If pooling of {@code PreparedStatement}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}.
      *
      * @param sql
@@ -246,7 +246,7 @@ class ConnectionImpl extends DelegatingConnection<Connection> {
     }
 
     /**
-     * If pooling of <code>PreparedStatement</code>s is turned on in the {@link DriverAdapterCPDS}, a pooled object may
+     * If pooling of {@code PreparedStatement}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}.
      *
      * @throws SQLException
diff --git a/java/org/apache/tomcat/dbcp/dbcp2/cpdsadapter/DriverAdapterCPDS.java b/java/org/apache/tomcat/dbcp/dbcp2/cpdsadapter/DriverAdapterCPDS.java
index ff50366fae..642ced624e 100644
--- a/java/org/apache/tomcat/dbcp/dbcp2/cpdsadapter/DriverAdapterCPDS.java
+++ b/java/org/apache/tomcat/dbcp/dbcp2/cpdsadapter/DriverAdapterCPDS.java
@@ -37,7 +37,6 @@ import javax.naming.spi.ObjectFactory;
 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;
@@ -50,29 +49,29 @@ import org.apache.tomcat.dbcp.pool2.impl.GenericKeyedObjectPoolConfig;
 /**
  * <p>
  * An adapter for JDBC drivers that do not include an implementation of {@link javax.sql.ConnectionPoolDataSource}, but
- * still include a {@link java.sql.DriverManager} implementation. <code>ConnectionPoolDataSource</code>s are not used
- * within general applications. They are used by <code>DataSource</code> implementations that pool
- * <code>Connection</code>s, such as {@link org.apache.tomcat.dbcp.dbcp2.datasources.SharedPoolDataSource}. A J2EE container
- * will normally provide some method of initializing the <code>ConnectionPoolDataSource</code> whose attributes are
+ * still include a {@link java.sql.DriverManager} implementation. {@code ConnectionPoolDataSource}s are not used
+ * within general applications. They are used by {@code DataSource} implementations that pool
+ * {@code Connection}s, such as {@link org.apache.tomcat.dbcp.dbcp2.datasources.SharedPoolDataSource}. A J2EE container
+ * will normally provide some method of initializing the {@code ConnectionPoolDataSource} whose attributes are
  * presented as bean getters/setters and then deploying it via JNDI. It is then available as a source of physical
- * connections to the database, when the pooling <code>DataSource</code> needs to create a new physical connection.
+ * connections to the database, when the pooling {@code DataSource} needs to create a new physical connection.
  * </p>
  * <p>
  * Although normally used within a JNDI environment, the DriverAdapterCPDS can be instantiated and initialized as any
- * bean and then attached directly to a pooling <code>DataSource</code>. <code>Jdbc2PoolDataSource</code> can use the
- * <code>ConnectionPoolDataSource</code> with or without the use of JNDI.
+ * bean and then attached directly to a pooling {@code DataSource}. {@code Jdbc2PoolDataSource} can use the
+ * {@code ConnectionPoolDataSource} with or without the use of JNDI.
  * </p>
  * <p>
- * The DriverAdapterCPDS also provides <code>PreparedStatement</code> pooling which is not generally available in jdbc2
- * <code>ConnectionPoolDataSource</code> implementation, but is addressed within the jdbc3 specification. The
- * <code>PreparedStatement</code> pool in DriverAdapterCPDS has been in the dbcp package for some time, but it has not
+ * The DriverAdapterCPDS also provides {@code PreparedStatement} pooling which is not generally available in jdbc2
+ * {@code ConnectionPoolDataSource} implementation, but is addressed within the jdbc3 specification. The
+ * {@code PreparedStatement} pool in DriverAdapterCPDS has been in the dbcp package for some time, but it has not
  * undergone extensive testing in the configuration used here. It should be considered experimental and can be toggled
  * with the poolPreparedStatements attribute.
  * </p>
  * <p>
  * The <a href="package-summary.html">package documentation</a> contains an example using catalina and JNDI. The
  * <a href="../datasources/package-summary.html">datasources package documentation</a> shows how to use
- * <code>DriverAdapterCPDS</code> as a source for <code>Jdbc2PoolDataSource</code> without the use of JNDI.
+ * {@code DriverAdapterCPDS} as a source for {@code Jdbc2PoolDataSource} without the use of JNDI.
  * </p>
  *
  * @since 2.0
@@ -92,8 +91,8 @@ public class DriverAdapterCPDS implements ConnectionPoolDataSource, Referenceabl
     /** Description */
     private String description;
 
-    /** Url name */
-    private String url;
+    /** Connection string */
+    private String connectionString;
 
     /** User name */
     private String userName;
@@ -273,7 +272,7 @@ public class DriverAdapterCPDS implements ConnectionPoolDataSource, Referenceabl
      */
     @Override
     public Object getObjectInstance(final Object refObj, final Name name, final Context context,
-        final Hashtable<?, ?> env) throws Exception {
+        final Hashtable<?, ?> env) throws ClassNotFoundException {
         // The spec says to return null if we can't create an instance
         // of the reference
         DriverAdapterCPDS cpds = null;
@@ -289,7 +288,7 @@ public class DriverAdapterCPDS implements ConnectionPoolDataSource, Referenceabl
                 if (isNotEmpty(ra)) {
                     setDriver(getStringContent(ra));
                 }
-                ra = ref.get("url");
+                ra = ref.get("connectionString");
                 if (isNotEmpty(ra)) {
                     setUrl(getStringContent(ra));
                 }
@@ -363,7 +362,7 @@ public class DriverAdapterCPDS implements ConnectionPoolDataSource, Referenceabl
      * @since 2.4.0
      */
     public char[] getPasswordCharArray() {
-        return userPassword == null ? null : userPassword.clone();
+        return Utils.clone(userPassword);
     }
 
     /**
@@ -450,7 +449,7 @@ public class DriverAdapterCPDS implements ConnectionPoolDataSource, Referenceabl
         ref.add(new StringRefAddr("loginTimeout", String.valueOf(getLoginTimeout())));
         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("connectionString", getUrl()));
 
         ref.add(new StringRefAddr("poolPreparedStatements", String.valueOf(isPoolPreparedStatements())));
         ref.add(new StringRefAddr("maxIdle", String.valueOf(getMaxIdle())));
@@ -486,12 +485,12 @@ public class DriverAdapterCPDS implements ConnectionPoolDataSource, Referenceabl
     }
 
     /**
-     * Gets the value of url used to locate the database for this datasource.
+     * Gets the value of connection string used to locate the database for this data source.
      *
-     * @return value of url.
+     * @return value of connection string.
      */
     public String getUrl() {
-        return url;
+        return connectionString;
     }
 
     /**
@@ -517,7 +516,7 @@ public class DriverAdapterCPDS implements ConnectionPoolDataSource, Referenceabl
     }
 
     /**
-     * Whether to toggle the pooling of <code>PreparedStatement</code>s
+     * Whether to toggle the pooling of {@code PreparedStatement}s
      *
      * @return value of poolPreparedStatements.
      */
@@ -538,11 +537,11 @@ public class DriverAdapterCPDS implements ConnectionPoolDataSource, Referenceabl
     /**
      * Sets the connection properties passed to the JDBC driver.
      * <p>
-     * If <code>props</code> contains "user" and/or "password" properties, the corresponding instance properties are
+     * If {@code props} contains "user" and/or "password" properties, the corresponding instance properties are
      * set. If these properties are not present, they are filled in using {@link #getUser()}, {@link #getPassword()}
      * when {@link #getPooledConnection()} is called, or using the actual parameters to the method call when
      * {@link #getPooledConnection(String, String)} is called. Calls to {@link #setUser(String)} or
-     * {@link #setPassword(String)} overwrite the values of these properties if <code>connectionProperties</code> is not
+     * {@link #setPassword(String)} overwrite the values of these properties if {@code connectionProperties} is not
      * null.
      * </p>
      *
@@ -675,7 +674,7 @@ public class DriverAdapterCPDS implements ConnectionPoolDataSource, Referenceabl
      * Sets the number of statements to examine during each run of the idle object evictor thread (if any).
      * <p>
      * When a negative value is supplied,
-     * <code>ceil({@link BasicDataSource#getNumIdle})/abs({@link #getNumTestsPerEvictionRun})</code> tests will be run.
+     * {@code ceil({@link BasicDataSource#getNumIdle})/abs({@link #getNumTestsPerEvictionRun})} tests will be run.
      * I.e., when the value is <i>-n</i>, roughly one <i>n</i>th of the idle objects will be tested per run.
      * </p>
      *
@@ -714,7 +713,7 @@ public class DriverAdapterCPDS implements ConnectionPoolDataSource, Referenceabl
     }
 
     /**
-     * Whether to toggle the pooling of <code>PreparedStatement</code>s
+     * Whether to toggle the pooling of {@code PreparedStatement}s
      *
      * @param poolPreparedStatements true to pool statements.
      * @throws IllegalStateException if {@link #getPooledConnection()} has been called
@@ -741,14 +740,14 @@ public class DriverAdapterCPDS implements ConnectionPoolDataSource, Referenceabl
     }
 
     /**
-     * Sets the value of URL string used to locate the database for this datasource.
+     * Sets the value of URL string used to locate the database for this data source.
      *
-     * @param url Value to assign to url.
+     * @param connectionString Value to assign to connection string.
      * @throws IllegalStateException if {@link #getPooledConnection()} has been called
      */
-    public void setUrl(final String url) {
+    public void setUrl(final String connectionString) {
         assertInitializationAllowed();
-        this.url = url;
+        this.connectionString = connectionString;
     }
 
     /**
@@ -773,10 +772,10 @@ public class DriverAdapterCPDS implements ConnectionPoolDataSource, Referenceabl
         final StringBuilder builder = new StringBuilder(super.toString());
         builder.append("[description=");
         builder.append(description);
-        builder.append(", url=");
+        builder.append(", connectionString=");
         // TODO What if the connection string contains a 'user' or 'password' query parameter but that connection string
         // is not in a legal URL format?
-        builder.append(url);
+        builder.append(connectionString);
         builder.append(", driver=");
         builder.append(driver);
         builder.append(", loginTimeout=");
diff --git a/java/org/apache/tomcat/dbcp/dbcp2/cpdsadapter/PStmtKeyCPDS.java b/java/org/apache/tomcat/dbcp/dbcp2/cpdsadapter/PStmtKeyCPDS.java
index 083f3ba2eb..68e871ea14 100644
--- a/java/org/apache/tomcat/dbcp/dbcp2/cpdsadapter/PStmtKeyCPDS.java
+++ b/java/org/apache/tomcat/dbcp/dbcp2/cpdsadapter/PStmtKeyCPDS.java
@@ -44,7 +44,7 @@ public class PStmtKeyCPDS extends PStmtKey {
      *            The SQL 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>.
+     *            {@code Statement.RETURN_GENERATED_KEYS} or {@code Statement.NO_GENERATED_KEYS}.
      */
     public PStmtKeyCPDS(final String sql, final int autoGeneratedKeys) {
         super(sql, null, autoGeneratedKeys);
@@ -56,11 +56,11 @@ public class PStmtKeyCPDS extends PStmtKey {
      * @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>.
+     *            A result set type; one of {@code ResultSet.TYPE_FORWARD_ONLY},
+     *            {@code ResultSet.TYPE_SCROLL_INSENSITIVE}, or {@code ResultSet.TYPE_SCROLL_SENSITIVE}.
      * @param resultSetConcurrency
-     *            A concurrency type; one of <code>ResultSet.CONCUR_READ_ONLY</code> or
-     *            <code>ResultSet.CONCUR_UPDATABLE</code>.
+     *            A concurrency type; one of {@code ResultSet.CONCUR_READ_ONLY} or
+     *            {@code ResultSet.CONCUR_UPDATABLE}.
      */
     public PStmtKeyCPDS(final String sql, final int resultSetType, final int resultSetConcurrency) {
         super(sql, resultSetType, resultSetConcurrency);
@@ -72,14 +72,14 @@ public class PStmtKeyCPDS extends PStmtKey {
      * @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>.
+     *            a result set type; one of {@code ResultSet.TYPE_FORWARD_ONLY},
+     *            {@code ResultSet.TYPE_SCROLL_INSENSITIVE}, or {@code ResultSet.TYPE_SCROLL_SENSITIVE}.
      * @param resultSetConcurrency
-     *            A concurrency type; one of <code>ResultSet.CONCUR_READ_ONLY</code> or
-     *            <code>ResultSet.CONCUR_UPDATABLE</code>
+     *            A concurrency type; one of {@code ResultSet.CONCUR_READ_ONLY} or
+     *            {@code ResultSet.CONCUR_UPDATABLE}
      * @param resultSetHoldability
-     *            One of the following <code>ResultSet</code> constants: <code>ResultSet.HOLD_CURSORS_OVER_COMMIT</code>
-     *            or <code>ResultSet.CLOSE_CURSORS_AT_COMMIT</code>.
+     *            One of the following {@code ResultSet} constants: {@code ResultSet.HOLD_CURSORS_OVER_COMMIT}
+     *            or {@code ResultSet.CLOSE_CURSORS_AT_COMMIT}.
      */
     public PStmtKeyCPDS(final String sql, final int resultSetType, final int resultSetConcurrency,
             final int resultSetHoldability) {
diff --git a/java/org/apache/tomcat/dbcp/dbcp2/cpdsadapter/PooledConnectionImpl.java b/java/org/apache/tomcat/dbcp/dbcp2/cpdsadapter/PooledConnectionImpl.java
index 60dcc758c3..c247cfd4cc 100644
--- a/java/org/apache/tomcat/dbcp/dbcp2/cpdsadapter/PooledConnectionImpl.java
+++ b/java/org/apache/tomcat/dbcp/dbcp2/cpdsadapter/PooledConnectionImpl.java
@@ -36,17 +36,18 @@ import org.apache.tomcat.dbcp.dbcp2.PStmtKey;
 import org.apache.tomcat.dbcp.dbcp2.PoolableCallableStatement;
 import org.apache.tomcat.dbcp.dbcp2.PoolablePreparedStatement;
 import org.apache.tomcat.dbcp.dbcp2.PoolingConnection.StatementType;
+import org.apache.tomcat.dbcp.dbcp2.Utils;
 import org.apache.tomcat.dbcp.pool2.KeyedObjectPool;
 import org.apache.tomcat.dbcp.pool2.KeyedPooledObjectFactory;
 import org.apache.tomcat.dbcp.pool2.PooledObject;
 import org.apache.tomcat.dbcp.pool2.impl.DefaultPooledObject;
 
 /**
- * Implementation of PooledConnection that is returned by PooledConnectionDataSource.
+ * Implementation of {@link PooledConnection} that is returned by {@link DriverAdapterCPDS}.
  *
  * @since 2.0
  */
-class PooledConnectionImpl
+final class PooledConnectionImpl
         implements PooledConnection, KeyedPooledObjectFactory<PStmtKey, DelegatingPreparedStatement> {
 
     private static final String CLOSED = "Attempted to use PooledConnection after closed() was called.";
@@ -113,7 +114,7 @@ class PooledConnectionImpl
      */
     @Override
     public void activateObject(final PStmtKey key, final PooledObject<DelegatingPreparedStatement> pooledObject)
-            throws Exception {
+            throws SQLException {
         pooledObject.getObject().activate();
     }
 
@@ -145,8 +146,8 @@ class PooledConnectionImpl
     }
 
     /**
-     * Closes the physical connection and marks this <code>PooledConnection</code> so that it may not be used to
-     * generate any more logical <code>Connection</code>s.
+     * Closes the physical connection and marks this {@code PooledConnection} so that it may not be used to
+     * generate any more logical {@code Connection}s.
      *
      * @throws SQLException
      *             Thrown when an error occurs or the connection is already closed.
@@ -184,7 +185,7 @@ class PooledConnectionImpl
      * @return a {@link PStmtKey} for the given arguments.
      */
     protected PStmtKey createKey(final String sql) {
-        return new PStmtKey(normalizeSQL(sql), getCatalogOrNull(), getSchemaOrNull());
+        return new PStmtKey(sql, getCatalogOrNull(), getSchemaOrNull());
     }
 
     /**
@@ -194,11 +195,11 @@ class PooledConnectionImpl
      *            The SQL 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>.
+     *            {@code Statement.RETURN_GENERATED_KEYS} or {@code Statement.NO_GENERATED_KEYS}.
      * @return a key to uniquely identify a prepared statement.
      */
     protected PStmtKey createKey(final String sql, final int autoGeneratedKeys) {
-        return new PStmtKey(normalizeSQL(sql), getCatalogOrNull(), getSchemaOrNull(), autoGeneratedKeys);
+        return new PStmtKey(sql, getCatalogOrNull(), getSchemaOrNull(), autoGeneratedKeys);
     }
 
     /**
@@ -207,16 +208,15 @@ class PooledConnectionImpl
      * @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>.
+     *            A result set type; one of {@code ResultSet.TYPE_FORWARD_ONLY},
+     *            {@code ResultSet.TYPE_SCROLL_INSENSITIVE}, or {@code ResultSet.TYPE_SCROLL_SENSITIVE}.
      * @param resultSetConcurrency
-     *            A concurrency type; one of <code>ResultSet.CONCUR_READ_ONLY</code> or
-     *            <code>ResultSet.CONCUR_UPDATABLE</code>.
+     *            A concurrency type; one of {@code ResultSet.CONCUR_READ_ONLY} or
+     *            {@code ResultSet.CONCUR_UPDATABLE}.
      * @return a key to uniquely identify a prepared statement.
      */
     protected PStmtKey createKey(final String sql, final int resultSetType, final int resultSetConcurrency) {
-        return new PStmtKey(normalizeSQL(sql), getCatalogOrNull(), getSchemaOrNull(), resultSetType,
-                resultSetConcurrency);
+        return new PStmtKey(sql, getCatalogOrNull(), getSchemaOrNull(), resultSetType, resultSetConcurrency);
     }
 
     /**
@@ -225,20 +225,18 @@ class PooledConnectionImpl
      * @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>.
+     *            a result set type; one of {@code ResultSet.TYPE_FORWARD_ONLY},
+     *            {@code ResultSet.TYPE_SCROLL_INSENSITIVE}, or {@code ResultSet.TYPE_SCROLL_SENSITIVE}.
      * @param resultSetConcurrency
-     *            A concurrency type; one of <code>ResultSet.CONCUR_READ_ONLY</code> or
-     *            <code>ResultSet.CONCUR_UPDATABLE</code>
+     *            A concurrency type; one of {@code ResultSet.CONCUR_READ_ONLY} or
+     *            {@code ResultSet.CONCUR_UPDATABLE}
      * @param resultSetHoldability
-     *            One of the following <code>ResultSet</code> constants: <code>ResultSet.HOLD_CURSORS_OVER_COMMIT</code>
-     *            or <code>ResultSet.CLOSE_CURSORS_AT_COMMIT</code>.
+     *            One of the following {@code ResultSet} constants: {@code ResultSet.HOLD_CURSORS_OVER_COMMIT}
+     *            or {@code ResultSet.CLOSE_CURSORS_AT_COMMIT}.
      * @return a key to uniquely identify a prepared statement.
      */
-    protected PStmtKey createKey(final String sql, final int resultSetType, final int resultSetConcurrency,
-            final int resultSetHoldability) {
-        return new PStmtKey(normalizeSQL(sql), getCatalogOrNull(), getSchemaOrNull(), resultSetType,
-                resultSetConcurrency, resultSetHoldability);
+    protected PStmtKey createKey(final String sql, final int resultSetType, final int resultSetConcurrency, final int resultSetHoldability) {
+        return new PStmtKey(sql, getCatalogOrNull(), getSchemaOrNull(), resultSetType, resultSetConcurrency, resultSetHoldability);
     }
 
     /**
@@ -247,23 +245,22 @@ class PooledConnectionImpl
      * @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>
+     *            a result set type; one of {@code ResultSet.TYPE_FORWARD_ONLY},
+     *            {@code ResultSet.TYPE_SCROLL_INSENSITIVE}, or {@code ResultSet.TYPE_SCROLL_SENSITIVE}
      * @param resultSetConcurrency
-     *            A concurrency type; one of <code>ResultSet.CONCUR_READ_ONLY</code> or
-     *            <code>ResultSet.CONCUR_UPDATABLE</code>.
+     *            A concurrency type; one of {@code ResultSet.CONCUR_READ_ONLY} or
+     *            {@code ResultSet.CONCUR_UPDATABLE}.
      * @param resultSetHoldability
-     *            One of the following <code>ResultSet</code> constants: <code>ResultSet.HOLD_CURSORS_OVER_COMMIT</code>
-     *            or <code>ResultSet.CLOSE_CURSORS_AT_COMMIT</code>.
+     *            One of the following {@code ResultSet} constants: {@code ResultSet.HOLD_CURSORS_OVER_COMMIT}
+     *            or {@code ResultSet.CLOSE_CURSORS_AT_COMMIT}.
      * @param statementType
      *            The SQL statement type, prepared or callable.
      * @return a key to uniquely identify a prepared statement.
      * @since 2.4.0
      */
-    protected PStmtKey createKey(final String sql, final int resultSetType, final int resultSetConcurrency,
-            final int resultSetHoldability, final StatementType statementType) {
-        return new PStmtKey(normalizeSQL(sql), getCatalogOrNull(), getSchemaOrNull(), resultSetType,
-                resultSetConcurrency, resultSetHoldability, statementType);
+    protected PStmtKey createKey(final String sql, final int resultSetType, final int resultSetConcurrency, final int resultSetHoldability,
+        final StatementType statementType) {
+        return new PStmtKey(sql, getCatalogOrNull(), getSchemaOrNull(), resultSetType, resultSetConcurrency, resultSetHoldability, statementType);
     }
 
     /**
@@ -272,20 +269,18 @@ class PooledConnectionImpl
      * @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>.
+     *            A result set type; one of {@code ResultSet.TYPE_FORWARD_ONLY},
+     *            {@code ResultSet.TYPE_SCROLL_INSENSITIVE}, or {@code ResultSet.TYPE_SCROLL_SENSITIVE}.
      * @param resultSetConcurrency
-     *            A concurrency type; one of <code>ResultSet.CONCUR_READ_ONLY</code> or
-     *            <code>ResultSet.CONCUR_UPDATABLE</code>.
+     *            A concurrency type; one of {@code ResultSet.CONCUR_READ_ONLY} or
+     *            {@code ResultSet.CONCUR_UPDATABLE}.
      * @param statementType
      *            The SQL statement type, prepared or callable.
      * @return a key to uniquely identify a prepared statement.
      * @since 2.4.0
      */
-    protected PStmtKey createKey(final String sql, final int resultSetType, final int resultSetConcurrency,
-            final StatementType statementType) {
-        return new PStmtKey(normalizeSQL(sql), getCatalogOrNull(), getSchemaOrNull(), resultSetType,
-                resultSetConcurrency, statementType);
+    protected PStmtKey createKey(final String sql, final int resultSetType, final int resultSetConcurrency, final StatementType statementType) {
+        return new PStmtKey(sql, getCatalogOrNull(), getSchemaOrNull(), resultSetType, resultSetConcurrency, statementType);
     }
 
     /**
@@ -299,7 +294,7 @@ class PooledConnectionImpl
      * @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);
+        return new PStmtKey(sql, getCatalogOrNull(), getSchemaOrNull(), columnIndexes);
     }
 
     /**
@@ -312,7 +307,7 @@ class PooledConnectionImpl
      * @return a key to uniquely identify a prepared statement.
      */
     protected PStmtKey createKey(final String sql, final StatementType statementType) {
-        return new PStmtKey(normalizeSQL(sql), getCatalogOrNull(), getSchemaOrNull(), statementType);
+        return new PStmtKey(sql, getCatalogOrNull(), getSchemaOrNull(), statementType);
     }
 
     /**
@@ -325,7 +320,7 @@ class PooledConnectionImpl
      * @return a key to uniquely identify a prepared statement.
      */
     protected PStmtKey createKey(final String sql, final String[] columnNames) {
-        return new PStmtKey(normalizeSQL(sql), getCatalogOrNull(), getSchemaOrNull(), columnNames);
+        return new PStmtKey(sql, getCatalogOrNull(), getSchemaOrNull(), columnNames);
     }
 
     /**
@@ -338,7 +333,7 @@ class PooledConnectionImpl
      */
     @Override
     public void destroyObject(final PStmtKey key, final PooledObject<DelegatingPreparedStatement> pooledObject)
-            throws Exception {
+            throws SQLException {
         pooledObject.getObject().getInnermostDelegate().close();
     }
 
@@ -349,12 +344,7 @@ class PooledConnectionImpl
     protected void finalize() throws Throwable {
         // Closing the Connection ensures that if anyone tries to use it,
         // an error will occur.
-        try {
-            connection.close();
-        } catch (final Exception ignored) {
-            // ignore
-        }
-
+        Utils.close(connection, null);
         // make sure the last connection is marked as closed
         if (logicalConnection != null && !logicalConnection.isClosed()) {
             throw new SQLException("PooledConnection was gc'ed, without its last Connection being closed.");
@@ -415,7 +405,7 @@ class PooledConnectionImpl
      *            The key for the {@link PreparedStatement} to be created.
      */
     @Override
-    public PooledObject<DelegatingPreparedStatement> makeObject(final PStmtKey key) throws Exception {
+    public PooledObject<DelegatingPreparedStatement> makeObject(final PStmtKey key) throws SQLException {
         if (null == key) {
             throw new IllegalArgumentException("Prepared statement key is null or invalid.");
         }
@@ -433,25 +423,12 @@ class PooledConnectionImpl
         return new DefaultPooledObject<>(pcs);
     }
 
-    /**
-     * Normalizes the given SQL statement, producing a canonical form that is semantically equivalent to the original.
-     * @param sql
-     *            The SQL statement.
-     * @return the normalized SQL statement.
-     */
-    protected String normalizeSQL(final String sql) {
-        return sql.trim();
-    }
-
     /**
      * Sends a connectionClosed event.
      */
     void notifyListeners() {
         final ConnectionEvent event = new ConnectionEvent(this);
-        final Object[] listeners = eventListeners.toArray();
-        for (final Object listener : listeners) {
-            ((ConnectionEventListener) listener).connectionClosed(event);
-        }
+        new ArrayList<>(eventListeners).forEach(listener -> listener.connectionClosed(event));
     }
 
     /**
@@ -465,7 +442,7 @@ class PooledConnectionImpl
      */
     @Override
     public void passivateObject(final PStmtKey key, final PooledObject<DelegatingPreparedStatement> pooledObject)
-            throws Exception {
+            throws SQLException {
         final DelegatingPreparedStatement dps = pooledObject.getObject();
         dps.clearParameters();
         dps.passivate();
@@ -477,8 +454,8 @@ class PooledConnectionImpl
      * @param sql
      *            an SQL statement that may contain one or more '?' parameter placeholders. Typically this statement is
      *            specified using JDBC call escape syntax.
-     * @return a default <code>CallableStatement</code> object containing the pre-compiled SQL statement.
-     * @exception SQLException
+     * @return a default {@code CallableStatement} object containing the pre-compiled SQL statement.
+     * @throws SQLException
      *                Thrown if a database access error occurs or this method is called on a closed connection.
      * @since 2.4.0
      */
@@ -499,19 +476,19 @@ class PooledConnectionImpl
      * Creates or obtains a {@link CallableStatement} from my pool.
      *
      * @param sql
-     *            a <code>String</code> object that is the SQL statement to be sent to the database; may contain on or
+     *            a {@code String} object that is the SQL statement to be sent to the database; may contain on or
      *            more '?' parameters.
      * @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>.
+     *            a result set type; one of {@code ResultSet.TYPE_FORWARD_ONLY},
+     *            {@code ResultSet.TYPE_SCROLL_INSENSITIVE}, or {@code ResultSet.TYPE_SCROLL_SENSITIVE}.
      * @param resultSetConcurrency
-     *            a concurrency type; one of <code>ResultSet.CONCUR_READ_ONLY</code> or
-     *            <code>ResultSet.CONCUR_UPDATABLE</code>.
-     * @return a <code>CallableStatement</code> object containing the pre-compiled SQL statement that will produce
-     *         <code>ResultSet</code> objects with the given type and concurrency.
+     *            a concurrency type; one of {@code ResultSet.CONCUR_READ_ONLY} or
+     *            {@code ResultSet.CONCUR_UPDATABLE}.
+     * @return a {@code CallableStatement} object containing the pre-compiled SQL statement that will produce
+     *         {@code ResultSet} objects with the given type and concurrency.
      * @throws SQLException
      *             Thrown if a database access error occurs, this method is called on a closed connection or the given
-     *             parameters are not <code>ResultSet</code> constants indicating type and concurrency.
+     *             parameters are not {@code ResultSet} constants indicating type and concurrency.
      * @since 2.4.0
      */
     CallableStatement prepareCall(final String sql, final int resultSetType, final int resultSetConcurrency)
@@ -533,22 +510,22 @@ class PooledConnectionImpl
      * Creates or obtains a {@link CallableStatement} from my pool.
      *
      * @param sql
-     *            a <code>String</code> object that is the SQL statement to be sent to the database; may contain on or
+     *            a {@code String} object that is the SQL statement to be sent to the database; may contain on or
      *            more '?' parameters.
      * @param resultSetType
-     *            one of the following <code>ResultSet</code> constants: <code>ResultSet.TYPE_FORWARD_ONLY</code>,
-     *            <code>ResultSet.TYPE_SCROLL_INSENSITIVE</code>, or <code>ResultSet.TYPE_SCROLL_SENSITIVE</code>.
+     *            one of the following {@code ResultSet} constants: {@code ResultSet.TYPE_FORWARD_ONLY},
+     *            {@code ResultSet.TYPE_SCROLL_INSENSITIVE}, or {@code ResultSet.TYPE_SCROLL_SENSITIVE}.
      * @param resultSetConcurrency
-     *            one of the following <code>ResultSet</code> constants: <code>ResultSet.CONCUR_READ_ONLY</code> or
-     *            <code>ResultSet.CONCUR_UPDATABLE</code>.
+     *            one of the following {@code ResultSet} constants: {@code ResultSet.CONCUR_READ_ONLY} or
+     *            {@code ResultSet.CONCUR_UPDATABLE}.
      * @param resultSetHoldability
-     *            one of the following <code>ResultSet</code> constants: <code>ResultSet.HOLD_CURSORS_OVER_COMMIT</code>
-     *            or <code>ResultSet.CLOSE_CURSORS_AT_COMMIT</code>.
-     * @return a new <code>CallableStatement</code> object, containing the pre-compiled SQL statement, that will
-     *         generate <code>ResultSet</code> objects with the given type, concurrency, and holdability.
+     *            one of the following {@code ResultSet} constants: {@code ResultSet.HOLD_CURSORS_OVER_COMMIT}
+     *            or {@code ResultSet.CLOSE_CURSORS_AT_COMMIT}.
+     * @return a new {@code CallableStatement} object, containing the pre-compiled SQL statement, that will
+     *         generate {@code ResultSet} objects with the given type, concurrency, and holdability.
      * @throws SQLException
      *             Thrown if a database access error occurs, this method is called on a closed connection or the given
-     *             parameters are not <code>ResultSet</code> constants indicating type, concurrency, and holdability.
+     *             parameters are not {@code ResultSet} constants indicating type, concurrency, and holdability.
      * @since 2.4.0
      */
     CallableStatement prepareCall(final String sql, final int resultSetType, final int resultSetConcurrency,
@@ -594,7 +571,7 @@ class PooledConnectionImpl
      *            an SQL statement that may contain one or more '?' IN parameter placeholders.
      * @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>.
+     *            {@code Statement.RETURN_GENERATED_KEYS} or {@code Statement.NO_GENERATED_KEYS}.
      * @return a {@link PoolablePreparedStatement}
      * @throws SQLException Thrown if a database access error occurs, this method is called on a closed connection, or
      *         the borrow failed.
@@ -617,14 +594,14 @@ class PooledConnectionImpl
      * Creates or obtains a {@link PreparedStatement} from my pool.
      *
      * @param sql
-     *            a <code>String</code> object that is the SQL statement to be sent to the database; may contain one or
+     *            a {@code String} object that is the SQL statement to be sent to the database; may contain one or
      *            more '?' IN parameters.
      * @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>.
+     *            a result set type; one of {@code ResultSet.TYPE_FORWARD_ONLY},
+     *            {@code ResultSet.TYPE_SCROLL_INSENSITIVE}, or {@code ResultSet.TYPE_SCROLL_SENSITIVE}.
      * @param resultSetConcurrency
-     *            a concurrency type; one of <code>ResultSet.CONCUR_READ_ONLY</code> or
-     *            <code>ResultSet.CONCUR_UPDATABLE</code>.
+     *            a concurrency type; one of {@code ResultSet.CONCUR_READ_ONLY} or
+     *            {@code ResultSet.CONCUR_UPDATABLE}.
      *
      * @return a {@link PoolablePreparedStatement}.
      * @throws SQLException Thrown if a database access error occurs, this method is called on a closed connection, or
diff --git a/java/org/apache/tomcat/dbcp/dbcp2/cpdsadapter/package-info.java b/java/org/apache/tomcat/dbcp/dbcp2/cpdsadapter/package-info.java
index c36512bdca..e5496f3bfd 100644
--- a/java/org/apache/tomcat/dbcp/dbcp2/cpdsadapter/package-info.java
+++ b/java/org/apache/tomcat/dbcp/dbcp2/cpdsadapter/package-info.java
@@ -18,8 +18,8 @@
 /**
  * <p>
  * This package contains one public class which is a
- * <code>ConnectionPoolDataSource</code> (CPDS) implementation that can be used to
- * adapt older <code>Driver</code> based JDBC implementations. Below is an
+ * {@code ConnectionPoolDataSource} (CPDS) implementation that can be used to
+ * adapt older {@code Driver} based JDBC implementations. Below is an
  * example of setting up the CPDS to be available via JNDI in the
  * catalina servlet container.
  * </p>
diff --git a/java/org/apache/tomcat/dbcp/dbcp2/datasources/CPDSConnectionFactory.java b/java/org/apache/tomcat/dbcp/dbcp2/datasources/CPDSConnectionFactory.java
index 7ee9031fd2..ec67369a94 100644
--- a/java/org/apache/tomcat/dbcp/dbcp2/datasources/CPDSConnectionFactory.java
+++ b/java/org/apache/tomcat/dbcp/dbcp2/datasources/CPDSConnectionFactory.java
@@ -21,7 +21,6 @@ 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;
@@ -43,7 +42,7 @@ import org.apache.tomcat.dbcp.pool2.impl.DefaultPooledObject;
  *
  * @since 2.0
  */
-class CPDSConnectionFactory
+final class CPDSConnectionFactory
         implements PooledObjectFactory<PooledConnectionAndInfo>, ConnectionEventListener, PooledConnectionManager {
 
     private static final String NO_KEY_MESSAGE = "close() was called on a Connection, but I have no record of the underlying PooledConnection.";
@@ -176,7 +175,7 @@ class CPDSConnectionFactory
     }
 
     @Override
-    public void activateObject(final PooledObject<PooledConnectionAndInfo> p) throws Exception {
+    public void activateObject(final PooledObject<PooledConnectionAndInfo> p) throws SQLException {
         validateLifetime(p);
     }
 
@@ -256,11 +255,11 @@ class CPDSConnectionFactory
      * Closes the PooledConnection and stops listening for events from it.
      */
     @Override
-    public void destroyObject(final PooledObject<PooledConnectionAndInfo> p) throws Exception {
+    public void destroyObject(final PooledObject<PooledConnectionAndInfo> p) throws SQLException {
         doDestroyObject(p.getObject());
     }
 
-    private void doDestroyObject(final PooledConnectionAndInfo pci) throws Exception {
+    private void doDestroyObject(final PooledConnectionAndInfo pci) throws SQLException {
         final PooledConnection pc = pci.getPooledConnection();
         pc.removeConnectionEventListener(this);
         pcMap.remove(pc);
@@ -304,13 +303,8 @@ class CPDSConnectionFactory
         }
     }
 
-    // ***********************************************************************
-    // java.sql.ConnectionEventListener implementation
-    // ***********************************************************************
-
     @Override
     public synchronized PooledObject<PooledConnectionAndInfo> makeObject() {
-        final PooledConnectionAndInfo pci;
         try {
             PooledConnection pc = null;
             if (userPassKey.getUserName() == null) {
@@ -318,24 +312,22 @@ class CPDSConnectionFactory
             } 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);
+            final PooledConnectionAndInfo pci = new PooledConnectionAndInfo(pc, userPassKey);
             pcMap.put(pc, pci);
+            return new DefaultPooledObject<>(pci);
         } catch (final SQLException e) {
             throw new RuntimeException(e.getMessage());
         }
-        return new DefaultPooledObject<>(pci);
     }
 
     @Override
-    public void passivateObject(final PooledObject<PooledConnectionAndInfo> p) throws Exception {
+    public void passivateObject(final PooledObject<PooledConnectionAndInfo> p) throws SQLException {
         validateLifetime(p);
     }
 
@@ -434,13 +426,8 @@ class CPDSConnectionFactory
         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));
-            }
-        }
+    private void validateLifetime(final PooledObject<PooledConnectionAndInfo> p) throws SQLException {
+        Utils.validateLifetime(p, maxConnDuration);
     }
 
     @Override
diff --git a/java/org/apache/tomcat/dbcp/dbcp2/datasources/CharArray.java b/java/org/apache/tomcat/dbcp/dbcp2/datasources/CharArray.java
index 58b4d9a2de..e257bda71a 100644
--- a/java/org/apache/tomcat/dbcp/dbcp2/datasources/CharArray.java
+++ b/java/org/apache/tomcat/dbcp/dbcp2/datasources/CharArray.java
@@ -17,6 +17,7 @@
 
 package org.apache.tomcat.dbcp.dbcp2.datasources;
 
+import java.io.Serializable;
 import java.util.Arrays;
 
 import org.apache.tomcat.dbcp.dbcp2.Utils;
@@ -29,7 +30,9 @@ import org.apache.tomcat.dbcp.dbcp2.Utils;
  *
  * @since 2.9.0
  */
-final class CharArray {
+final class CharArray implements Serializable {
+
+    private static final long serialVersionUID = 1L;
 
     static final CharArray NULL = new CharArray((char[]) null);
 
@@ -70,7 +73,7 @@ final class CharArray {
      * @return value, may be null.
      */
     char[] get() {
-        return chars == null ? null : chars.clone();
+        return Utils.clone(chars);
     }
 
     @Override
@@ -78,11 +81,4 @@ final class CharArray {
         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 43cd8ba601..2fbfb18a24 100644
--- a/java/org/apache/tomcat/dbcp/dbcp2/datasources/InstanceKeyDataSource.java
+++ b/java/org/apache/tomcat/dbcp/dbcp2/datasources/InstanceKeyDataSource.java
@@ -34,36 +34,37 @@ import javax.sql.ConnectionPoolDataSource;
 import javax.sql.DataSource;
 import javax.sql.PooledConnection;
 
+import org.apache.tomcat.dbcp.dbcp2.Utils;
 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>
- * The base class for <code>SharedPoolDataSource</code> and <code>PerUserPoolDataSource</code>. Many of the
+ * The base class for {@code SharedPoolDataSource} and {@code PerUserPoolDataSource}. Many of the
  * configuration properties are shared and defined here. This class is declared public in order to allow particular
  * usage with commons-beanutils; do not make direct use of it outside of <em>commons-dbcp2</em>.
  * </p>
  *
  * <p>
- * A J2EE container will normally provide some method of initializing the <code>DataSource</code> whose attributes are
+ * A J2EE container will normally provide some method of initializing the {@code DataSource} whose attributes are
  * presented as bean getters/setters and then deploying it via JNDI. It is then available to an application as a source
  * of pooled logical connections to the database. The pool needs a source of physical connections. This source is in the
- * form of a <code>ConnectionPoolDataSource</code> that can be specified via the {@link #setDataSourceName(String)} used
+ * form of a {@code ConnectionPoolDataSource} that can be specified via the {@link #setDataSourceName(String)} used
  * to lookup the source via JNDI.
  * </p>
  *
  * <p>
  * Although normally used within a JNDI environment, A DataSource can be instantiated and initialized as any bean. In
- * this case the <code>ConnectionPoolDataSource</code> will likely be instantiated in a similar manner. This class
+ * this case the {@code ConnectionPoolDataSource} will likely be instantiated in a similar manner. This class
  * allows the physical source of connections to be attached directly to this pool using the
  * {@link #setConnectionPoolDataSource(ConnectionPoolDataSource)} method.
  * </p>
  *
  * <p>
  * The dbcp package contains an adapter, {@link org.apache.tomcat.dbcp.dbcp2.cpdsadapter.DriverAdapterCPDS}, that can be
- * used to allow the use of <code>DataSource</code>'s based on this class with JDBC driver implementations that do not
- * supply a <code>ConnectionPoolDataSource</code>, but still provide a {@link java.sql.Driver} implementation.
+ * used to allow the use of {@code DataSource}'s based on this class with JDBC driver implementations that do not
+ * supply a {@code ConnectionPoolDataSource}, but still provide a {@link java.sql.Driver} implementation.
  * </p>
  *
  * <p>
@@ -159,7 +160,7 @@ public abstract class InstanceKeyDataSource implements DataSource, Referenceable
      * Closes the connection pool being maintained by this datasource.
      */
     @Override
-    public abstract void close() throws Exception;
+    public abstract void close() throws SQLException;
 
     private void closeDueToException(final PooledConnectionAndInfo info) {
         if (info != null) {
@@ -185,13 +186,13 @@ public abstract class InstanceKeyDataSource implements DataSource, Referenceable
     /**
      * 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
+     * {@code getPooledConnectionAndInfo} is compared to the {@code password} 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.
+     * changed. In this case, the {@code PooledConnectionAndInfo} instance retrieved with the old password is
+     * destroyed and the {@code getPooledConnectionAndInfo} is repeatedly invoked until a
+     * {@code PooledConnectionAndInfo} instance with the new password is returned.
      */
     @Override
     public Connection getConnection(final String userName, final String userPassword) throws SQLException {
@@ -263,11 +264,7 @@ public abstract class InstanceKeyDataSource implements DataSource, Referenceable
             connection.clearWarnings();
             return connection;
         } catch (final SQLException ex) {
-            try {
-                connection.close();
-            } catch (final Exception exc) {
-                getLogWriter().println("ignoring exception during close: " + exc);
-            }
+            Utils.close(connection, e -> getLogWriter().println("ignoring exception during close: " + e));
             throw ex;
         }
     }
@@ -668,7 +665,7 @@ public abstract class InstanceKeyDataSource implements DataSource, Referenceable
 
     /**
      * 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
+     * can be changed on the Connection using Connection.setAutoCommit(boolean). The default is {@code null} which
      * will use the default value for the drive.
      *
      * @return value of defaultAutoCommit.
@@ -679,7 +676,7 @@ public abstract class InstanceKeyDataSource implements DataSource, Referenceable
 
     /**
      * 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
+     * can be changed on the Connection using Connection.setReadOnly(boolean). The default is {@code null} which
      * will use the default value for the drive.
      *
      * @return value of defaultReadOnly.
@@ -744,7 +741,7 @@ public abstract class InstanceKeyDataSource implements DataSource, Referenceable
 
     /**
      * 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
+     * can be changed on the Connection using Connection.setAutoCommit(boolean). The default is {@code null} which
      * will use the default value for the drive.
      *
      * @param defaultAutoCommit
@@ -904,7 +901,7 @@ public abstract class InstanceKeyDataSource implements DataSource, Referenceable
 
     /**
      * Sets 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
+     * can be changed on the Connection using Connection.setReadOnly(boolean). The default is {@code null} which
      * will use the default value for the drive.
      *
      * @param defaultReadOnly
@@ -1236,7 +1233,7 @@ public abstract class InstanceKeyDataSource implements DataSource, Referenceable
             if (conn != null) {
                 try {
                     conn.close();
-                } catch (final SQLException e) {
+                } catch (final SQLException ignored) {
                     // at least we could connect
                 }
             }
diff --git a/java/org/apache/tomcat/dbcp/dbcp2/datasources/InstanceKeyDataSourceFactory.java b/java/org/apache/tomcat/dbcp/dbcp2/datasources/InstanceKeyDataSourceFactory.java
index ad01b8abb1..325fad1b78 100644
--- a/java/org/apache/tomcat/dbcp/dbcp2/datasources/InstanceKeyDataSourceFactory.java
+++ b/java/org/apache/tomcat/dbcp/dbcp2/datasources/InstanceKeyDataSourceFactory.java
@@ -24,7 +24,6 @@ import java.util.ArrayList;
 import java.util.Hashtable;
 import java.util.List;
 import java.util.Map;
-import java.util.Map.Entry;
 import java.util.Properties;
 import java.util.concurrent.ConcurrentHashMap;
 
@@ -38,7 +37,7 @@ 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
+ * A JNDI ObjectFactory which creates {@code SharedPoolDataSource}s or {@code PerUserPoolDataSource}s
  *
  * @since 2.0
  */
@@ -49,29 +48,22 @@ abstract class InstanceKeyDataSourceFactory implements ObjectFactory {
     /**
      * Closes all pools associated with this class.
      *
-     * @throws Exception
+     * @throws ListException
      *             a {@link ListException} containing all exceptions thrown by {@link InstanceKeyDataSource#close()}
      * @see InstanceKeyDataSource#close()
-     * @see ListException
      * @since 2.4.0 throws a {@link ListException} instead of, in 2.3.0 and before, the first exception thrown by
      *        {@link InstanceKeyDataSource#close()}.
      */
-    public static void closeAll() throws Exception {
+    public static void closeAll() throws ListException {
         // Get iterator to loop over all instances of this data source.
         final List<Throwable> exceptionList = new ArrayList<>(INSTANCE_MAP.size());
-        for (final Entry<String, InstanceKeyDataSource> next : INSTANCE_MAP.entrySet()) {
+        INSTANCE_MAP.entrySet().forEach(entry -> {
             // Bullet-proof to avoid anything else but problems from InstanceKeyDataSource#close().
-            if (next != null) {
-                final InstanceKeyDataSource value = next.getValue();
-                if (value != null) {
-                    try {
-                        value.close();
-                    } catch (final Exception e) {
-                        exceptionList.add(e);
-                    }
-                }
+            if (entry != null) {
+                final InstanceKeyDataSource value = entry.getValue();
+                Utils.close(value, exceptionList::add);
             }
-        }
+        });
         INSTANCE_MAP.clear();
         if (!exceptionList.isEmpty()) {
             throw new ListException("Could not close all InstanceKeyDataSource instances.", exceptionList);
@@ -107,7 +99,7 @@ abstract class InstanceKeyDataSourceFactory implements ObjectFactory {
             if (s != null) {
                 try {
                     max = Math.max(max, Integer.parseInt(s));
-                } catch (final NumberFormatException e) {
+                } catch (final NumberFormatException ignored) {
                     // no sweat, ignore those keys
                 }
             }
diff --git a/java/org/apache/tomcat/dbcp/dbcp2/datasources/KeyedCPDSConnectionFactory.java b/java/org/apache/tomcat/dbcp/dbcp2/datasources/KeyedCPDSConnectionFactory.java
index 10595ff52d..a67d4a4dd8 100644
--- a/java/org/apache/tomcat/dbcp/dbcp2/datasources/KeyedCPDSConnectionFactory.java
+++ b/java/org/apache/tomcat/dbcp/dbcp2/datasources/KeyedCPDSConnectionFactory.java
@@ -21,7 +21,6 @@ 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;
@@ -44,7 +43,7 @@ import org.apache.tomcat.dbcp.pool2.impl.DefaultPooledObject;
  *
  * @since 2.0
  */
-class KeyedCPDSConnectionFactory implements KeyedPooledObjectFactory<UserPassKey, PooledConnectionAndInfo>,
+final class KeyedCPDSConnectionFactory implements KeyedPooledObjectFactory<UserPassKey, PooledConnectionAndInfo>,
         ConnectionEventListener, PooledConnectionManager {
 
     private static final String NO_KEY_MESSAGE = "close() was called on a Connection, but "
@@ -112,7 +111,7 @@ class KeyedCPDSConnectionFactory implements KeyedPooledObjectFactory<UserPassKey
     }
 
     @Override
-    public void activateObject(final UserPassKey key, final PooledObject<PooledConnectionAndInfo> p) throws Exception {
+    public void activateObject(final UserPassKey key, final PooledObject<PooledConnectionAndInfo> p) throws SQLException {
         validateLifetime(p);
     }
 
@@ -187,7 +186,7 @@ class KeyedCPDSConnectionFactory implements KeyedPooledObjectFactory<UserPassKey
      * Closes the PooledConnection and stops listening for events from it.
      */
     @Override
-    public void destroyObject(final UserPassKey key, final PooledObject<PooledConnectionAndInfo> p) throws Exception {
+    public void destroyObject(final UserPassKey key, final PooledObject<PooledConnectionAndInfo> p) throws SQLException {
         final PooledConnection pooledConnection = p.getObject().getPooledConnection();
         pooledConnection.removeConnectionEventListener(this);
         pcMap.remove(pooledConnection);
@@ -224,10 +223,6 @@ class KeyedCPDSConnectionFactory implements KeyedPooledObjectFactory<UserPassKey
         }
     }
 
-    // ***********************************************************************
-    // java.sql.ConnectionEventListener implementation
-    // ***********************************************************************
-
     /**
      * Creates a new {@code PooledConnectionAndInfo} from the given {@code UserPassKey}.
      *
@@ -235,10 +230,10 @@ class KeyedCPDSConnectionFactory implements KeyedPooledObjectFactory<UserPassKey
      *            {@code UserPassKey} containing user credentials
      * @throws SQLException
      *             if the connection could not be created.
-     * @see org.apache.tomcat.dbcp.pool2.KeyedPooledObjectFactory#makeObject(java.lang.Object)
+     * @see org.apache.tomcat.dbcp.pool2.KeyedPooledObjectFactory#makeObject(Object)
      */
     @Override
-    public synchronized PooledObject<PooledConnectionAndInfo> makeObject(final UserPassKey userPassKey) throws Exception {
+    public synchronized PooledObject<PooledConnectionAndInfo> makeObject(final UserPassKey userPassKey) throws SQLException {
         PooledConnection pooledConnection = null;
         final String userName = userPassKey.getUserName();
         final String password = userPassKey.getPassword();
@@ -262,7 +257,7 @@ class KeyedCPDSConnectionFactory implements KeyedPooledObjectFactory<UserPassKey
     }
 
     @Override
-    public void passivateObject(final UserPassKey key, final PooledObject<PooledConnectionAndInfo> p) throws Exception {
+    public void passivateObject(final UserPassKey key, final PooledObject<PooledConnectionAndInfo> p) throws SQLException {
         validateLifetime(p);
     }
 
@@ -317,13 +312,8 @@ class KeyedCPDSConnectionFactory implements KeyedPooledObjectFactory<UserPassKey
         this.pool = pool;
     }
 
-    private void validateLifetime(final PooledObject<PooledConnectionAndInfo> p) throws Exception {
-        if (maxConnLifetime.compareTo(Duration.ZERO) > 0) {
-            final Duration lifetimeDuration = Duration.between(p.getCreateInstant(), Instant.now());
-            if (lifetimeDuration.compareTo(maxConnLifetime) > 0) {
-                throw new Exception(Utils.getMessage("connectionFactory.lifetimeExceeded", lifetimeDuration, maxConnLifetime));
-            }
-        }
+    private void validateLifetime(final PooledObject<PooledConnectionAndInfo> pooledObject) throws SQLException {
+        Utils.validateLifetime(pooledObject, maxConnLifetime);
     }
 
     /**
diff --git a/java/org/apache/tomcat/dbcp/dbcp2/datasources/PerUserPoolDataSource.java b/java/org/apache/tomcat/dbcp/dbcp2/datasources/PerUserPoolDataSource.java
index 02331d7fa5..6fe183f903 100644
--- a/java/org/apache/tomcat/dbcp/dbcp2/datasources/PerUserPoolDataSource.java
+++ b/java/org/apache/tomcat/dbcp/dbcp2/datasources/PerUserPoolDataSource.java
@@ -39,7 +39,7 @@ import org.apache.tomcat.dbcp.pool2.impl.GenericObjectPool;
 
 /**
  * <p>
- * A pooling <code>DataSource</code> appropriate for deployment within J2EE environment. There are many configuration
+ * A pooling {@code DataSource} appropriate for deployment within J2EE environment. There are many configuration
  * options, most of which are defined in the parent class. This datasource uses individual pools per user, and some
  * properties can be set specifically for a given user, if the deployment environment can support initialization of
  * mapped properties. So for example, a pool of admin or write-access Connections can be guaranteed a certain number of
@@ -48,8 +48,8 @@ import org.apache.tomcat.dbcp.pool2.impl.GenericObjectPool;
  *
  * <p>
  * User passwords can be changed without re-initializing the datasource. When a
- * <code>getConnection(userName, password)</code> request is processed with a password that is different from those used
- * to create connections in the pool associated with <code>userName</code>, an attempt is made to create a new
+ * {@code getConnection(userName, password)} request is processed with a password that is different from those used
+ * to create connections in the pool associated with {@code userName}, an attempt is made to create a new
  * connection using the supplied password and if this succeeds, the existing pool is cleared and a new pool is created
  * for connections using the new password.
  * </p>
@@ -62,7 +62,11 @@ public class PerUserPoolDataSource extends InstanceKeyDataSource {
 
     private static final Log log = LogFactory.getLog(PerUserPoolDataSource.class);
 
-    // Per user pool properties
+    private static <K, V> HashMap<K, V> createMap() {
+        // Should there be a default size different than what this ctor provides?
+        return new HashMap<>();
+    }
+
     private Map<String, Boolean> perUserBlockWhenExhausted;
     private Map<String, String> perUserEvictionPolicyClassName;
     private Map<String, Boolean> perUserLifo;
@@ -78,8 +82,6 @@ public class PerUserPoolDataSource extends InstanceKeyDataSource {
     private Map<String, Boolean> perUserTestOnReturn;
     private Map<String, Boolean> perUserTestWhileIdle;
     private Map<String, Duration> perUserDurationBetweenEvictionRuns;
-
-    // Per user connection properties
     private Map<String, Boolean> perUserDefaultAutoCommit;
     private Map<String, Integer> perUserDefaultTransactionIsolation;
     private Map<String, Boolean> perUserDefaultReadOnly;
@@ -87,7 +89,7 @@ public class PerUserPoolDataSource extends InstanceKeyDataSource {
     /**
      * Map to keep track of Pools for a given user.
      */
-    private transient Map<PoolKey, PooledConnectionManager> managers = new HashMap<>();
+    private transient Map<PoolKey, PooledConnectionManager> managers = createMap();
 
     /**
      * Default no-arg constructor for Serialization.
@@ -102,13 +104,13 @@ public class PerUserPoolDataSource extends InstanceKeyDataSource {
      * @since 2.3.0
      */
     public void clear() {
-        for (final PooledConnectionManager manager : managers.values()) {
+        managers.values().forEach(manager -> {
             try {
                 getCPDSConnectionFactoryPool(manager).clear();
-            } catch (final Exception closePoolException) {
+            } catch (final Exception ignored) {
                 // ignore and try to close others.
             }
-        }
+        });
         InstanceKeyDataSourceFactory.removeInstance(getInstanceKey());
     }
 
@@ -119,9 +121,7 @@ public class PerUserPoolDataSource extends InstanceKeyDataSource {
      */
     @Override
     public void close() {
-        for (final PooledConnectionManager manager : managers.values()) {
-            Utils.closeQuietly(getCPDSConnectionFactoryPool(manager));
-        }
+        managers.values().forEach(manager -> Utils.closeQuietly(getCPDSConnectionFactoryPool(manager)));
         InstanceKeyDataSourceFactory.removeInstance(getInstanceKey());
     }
 
@@ -129,7 +129,7 @@ public class PerUserPoolDataSource extends InstanceKeyDataSource {
      * Converts a map with Long milliseconds values to another map with Duration values.
      */
     private Map<String, Duration> convertMap(final Map<String, Duration> currentMap, final Map<String, Long> longMap) {
-        final Map<String, Duration> durationMap = new HashMap<>();
+        final Map<String, Duration> durationMap = createMap();
         longMap.forEach((k, v) -> durationMap.put(k, toDurationOrNull(v)));
         if (currentMap == null) {
             return durationMap;
@@ -140,11 +140,6 @@ public class PerUserPoolDataSource extends InstanceKeyDataSource {
 
     }
 
-    private HashMap<String, Boolean> createMap() {
-        // Should there be a default size different than what this ctor provides?
-        return new HashMap<>();
-    }
-
     @Override
     protected PooledConnectionManager getConnectionManager(final UserPassKey upKey) {
         return managers.get(getPoolKey(upKey.getUserName()));
@@ -669,7 +664,7 @@ public class PerUserPoolDataSource extends InstanceKeyDataSource {
     }
 
     /**
-     * Returns a <code>PerUserPoolDataSource</code> {@link Reference}.
+     * Returns a {@code PerUserPoolDataSource} {@link Reference}.
      */
     @Override
     public Reference getReference() throws NamingException {
@@ -678,21 +673,19 @@ public class PerUserPoolDataSource extends InstanceKeyDataSource {
         return ref;
     }
 
-    private Map<String, Duration> makeMap(final Map<String, Duration> currentMap, final Map<String, Duration> newMap) {
-        if (currentMap == null) {
-            return new HashMap<>(newMap);
+    <K, V> Map<K, V> put(Map<K, V> map, final K key, final V value) {
+        if (map == null) {
+            map = createMap();
         }
-        currentMap.clear();
-        currentMap.putAll(newMap);
-        return currentMap;
-
+        map.put(key, value);
+        return map;
     }
 
     /**
      * Supports Serialization interface.
      *
      * @param in
-     *            a <code>java.io.ObjectInputStream</code> value
+     *            a {@code java.io.ObjectInputStream} value
      * @throws IOException
      *             if an error occurs
      * @throws ClassNotFoundException
@@ -748,14 +741,18 @@ public class PerUserPoolDataSource extends InstanceKeyDataSource {
         }
     }
 
-    void setPerUserBlockWhenExhausted(final Map<String, Boolean> userDefaultBlockWhenExhausted) {
-        assertInitializationAllowed();
-        if (perUserBlockWhenExhausted == null) {
-            perUserBlockWhenExhausted = createMap();
-        } else {
-            perUserBlockWhenExhausted.clear();
+    private <K, V> Map<K, V> replaceAll(final Map<K, V> currentMap, final Map<K, V> newMap) {
+        if (currentMap == null) {
+            return new HashMap<>(newMap);
         }
-        perUserBlockWhenExhausted.putAll(userDefaultBlockWhenExhausted);
+        currentMap.clear();
+        currentMap.putAll(newMap);
+        return currentMap;
+    }
+
+    void setPerUserBlockWhenExhausted(final Map<String, Boolean> newMap) {
+        assertInitializationAllowed();
+        perUserBlockWhenExhausted = replaceAll(perUserBlockWhenExhausted, newMap);
     }
 
     /**
@@ -768,20 +765,12 @@ public class PerUserPoolDataSource extends InstanceKeyDataSource {
      */
     public void setPerUserBlockWhenExhausted(final String userName, final Boolean value) {
         assertInitializationAllowed();
-        if (perUserBlockWhenExhausted == null) {
-            perUserBlockWhenExhausted = createMap();
-        }
-        perUserBlockWhenExhausted.put(userName, value);
+        perUserBlockWhenExhausted = put(perUserBlockWhenExhausted, userName, value);
     }
 
-    void setPerUserDefaultAutoCommit(final Map<String, Boolean> userDefaultAutoCommit) {
+    void setPerUserDefaultAutoCommit(final Map<String, Boolean> newMap) {
         assertInitializationAllowed();
-        if (perUserDefaultAutoCommit == null) {
-            perUserDefaultAutoCommit = createMap();
-        } else {
-            perUserDefaultAutoCommit.clear();
-        }
-        perUserDefaultAutoCommit.putAll(userDefaultAutoCommit);
+        perUserDefaultAutoCommit = replaceAll(perUserDefaultAutoCommit, newMap);
     }
 
     /**
@@ -794,20 +783,13 @@ public class PerUserPoolDataSource extends InstanceKeyDataSource {
      */
     public void setPerUserDefaultAutoCommit(final String userName, final Boolean value) {
         assertInitializationAllowed();
-        if (perUserDefaultAutoCommit == null) {
-            perUserDefaultAutoCommit = createMap();
-        }
-        perUserDefaultAutoCommit.put(userName, value);
+        perUserDefaultAutoCommit = put(perUserDefaultAutoCommit, userName, value);
+
     }
 
-    void setPerUserDefaultReadOnly(final Map<String, Boolean> userDefaultReadOnly) {
+    void setPerUserDefaultReadOnly(final Map<String, Boolean> newMap) {
         assertInitializationAllowed();
-        if (perUserDefaultReadOnly == null) {
-            perUserDefaultReadOnly = createMap();
-        } else {
-            perUserDefaultReadOnly.clear();
-        }
-        perUserDefaultReadOnly.putAll(userDefaultReadOnly);
+        perUserDefaultReadOnly = replaceAll(perUserDefaultReadOnly, newMap);
     }
 
     /**
@@ -820,20 +802,13 @@ public class PerUserPoolDataSource extends InstanceKeyDataSource {
      */
     public void setPerUserDefaultReadOnly(final String userName, final Boolean value) {
         assertInitializationAllowed();
-        if (perUserDefaultReadOnly == null) {
-            perUserDefaultReadOnly = createMap();
-        }
-        perUserDefaultReadOnly.put(userName, value);
+        perUserDefaultReadOnly = put(perUserDefaultReadOnly, userName, value);
+
     }
 
-    void setPerUserDefaultTransactionIsolation(final Map<String, Integer> userDefaultTransactionIsolation) {
+    void setPerUserDefaultTransactionIsolation(final Map<String, Integer> newMap) {
         assertInitializationAllowed();
-        if (perUserDefaultTransactionIsolation == null) {
-            perUserDefaultTransactionIsolation = new HashMap<>();
-        } else {
-            perUserDefaultTransactionIsolation.clear();
-        }
-        perUserDefaultTransactionIsolation.putAll(userDefaultTransactionIsolation);
+        perUserDefaultTransactionIsolation = replaceAll(perUserDefaultTransactionIsolation, newMap);
     }
 
     /**
@@ -847,15 +822,13 @@ public class PerUserPoolDataSource extends InstanceKeyDataSource {
      */
     public void setPerUserDefaultTransactionIsolation(final String userName, final Integer value) {
         assertInitializationAllowed();
-        if (perUserDefaultTransactionIsolation == null) {
-            perUserDefaultTransactionIsolation = new HashMap<>();
-        }
-        perUserDefaultTransactionIsolation.put(userName, value);
+        perUserDefaultTransactionIsolation = put(perUserDefaultTransactionIsolation, userName, value);
+
     }
 
     void setPerUserDurationBetweenEvictionRuns(final Map<String, Duration> newMap) {
         assertInitializationAllowed();
-        perUserDurationBetweenEvictionRuns = makeMap(perUserDurationBetweenEvictionRuns, newMap);
+        perUserDurationBetweenEvictionRuns = replaceAll(perUserDurationBetweenEvictionRuns, newMap);
     }
 
     /**
@@ -870,20 +843,13 @@ public class PerUserPoolDataSource extends InstanceKeyDataSource {
      */
     public void setPerUserDurationBetweenEvictionRuns(final String userName, final Duration value) {
         assertInitializationAllowed();
-        if (perUserDurationBetweenEvictionRuns == null) {
-            perUserDurationBetweenEvictionRuns = new HashMap<>();
-        }
-        perUserDurationBetweenEvictionRuns.put(userName, value);
+        perUserDurationBetweenEvictionRuns = put(perUserDurationBetweenEvictionRuns, userName, value);
+
     }
 
-    void setPerUserEvictionPolicyClassName(final Map<String, String> userDefaultEvictionPolicyClassName) {
+    void setPerUserEvictionPolicyClassName(final Map<String, String> newMap) {
         assertInitializationAllowed();
-        if (perUserEvictionPolicyClassName == null) {
-            perUserEvictionPolicyClassName = new HashMap<>();
-        } else {
-            perUserEvictionPolicyClassName.clear();
-        }
-        perUserEvictionPolicyClassName.putAll(userDefaultEvictionPolicyClassName);
+        perUserEvictionPolicyClassName = replaceAll(perUserEvictionPolicyClassName, newMap);
     }
 
     /**
@@ -897,20 +863,12 @@ public class PerUserPoolDataSource extends InstanceKeyDataSource {
      */
     public void setPerUserEvictionPolicyClassName(final String userName, final String value) {
         assertInitializationAllowed();
-        if (perUserEvictionPolicyClassName == null) {
-            perUserEvictionPolicyClassName = new HashMap<>();
-        }
-        perUserEvictionPolicyClassName.put(userName, value);
+        perUserEvictionPolicyClassName = put(perUserEvictionPolicyClassName, userName, value);
     }
 
-    void setPerUserLifo(final Map<String, Boolean> userDefaultLifo) {
+    void setPerUserLifo(final Map<String, Boolean> newMap) {
         assertInitializationAllowed();
-        if (perUserLifo == null) {
-            perUserLifo = createMap();
-        } else {
-            perUserLifo.clear();
-        }
-        perUserLifo.putAll(userDefaultLifo);
+        perUserLifo = replaceAll(perUserLifo, newMap);
     }
 
     /**
@@ -923,20 +881,12 @@ public class PerUserPoolDataSource extends InstanceKeyDataSource {
      */
     public void setPerUserLifo(final String userName, final Boolean value) {
         assertInitializationAllowed();
-        if (perUserLifo == null) {
-            perUserLifo = createMap();
-        }
-        perUserLifo.put(userName, value);
+        perUserLifo = put(perUserLifo, userName, value);
     }
 
-    void setPerUserMaxIdle(final Map<String, Integer> userDefaultMaxIdle) {
+    void setPerUserMaxIdle(final Map<String, Integer> newMap) {
         assertInitializationAllowed();
-        if (perUserMaxIdle == null) {
-            perUserMaxIdle = new HashMap<>();
-        } else {
-            perUserMaxIdle.clear();
-        }
-        perUserMaxIdle.putAll(userDefaultMaxIdle);
+        perUserMaxIdle = replaceAll(perUserMaxIdle, newMap);
     }
 
     /**
@@ -949,20 +899,12 @@ public class PerUserPoolDataSource extends InstanceKeyDataSource {
      */
     public void setPerUserMaxIdle(final String userName, final Integer value) {
         assertInitializationAllowed();
-        if (perUserMaxIdle == null) {
-            perUserMaxIdle = new HashMap<>();
-        }
-        perUserMaxIdle.put(userName, value);
+        perUserMaxIdle = put(perUserMaxIdle, userName, value);
     }
 
-    void setPerUserMaxTotal(final Map<String, Integer> userDefaultMaxTotal) {
+    void setPerUserMaxTotal(final Map<String, Integer> newMap) {
         assertInitializationAllowed();
-        if (perUserMaxTotal == null) {
-            perUserMaxTotal = new HashMap<>();
-        } else {
-            perUserMaxTotal.clear();
-        }
-        perUserMaxTotal.putAll(userDefaultMaxTotal);
+        perUserMaxTotal = replaceAll(perUserMaxTotal, newMap);
     }
 
     /**
@@ -975,15 +917,7 @@ public class PerUserPoolDataSource extends InstanceKeyDataSource {
      */
     public void setPerUserMaxTotal(final String userName, final Integer value) {
         assertInitializationAllowed();
-        if (perUserMaxTotal == null) {
-            perUserMaxTotal = new HashMap<>();
-        }
-        perUserMaxTotal.put(userName, value);
-    }
-
-    void setPerUserMaxWaitDuration(final Map<String, Duration> newMap) {
-        assertInitializationAllowed();
-        perUserMaxWaitDuration = makeMap(perUserMaxWaitDuration, newMap);
+        perUserMaxTotal = put(perUserMaxTotal, userName, value);
     }
 
     /**
@@ -997,10 +931,12 @@ public class PerUserPoolDataSource extends InstanceKeyDataSource {
      */
     public void setPerUserMaxWait(final String userName, final Duration value) {
         assertInitializationAllowed();
-        if (perUserMaxWaitDuration == null) {
-            perUserMaxWaitDuration = new HashMap<>();
-        }
-        perUserMaxWaitDuration.put(userName, value);
+        perUserMaxWaitDuration = put(perUserMaxWaitDuration, userName, value);
+    }
+
+    void setPerUserMaxWaitDuration(final Map<String, Duration> newMap) {
+        assertInitializationAllowed();
+        perUserMaxWaitDuration = replaceAll(perUserMaxWaitDuration, newMap);
     }
 
     void setPerUserMaxWaitMillis(final Map<String, Long> newMap) {
@@ -1024,7 +960,7 @@ public class PerUserPoolDataSource extends InstanceKeyDataSource {
 
     void setPerUserMinEvictableIdle(final Map<String, Duration> newMap) {
         assertInitializationAllowed();
-        perUserMinEvictableIdleDuration = makeMap(perUserMinEvictableIdleDuration, newMap);
+        perUserMinEvictableIdleDuration = replaceAll(perUserMinEvictableIdleDuration, newMap);
     }
 
     /**
@@ -1039,10 +975,7 @@ public class PerUserPoolDataSource extends InstanceKeyDataSource {
      */
     public void setPerUserMinEvictableIdle(final String userName, final Duration value) {
         assertInitializationAllowed();
-        if (perUserMinEvictableIdleDuration == null) {
-            perUserMinEvictableIdleDuration = new HashMap<>();
-        }
-        perUserMinEvictableIdleDuration.put(userName, value);
+        perUserMinEvictableIdleDuration = put(perUserMinEvictableIdleDuration, userName, value);
     }
 
     /**
@@ -1060,14 +993,9 @@ public class PerUserPoolDataSource extends InstanceKeyDataSource {
         setPerUserMinEvictableIdle(userName, toDurationOrNull(value));
     }
 
-    void setPerUserMinIdle(final Map<String, Integer> userDefaultMinIdle) {
+    void setPerUserMinIdle(final Map<String, Integer> newMap) {
         assertInitializationAllowed();
-        if (perUserMinIdle == null) {
-            perUserMinIdle = new HashMap<>();
-        } else {
-            perUserMinIdle.clear();
-        }
-        perUserMinIdle.putAll(userDefaultMinIdle);
+        perUserMinIdle = replaceAll(perUserMinIdle, newMap);
     }
 
     /**
@@ -1080,20 +1008,12 @@ public class PerUserPoolDataSource extends InstanceKeyDataSource {
      */
     public void setPerUserMinIdle(final String userName, final Integer value) {
         assertInitializationAllowed();
-        if (perUserMinIdle == null) {
-            perUserMinIdle = new HashMap<>();
-        }
-        perUserMinIdle.put(userName, value);
+        perUserMinIdle = put(perUserMinIdle, userName, value);
     }
 
-    void setPerUserNumTestsPerEvictionRun(final Map<String, Integer> userDefaultNumTestsPerEvictionRun) {
+    void setPerUserNumTestsPerEvictionRun(final Map<String, Integer> newMap) {
         assertInitializationAllowed();
-        if (perUserNumTestsPerEvictionRun == null) {
-            perUserNumTestsPerEvictionRun = new HashMap<>();
-        } else {
-            perUserNumTestsPerEvictionRun.clear();
-        }
-        perUserNumTestsPerEvictionRun.putAll(userDefaultNumTestsPerEvictionRun);
+        perUserNumTestsPerEvictionRun = replaceAll(perUserNumTestsPerEvictionRun, newMap);
     }
 
     /**
@@ -1107,15 +1027,12 @@ public class PerUserPoolDataSource extends InstanceKeyDataSource {
      */
     public void setPerUserNumTestsPerEvictionRun(final String userName, final Integer value) {
         assertInitializationAllowed();
-        if (perUserNumTestsPerEvictionRun == null) {
-            perUserNumTestsPerEvictionRun = new HashMap<>();
-        }
-        perUserNumTestsPerEvictionRun.put(userName, value);
+        perUserNumTestsPerEvictionRun = put(perUserNumTestsPerEvictionRun, userName, value);
     }
 
     void setPerUserSoftMinEvictableIdle(final Map<String, Duration> newMap) {
         assertInitializationAllowed();
-        perUserSoftMinEvictableIdleDuration = makeMap(perUserSoftMinEvictableIdleDuration, newMap);
+        perUserSoftMinEvictableIdleDuration = replaceAll(perUserSoftMinEvictableIdleDuration, newMap);
     }
 
     /**
@@ -1130,10 +1047,7 @@ public class PerUserPoolDataSource extends InstanceKeyDataSource {
      */
     public void setPerUserSoftMinEvictableIdle(final String userName, final Duration value) {
         assertInitializationAllowed();
-        if (perUserSoftMinEvictableIdleDuration == null) {
-            perUserSoftMinEvictableIdleDuration = new HashMap<>();
-        }
-        perUserSoftMinEvictableIdleDuration.put(userName, value);
+        perUserSoftMinEvictableIdleDuration = put(perUserSoftMinEvictableIdleDuration, userName, value);
     }
 
     /**
@@ -1151,14 +1065,9 @@ public class PerUserPoolDataSource extends InstanceKeyDataSource {
         setPerUserSoftMinEvictableIdle(userName, toDurationOrNull(value));
     }
 
-    void setPerUserTestOnBorrow(final Map<String, Boolean> userDefaultTestOnBorrow) {
+    void setPerUserTestOnBorrow(final Map<String, Boolean> newMap) {
         assertInitializationAllowed();
-        if (perUserTestOnBorrow == null) {
-            perUserTestOnBorrow = createMap();
-        } else {
-            perUserTestOnBorrow.clear();
-        }
-        perUserTestOnBorrow.putAll(userDefaultTestOnBorrow);
+        perUserTestOnBorrow = replaceAll(perUserTestOnBorrow, newMap);
     }
 
     /**
@@ -1171,20 +1080,12 @@ public class PerUserPoolDataSource extends InstanceKeyDataSource {
      */
     public void setPerUserTestOnBorrow(final String userName, final Boolean value) {
         assertInitializationAllowed();
-        if (perUserTestOnBorrow == null) {
-            perUserTestOnBorrow = createMap();
-        }
-        perUserTestOnBorrow.put(userName, value);
+        perUserTestOnBorrow = put(perUserTestOnBorrow, userName, value);
     }
 
-    void setPerUserTestOnCreate(final Map<String, Boolean> userDefaultTestOnCreate) {
+    void setPerUserTestOnCreate(final Map<String, Boolean> newMap) {
         assertInitializationAllowed();
-        if (perUserTestOnCreate == null) {
-            perUserTestOnCreate = createMap();
-        } else {
-            perUserTestOnCreate.clear();
-        }
-        perUserTestOnCreate.putAll(userDefaultTestOnCreate);
+        perUserTestOnCreate = replaceAll(perUserTestOnCreate, newMap);
     }
 
     /**
@@ -1197,20 +1098,12 @@ public class PerUserPoolDataSource extends InstanceKeyDataSource {
      */
     public void setPerUserTestOnCreate(final String userName, final Boolean value) {
         assertInitializationAllowed();
-        if (perUserTestOnCreate == null) {
-            perUserTestOnCreate = createMap();
-        }
-        perUserTestOnCreate.put(userName, value);
+        perUserTestOnCreate = put(perUserTestOnCreate, userName, value);
     }
 
-    void setPerUserTestOnReturn(final Map<String, Boolean> userDefaultTestOnReturn) {
+    void setPerUserTestOnReturn(final Map<String, Boolean> newMap) {
         assertInitializationAllowed();
-        if (perUserTestOnReturn == null) {
-            perUserTestOnReturn = createMap();
-        } else {
-            perUserTestOnReturn.clear();
-        }
-        perUserTestOnReturn.putAll(userDefaultTestOnReturn);
+        perUserTestOnReturn = replaceAll(perUserTestOnReturn, newMap);
     }
 
     /**
@@ -1223,20 +1116,12 @@ public class PerUserPoolDataSource extends InstanceKeyDataSource {
      */
     public void setPerUserTestOnReturn(final String userName, final Boolean value) {
         assertInitializationAllowed();
-        if (perUserTestOnReturn == null) {
-            perUserTestOnReturn = createMap();
-        }
-        perUserTestOnReturn.put(userName, value);
+        perUserTestOnReturn = put(perUserTestOnReturn, userName, value);
     }
 
-    void setPerUserTestWhileIdle(final Map<String, Boolean> userDefaultTestWhileIdle) {
+    void setPerUserTestWhileIdle(final Map<String, Boolean> newMap) {
         assertInitializationAllowed();
-        if (perUserTestWhileIdle == null) {
-            perUserTestWhileIdle = createMap();
-        } else {
-            perUserTestWhileIdle.clear();
-        }
-        perUserTestWhileIdle.putAll(userDefaultTestWhileIdle);
+        perUserTestWhileIdle = replaceAll(perUserTestWhileIdle, newMap);
     }
 
     /**
@@ -1249,10 +1134,7 @@ public class PerUserPoolDataSource extends InstanceKeyDataSource {
      */
     public void setPerUserTestWhileIdle(final String userName, final Boolean value) {
         assertInitializationAllowed();
-        if (perUserTestWhileIdle == null) {
-            perUserTestWhileIdle = createMap();
-        }
-        perUserTestWhileIdle.put(userName, value);
+        perUserTestWhileIdle = put(perUserTestWhileIdle, userName, value);
     }
 
     /**
diff --git a/java/org/apache/tomcat/dbcp/dbcp2/datasources/PerUserPoolDataSourceFactory.java b/java/org/apache/tomcat/dbcp/dbcp2/datasources/PerUserPoolDataSourceFactory.java
index 3a2bcf7524..df53faa0b9 100644
--- a/java/org/apache/tomcat/dbcp/dbcp2/datasources/PerUserPoolDataSourceFactory.java
+++ b/java/org/apache/tomcat/dbcp/dbcp2/datasources/PerUserPoolDataSourceFactory.java
@@ -24,7 +24,7 @@ import javax.naming.RefAddr;
 import javax.naming.Reference;
 
 /**
- * A JNDI ObjectFactory which creates <code>SharedPoolDataSource</code>s
+ * A JNDI ObjectFactory which creates {@code SharedPoolDataSource}s
  *
  * @since 2.0
  */
diff --git a/java/org/apache/tomcat/dbcp/dbcp2/datasources/PoolKey.java b/java/org/apache/tomcat/dbcp/dbcp2/datasources/PoolKey.java
index 06c0fe3e6a..9cc08ab23b 100644
--- a/java/org/apache/tomcat/dbcp/dbcp2/datasources/PoolKey.java
+++ b/java/org/apache/tomcat/dbcp/dbcp2/datasources/PoolKey.java
@@ -22,7 +22,7 @@ import java.util.Objects;
 /**
  * @since 2.0
  */
-class PoolKey implements Serializable {
+final class PoolKey implements Serializable {
     private static final long serialVersionUID = 2252771047542484533L;
 
     private final String dataSourceName;
diff --git a/java/org/apache/tomcat/dbcp/dbcp2/datasources/PooledConnectionManager.java b/java/org/apache/tomcat/dbcp/dbcp2/datasources/PooledConnectionManager.java
index 0c1d2c6af8..f72eaa264a 100644
--- a/java/org/apache/tomcat/dbcp/dbcp2/datasources/PooledConnectionManager.java
+++ b/java/org/apache/tomcat/dbcp/dbcp2/datasources/PooledConnectionManager.java
@@ -37,15 +37,7 @@ interface PooledConnectionManager {
      */
     void closePool(String userName) throws SQLException;
 
-    // /**
-    // * Sets the database password used when creating connections.
-    // *
-    // * @param password password used when authenticating to the database
-    // * @since 3.0.0
-    // */
-    // void setPassword(char[] password);
-
-    /**
+     /**
      * Closes the PooledConnection and remove it from the connection pool to which it belongs, adjusting pool counters.
      *
      * @param pc
@@ -55,6 +47,16 @@ interface PooledConnectionManager {
      */
     void invalidate(PooledConnection pc) throws SQLException;
 
+//    /**
+//     * Sets the database password used when creating connections.
+//     *
+//     * @param password password used when authenticating to the database
+//     * @since 2.10.0
+//     */
+//     default void setPassword(char[] password) {
+//         setPassword(String.copyValueOf(password));
+//     }
+
     /**
      * Sets the database password used when creating connections.
      *
diff --git a/java/org/apache/tomcat/dbcp/dbcp2/datasources/SharedPoolDataSource.java b/java/org/apache/tomcat/dbcp/dbcp2/datasources/SharedPoolDataSource.java
index 6073fec07a..029868a377 100644
--- a/java/org/apache/tomcat/dbcp/dbcp2/datasources/SharedPoolDataSource.java
+++ b/java/org/apache/tomcat/dbcp/dbcp2/datasources/SharedPoolDataSource.java
@@ -32,15 +32,15 @@ import org.apache.tomcat.dbcp.pool2.impl.GenericKeyedObjectPoolConfig;
 
 /**
  * <p>
- * A pooling <code>DataSource</code> appropriate for deployment within J2EE environment. There are many configuration
+ * A pooling {@code DataSource} appropriate for deployment within J2EE environment. There are many configuration
  * options, most of which are defined in the parent class. All users (based on user name) share a single maximum number
  * of Connections in this data source.
  * </p>
  *
  * <p>
  * User passwords can be changed without re-initializing the data source. When a
- * <code>getConnection(user name, password)</code> request is processed with a password that is different from those
- * used to create connections in the pool associated with <code>user name</code>, an attempt is made to create a new
+ * {@code getConnection(user name, password)} request is processed with a password that is different from those
+ * used to create connections in the pool associated with {@code user name}, an attempt is made to create a new
  * connection using the supplied password and if this succeeds, idle connections created using the old password are
  * destroyed and new connections are created using the new password.
  * </p>
@@ -68,7 +68,7 @@ public class SharedPoolDataSource extends InstanceKeyDataSource {
      * Closes pool being maintained by this data source.
      */
     @Override
-    public void close() throws Exception {
+    public void close() throws SQLException {
         if (pool != null) {
             pool.close();
         }
@@ -145,7 +145,7 @@ public class SharedPoolDataSource extends InstanceKeyDataSource {
      * Supports Serialization interface.
      *
      * @param in
-     *            a <code>java.io.ObjectInputStream</code> value
+     *            a {@code java.io.ObjectInputStream} value
      * @throws IOException
      *             if an error occurs
      * @throws ClassNotFoundException
diff --git a/java/org/apache/tomcat/dbcp/dbcp2/datasources/UserPassKey.java b/java/org/apache/tomcat/dbcp/dbcp2/datasources/UserPassKey.java
index e763f1192b..02ed24bbcd 100644
--- a/java/org/apache/tomcat/dbcp/dbcp2/datasources/UserPassKey.java
+++ b/java/org/apache/tomcat/dbcp/dbcp2/datasources/UserPassKey.java
@@ -36,7 +36,7 @@ import org.apache.tomcat.dbcp.pool2.KeyedObjectPool;
  *
  * @since 2.0
  */
-class UserPassKey implements Serializable {
+final class UserPassKey implements Serializable {
     private static final long serialVersionUID = 5142970911626584817L;
 
     private final CharArray name;
diff --git a/java/org/apache/tomcat/dbcp/dbcp2/datasources/package-info.java b/java/org/apache/tomcat/dbcp/dbcp2/datasources/package-info.java
index 5b243607b5..25a762d52a 100644
--- a/java/org/apache/tomcat/dbcp/dbcp2/datasources/package-info.java
+++ b/java/org/apache/tomcat/dbcp/dbcp2/datasources/package-info.java
@@ -17,8 +17,8 @@
 
 /**
  * <p>
- * This package contains two DataSources: <code>PerUserPoolDataSource</code> and
- * <code>SharedPoolDataSource</code> which provide a database connection pool.
+ * This package contains two DataSources: {@code PerUserPoolDataSource} and
+ * {@code SharedPoolDataSource} which provide a database connection pool.
  * Below are a couple of usage examples.  One shows deployment into a JNDI system.
  * The other is a simple example initializing the pool using standard java code.
  * </p>
@@ -81,12 +81,12 @@
  * Apache Tomcat deploys all objects configured similarly to above within the
  * <strong>java:comp/env</strong> namespace.  So the JNDI path given for
  * the dataSourceName parameter is valid for a
- * <code>ConnectionPoolDataSource</code> that is deployed as given in the
+ * {@code ConnectionPoolDataSource} that is deployed as given in the
  * <a href="../cpdsadapter/package-summary.html">cpdsadapter example</a>
  * </p>
  *
  * <p>
- * The <code>DataSource</code> is now available to the application as shown
+ * The {@code DataSource} is now available to the application as shown
  * below:
  * </p>
  *
@@ -112,11 +112,11 @@
  * </code>
  *
  * <p>
- * The reference to the <code>DataSource</code> could be maintained, for
- * multiple getConnection() requests.  Or the <code>DataSource</code> can be
+ * The reference to the {@code DataSource} could be maintained, for
+ * multiple getConnection() requests.  Or the {@code DataSource} can be
  * looked up in different parts of the application code.
- * <code>PerUserPoolDataSourceFactory</code> and
- * <code>SharedPoolDataSourceFactory</code> will maintain the state of the pool
+ * {@code PerUserPoolDataSourceFactory} and
+ * {@code SharedPoolDataSourceFactory} will maintain the state of the pool
  * between different lookups.  This behavior may be different in other
  * implementations.
  * </p>
@@ -125,7 +125,7 @@
  *
  * <p>
  * Connection pooling is useful in applications regardless of whether they run
- * in a J2EE environment and a <code>DataSource</code> can be used within a
+ * in a J2EE environment and a {@code DataSource} can be used within a
  * simpler environment.  The example below shows SharedPoolDataSource using
  * DriverAdapterCPDS as the back end source, though any CPDS is applicable.
  * </p>
diff --git a/java/org/apache/tomcat/dbcp/dbcp2/managed/DataSourceXAConnectionFactory.java b/java/org/apache/tomcat/dbcp/dbcp2/managed/DataSourceXAConnectionFactory.java
index 01ac1fbe66..267c866722 100644
--- a/java/org/apache/tomcat/dbcp/dbcp2/managed/DataSourceXAConnectionFactory.java
+++ b/java/org/apache/tomcat/dbcp/dbcp2/managed/DataSourceXAConnectionFactory.java
@@ -38,6 +38,26 @@ import org.apache.tomcat.dbcp.dbcp2.Utils;
  * @since 2.0
  */
 public class DataSourceXAConnectionFactory implements XAConnectionFactory {
+
+    private static final class XAConnectionEventListener implements ConnectionEventListener {
+        @Override
+        public void connectionClosed(final ConnectionEvent event) {
+            final PooledConnection pc = (PooledConnection) event.getSource();
+            pc.removeConnectionEventListener(this);
+            try {
+                pc.close();
+            } catch (final SQLException e) {
+                System.err.println("Failed to close XAConnection");
+                e.printStackTrace();
+            }
+        }
+
+        @Override
+        public void connectionErrorOccurred(final ConnectionEvent event) {
+            connectionClosed(event);
+        }
+    }
+
     private final TransactionRegistry transactionRegistry;
     private final XADataSource xaDataSource;
     private String userName;
@@ -93,15 +113,15 @@ public class DataSourceXAConnectionFactory implements XAConnectionFactory {
      */
     public DataSourceXAConnectionFactory(final TransactionManager transactionManager, final XADataSource xaDataSource,
             final String userName, final char[] userPassword, final TransactionSynchronizationRegistry transactionSynchronizationRegistry) {
-        Objects.requireNonNull(transactionManager, "transactionManager is null");
-        Objects.requireNonNull(xaDataSource, "xaDataSource is null");
+        Objects.requireNonNull(transactionManager, "transactionManager");
+        Objects.requireNonNull(xaDataSource, "xaDataSource");
 
         // We do allow the transactionSynchronizationRegistry to be null for non-app server environments
 
         this.transactionRegistry = new TransactionRegistry(transactionManager, transactionSynchronizationRegistry);
         this.xaDataSource = xaDataSource;
         this.userName = userName;
-        this.userPassword = userPassword == null ? null : userPassword.clone();
+        this.userPassword = Utils.clone(userPassword);
     }
 
     /**
@@ -157,25 +177,7 @@ public class DataSourceXAConnectionFactory implements XAConnectionFactory {
         // The Connection we're returning is a handle on the XAConnection.
         // When the pool calling us closes the Connection, we need to
         // also close the XAConnection that holds the physical connection.
-        xaConnection.addConnectionEventListener(new ConnectionEventListener() {
-
-            @Override
-            public void connectionClosed(final ConnectionEvent event) {
-                final PooledConnection pc = (PooledConnection) event.getSource();
-                pc.removeConnectionEventListener(this);
-                try {
-                    pc.close();
-                } catch (final SQLException e) {
-                    System.err.println("Failed to close XAConnection");
-                    e.printStackTrace();
-                }
-            }
-
-            @Override
-            public void connectionErrorOccurred(final ConnectionEvent event) {
-                connectionClosed(event);
-            }
-        });
+        xaConnection.addConnectionEventListener(new XAConnectionEventListener());
 
         return connection;
     }
@@ -212,7 +214,7 @@ public class DataSourceXAConnectionFactory implements XAConnectionFactory {
      * @return the user password.
      */
     public char[] getUserPassword() {
-        return userPassword == null ? null : userPassword.clone();
+        return Utils.clone(userPassword);
     }
 
     /**
@@ -232,7 +234,7 @@ public class DataSourceXAConnectionFactory implements XAConnectionFactory {
      * @since 2.4.0
      */
     public void setPassword(final char[] userPassword) {
-        this.userPassword = userPassword == null ? null : userPassword.clone();
+        this.userPassword = Utils.clone(userPassword);
     }
 
     /**
diff --git a/java/org/apache/tomcat/dbcp/dbcp2/managed/LocalXAConnectionFactory.java b/java/org/apache/tomcat/dbcp/dbcp2/managed/LocalXAConnectionFactory.java
index 494b90ad72..8906330837 100644
--- a/java/org/apache/tomcat/dbcp/dbcp2/managed/LocalXAConnectionFactory.java
+++ b/java/org/apache/tomcat/dbcp/dbcp2/managed/LocalXAConnectionFactory.java
@@ -68,6 +68,13 @@ public class LocalXAConnectionFactory implements XAConnectionFactory {
             this.connection = localTransaction;
         }
 
+        private Xid checkCurrentXid() throws XAException {
+            if (this.currentXid == null) {
+                throw new XAException("There is no current transaction");
+            }
+            return currentXid;
+        }
+
         /**
          * Commits the transaction and restores the original auto commit setting.
          *
@@ -80,11 +87,8 @@ public class LocalXAConnectionFactory implements XAConnectionFactory {
          */
         @Override
         public synchronized void commit(final Xid xid, final boolean flag) throws XAException {
-            Objects.requireNonNull(xid, "xid is null");
-            if (this.currentXid == null) {
-                throw new XAException("There is no current transaction");
-            }
-            if (!this.currentXid.equals(xid)) {
+            Objects.requireNonNull(xid, "xid");
+            if (!checkCurrentXid().equals(xid)) {
                 throw new XAException("Invalid Xid: expected " + this.currentXid + ", but was " + xid);
             }
 
@@ -103,8 +107,8 @@ public class LocalXAConnectionFactory implements XAConnectionFactory {
             } finally {
                 try {
                     connection.setAutoCommit(originalAutoCommit);
-                } catch (final SQLException e) {
-                    // ignore
+                } catch (final SQLException ignored) {
+                    // ignored
                 }
                 this.currentXid = null;
             }
@@ -122,8 +126,8 @@ public class LocalXAConnectionFactory implements XAConnectionFactory {
          */
         @Override
         public synchronized void end(final Xid xid, final int flag) throws XAException {
-            Objects.requireNonNull(xid, "xid is null");
-            if (!this.currentXid.equals(xid)) {
+            Objects.requireNonNull(xid, "xid");
+            if (!checkCurrentXid().equals(xid)) {
                 throw new XAException("Invalid Xid: expected " + this.currentXid + ", but was " + xid);
             }
 
@@ -230,8 +234,8 @@ public class LocalXAConnectionFactory implements XAConnectionFactory {
          */
         @Override
         public synchronized void rollback(final Xid xid) throws XAException {
-            Objects.requireNonNull(xid, "xid is null");
-            if (!this.currentXid.equals(xid)) {
+            Objects.requireNonNull(xid, "xid");
+            if (!checkCurrentXid().equals(xid)) {
                 throw new XAException("Invalid Xid: expected " + this.currentXid + ", but was " + xid);
             }
 
@@ -242,8 +246,8 @@ public class LocalXAConnectionFactory implements XAConnectionFactory {
             } finally {
                 try {
                     connection.setAutoCommit(originalAutoCommit);
-                } catch (final SQLException e) {
-                    // Ignore.
+                } catch (final SQLException ignored) {
+                    // Ignored.
                 }
                 this.currentXid = null;
             }
@@ -344,8 +348,8 @@ public class LocalXAConnectionFactory implements XAConnectionFactory {
     public LocalXAConnectionFactory(final TransactionManager transactionManager,
             final TransactionSynchronizationRegistry transactionSynchronizationRegistry,
             final ConnectionFactory connectionFactory) {
-        Objects.requireNonNull(transactionManager, "transactionManager is null");
-        Objects.requireNonNull(connectionFactory, "connectionFactory is null");
+        Objects.requireNonNull(transactionManager, "transactionManager");
+        Objects.requireNonNull(connectionFactory, "connectionFactory");
         this.transactionRegistry = new TransactionRegistry(transactionManager, transactionSynchronizationRegistry);
         this.connectionFactory = connectionFactory;
     }
diff --git a/java/org/apache/tomcat/dbcp/dbcp2/managed/ManagedConnection.java b/java/org/apache/tomcat/dbcp/dbcp2/managed/ManagedConnection.java
index aeba61d798..81d370e5c9 100644
--- a/java/org/apache/tomcat/dbcp/dbcp2/managed/ManagedConnection.java
+++ b/java/org/apache/tomcat/dbcp/dbcp2/managed/ManagedConnection.java
@@ -58,12 +58,12 @@ public class ManagedConnection<C extends Connection> extends DelegatingConnectio
             }
         }
     }
+
     private final ObjectPool<C> pool;
     private final TransactionRegistry transactionRegistry;
     private final boolean accessToUnderlyingConnectionAllowed;
     private TransactionContext transactionContext;
     private boolean isSharedConnection;
-
     private final Lock lock;
 
     /**
@@ -264,11 +264,11 @@ public class ManagedConnection<C extends Connection> extends DelegatingConnectio
             if (connection != null && transactionContext.getSharedConnection() != connection) {
                 try {
                     pool.returnObject(connection);
-                } catch (final Exception ignored) {
+                } catch (final Exception e) {
                     // whatever... try to invalidate the connection
                     try {
                         pool.invalidateObject(connection);
-                    } catch (final Exception ignore) {
+                    } catch (final Exception ignored) {
                         // no big deal
                     }
                 }
@@ -313,7 +313,7 @@ public class ManagedConnection<C extends Connection> extends DelegatingConnectio
                     transactionContext = null;
                     try {
                         pool.invalidateObject(connection);
-                    } catch (final Exception e1) {
+                    } catch (final Exception ignored) {
                         // we are try but no luck
                     }
                     throw e;
diff --git a/java/org/apache/tomcat/dbcp/dbcp2/managed/ManagedDataSource.java b/java/org/apache/tomcat/dbcp/dbcp2/managed/ManagedDataSource.java
index ea2ad35859..802b0362a9 100644
--- a/java/org/apache/tomcat/dbcp/dbcp2/managed/ManagedDataSource.java
+++ b/java/org/apache/tomcat/dbcp/dbcp2/managed/ManagedDataSource.java
@@ -85,7 +85,7 @@ public class ManagedDataSource<C extends Connection> extends PoolingDataSource<C
         if (this.transactionRegistry != null) {
             throw new IllegalStateException("TransactionRegistry already set");
         }
-        Objects.requireNonNull(transactionRegistry, "transactionRegistry is null");
+        Objects.requireNonNull(transactionRegistry, "transactionRegistry");
 
         this.transactionRegistry = transactionRegistry;
     }
diff --git a/java/org/apache/tomcat/dbcp/dbcp2/managed/PoolableManagedConnectionFactory.java b/java/org/apache/tomcat/dbcp/dbcp2/managed/PoolableManagedConnectionFactory.java
index b7adcc0122..28c2046cd1 100644
--- a/java/org/apache/tomcat/dbcp/dbcp2/managed/PoolableManagedConnectionFactory.java
+++ b/java/org/apache/tomcat/dbcp/dbcp2/managed/PoolableManagedConnectionFactory.java
@@ -17,6 +17,7 @@
 package org.apache.tomcat.dbcp.dbcp2.managed;
 
 import java.sql.Connection;
+import java.sql.SQLException;
 import java.time.Duration;
 
 import javax.management.ObjectName;
@@ -66,12 +67,12 @@ public class PoolableManagedConnectionFactory extends PoolableConnectionFactory
 
     /**
      * Uses the configured XAConnectionFactory to create a {@link PoolableManagedConnection}. Throws
-     * <code>IllegalStateException</code> if the connection factory returns null. Also initializes the connection using
+     * {@code IllegalStateException} if the connection factory returns null. Also initializes the connection using
      * configured initialization SQL (if provided) and sets up a prepared statement pool associated with the
      * PoolableManagedConnection if statement pooling is enabled.
      */
     @Override
-    public synchronized PooledObject<PoolableConnection> makeObject() throws Exception {
+    public synchronized PooledObject<PoolableConnection> makeObject() throws SQLException {
         Connection conn = getConnectionFactory().createConnection();
         if (conn == null) {
             throw new IllegalStateException("Connection factory returned null from createConnection");
diff --git a/java/org/apache/tomcat/dbcp/dbcp2/LifetimeExceededException.java b/java/org/apache/tomcat/dbcp/dbcp2/managed/SynchronizationAdapter.java
similarity index 57%
copy from java/org/apache/tomcat/dbcp/dbcp2/LifetimeExceededException.java
copy to java/org/apache/tomcat/dbcp/dbcp2/managed/SynchronizationAdapter.java
index dd2bbdd7e1..c7969d242e 100644
--- a/java/org/apache/tomcat/dbcp/dbcp2/LifetimeExceededException.java
+++ b/java/org/apache/tomcat/dbcp/dbcp2/managed/SynchronizationAdapter.java
@@ -14,30 +14,23 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.apache.tomcat.dbcp.dbcp2;
+package org.apache.tomcat.dbcp.dbcp2.managed;
+
+import jakarta.transaction.Synchronization;
 
 /**
- * Exception thrown when a connection's maximum lifetime has been exceeded.
- *
- * @since 2.1
+ * Implements {@link Synchronization} for subclasses.
  */
-class LifetimeExceededException extends Exception {
+class SynchronizationAdapter implements Synchronization {
 
-    private static final long serialVersionUID = -3783783104516492659L;
-
-    /**
-     * Create a LifetimeExceededException.
-     */
-    public LifetimeExceededException() {
+    @Override
+    public void afterCompletion(final int status) {
+        // Noop
     }
 
-    /**
-     * Create a LifetimeExceededException with the given message.
-     *
-     * @param message
-     *            The message with which to create the exception
-     */
-    public LifetimeExceededException(final String message) {
-        super(message);
+    @Override
+    public void beforeCompletion() {
+        // Noop
     }
+
 }
diff --git a/java/org/apache/tomcat/dbcp/dbcp2/managed/TransactionContext.java b/java/org/apache/tomcat/dbcp/dbcp2/managed/TransactionContext.java
index 975fa88a7c..1a8823483f 100644
--- a/java/org/apache/tomcat/dbcp/dbcp2/managed/TransactionContext.java
+++ b/java/org/apache/tomcat/dbcp/dbcp2/managed/TransactionContext.java
@@ -69,8 +69,8 @@ public class TransactionContext {
      */
     public TransactionContext(final TransactionRegistry transactionRegistry, final Transaction transaction,
                               final TransactionSynchronizationRegistry transactionSynchronizationRegistry) {
-        Objects.requireNonNull(transactionRegistry, "transactionRegistry is null");
-        Objects.requireNonNull(transaction, "transaction is null");
+        Objects.requireNonNull(transactionRegistry, "transactionRegistry");
+        Objects.requireNonNull(transaction, "transaction");
         this.transactionRegistry = transactionRegistry;
         this.transactionRef = new WeakReference<>(transaction);
         this.transactionComplete = false;
@@ -89,27 +89,21 @@ public class TransactionContext {
         try {
             if (!isActive()) {
                 final Transaction transaction = this.transactionRef.get();
-                listener.afterCompletion(TransactionContext.this,
-                        transaction != null && transaction.getStatus() == Status.STATUS_COMMITTED);
+                listener.afterCompletion(this, transaction != null && transaction.getStatus() == Status.STATUS_COMMITTED);
                 return;
             }
-            final Synchronization s = new Synchronization() {
+            final Synchronization s = new SynchronizationAdapter() {
                 @Override
                 public void afterCompletion(final int status) {
                     listener.afterCompletion(TransactionContext.this, status == Status.STATUS_COMMITTED);
                 }
-
-                @Override
-                public void beforeCompletion() {
-                    // empty
-                }
             };
             if (transactionSynchronizationRegistry != null) {
                 transactionSynchronizationRegistry.registerInterposedSynchronization(s);
             } else {
                 getTransaction().registerSynchronization(s);
             }
-        } catch (final RollbackException e) {
+        } catch (final RollbackException ignored) {
             // JTA spec doesn't let us register with a transaction marked rollback only
             // just ignore this and the tx state will be cleared another way.
         } catch (final Exception e) {
@@ -200,7 +194,7 @@ public class TransactionContext {
         } catch (final IllegalStateException e) {
             // This can happen if the transaction is already timed out
             throw new SQLException("Unable to enlist connection in the transaction", e);
-        } catch (final RollbackException e) {
+        } catch (final RollbackException ignored) {
             // transaction was rolled back... proceed as if there never was a transaction
         } catch (final SystemException e) {
             throw new SQLException("Unable to enlist connection the transaction", e);
diff --git a/java/org/apache/tomcat/dbcp/dbcp2/managed/TransactionRegistry.java b/java/org/apache/tomcat/dbcp/dbcp2/managed/TransactionRegistry.java
index 8a33f27d5d..1d0d163d31 100644
--- a/java/org/apache/tomcat/dbcp/dbcp2/managed/TransactionRegistry.java
+++ b/java/org/apache/tomcat/dbcp/dbcp2/managed/TransactionRegistry.java
@@ -93,12 +93,7 @@ public class TransactionRegistry {
 
         // register the context (or create a new one)
         synchronized (this) {
-            TransactionContext cache = caches.get(transaction);
-            if (cache == null) {
-                cache = new TransactionContext(this, transaction, transactionSynchronizationRegistry);
-                caches.put(transaction, cache);
-            }
-            return cache;
+            return caches.computeIfAbsent(transaction, k -> new TransactionContext(this, k, transactionSynchronizationRegistry));
         }
     }
 
@@ -122,7 +117,7 @@ public class TransactionRegistry {
      *             Thrown when the connection does not have a registered XAResource.
      */
     public synchronized XAResource getXAResource(final Connection connection) throws SQLException {
-        Objects.requireNonNull(connection, "connection is null");
+        Objects.requireNonNull(connection, "connection");
         final Connection key = getConnectionKey(connection);
         final XAResource xaResource = xaResources.get(key);
         if (xaResource == null) {
@@ -141,8 +136,8 @@ public class TransactionRegistry {
      *            The XAResource which managed the connection within a transaction.
      */
     public synchronized void registerConnection(final Connection connection, final XAResource xaResource) {
-        Objects.requireNonNull(connection, "connection is null");
-        Objects.requireNonNull(xaResource, "xaResource is null");
+        Objects.requireNonNull(connection, "connection");
+        Objects.requireNonNull(xaResource, "xaResource");
         xaResources.put(connection, xaResource);
     }
 
diff --git a/webapps/docs/changelog.xml b/webapps/docs/changelog.xml
index b80ff1a41d..3bb40c0352 100644
--- a/webapps/docs/changelog.xml
+++ b/webapps/docs/changelog.xml
@@ -175,6 +175,10 @@
         Update the internal fork of Apache Commons FileUpload to 34eb241
         (2023-01-03, 2.0-SNAPSHOT). (markt)
       </update>
+      <update>
+        Update the internal fork of Apache Commons DBCP to f131286 (2023-01-03,
+        2.10.0-SNAPSHOT). (markt)
+      </update>
     </changelog>
   </subsection>
 </section>


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