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 16:10:47 UTC

[tomcat] branch 10.1.x updated (7d2a6333d7 -> 474f8c942b)

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

markt pushed a change to branch 10.1.x
in repository https://gitbox.apache.org/repos/asf/tomcat.git


    from 7d2a6333d7 Fix BZ 63390 - Fix test on Solaris.
     new 8a2285f13a Update packaged renamed fork of Commons File Upload
     new 3c0a637594 Update package renamed fork of Commons BCEL
     new 82ccef2290 Update package renamed fork of Commons Codec
     new 474f8c942b Update package renamed fork of Commons DBCP

The 4 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.


Summary of changes:
 MERGE.txt                                          |   8 +-
 java/org/apache/catalina/connector/Request.java    |  10 +-
 .../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 +-
 .../datasources/SharedPoolDataSourceFactory.java   |   2 +-
 .../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 +-
 .../dbcp/dbcp2/managed/SynchronizationAdapter.java |  18 +-
 .../dbcp/dbcp2/managed/TransactionContext.java     |  18 +-
 .../dbcp/dbcp2/managed/TransactionRegistry.java    |  13 +-
 .../tomcat/util/bcel/classfile/ConstantPool.java   |  18 +-
 .../tomcat/util/codec/binary/BaseNCodec.java       |   5 +-
 java/org/apache/tomcat/util/http/Parameters.java   |   5 +
 .../util/http/fileupload/FileUploadBase.java       |  29 +
 ...n.java => FileCountLimitExceededException.java} |  37 +-
 webapps/docs/changelog.xml                         |  16 +
 webapps/docs/config/ajp.xml                        |  15 +-
 webapps/docs/config/http.xml                       |  15 +-
 59 files changed, 1156 insertions(+), 1593 deletions(-)
 copy test/org/apache/el/TesterBeanAA.java => java/org/apache/tomcat/dbcp/dbcp2/managed/SynchronizationAdapter.java (71%)
 copy java/org/apache/tomcat/util/http/fileupload/impl/{SizeLimitExceededException.java => FileCountLimitExceededException.java} (54%)


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


[tomcat] 03/04: Update package renamed fork of Commons Codec

Posted by ma...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

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

commit 82ccef22901452aa264870f61df9c1e73cc16ab9
Author: Mark Thomas <ma...@apache.org>
AuthorDate: Tue Jan 3 12:41:45 2023 +0000

    Update package renamed fork of Commons Codec
---
 MERGE.txt                                                | 4 ++--
 java/org/apache/tomcat/util/codec/binary/BaseNCodec.java | 5 +++--
 webapps/docs/changelog.xml                               | 6 +++++-
 3 files changed, 10 insertions(+), 5 deletions(-)

diff --git a/MERGE.txt b/MERGE.txt
index de8e38d901..5673ec8d95 100644
--- a/MERGE.txt
+++ b/MERGE.txt
@@ -37,7 +37,7 @@ Unused code is removed
 Sub-tree:
 src/main/java/org/apache/bcel
 The SHA1 ID / tag for the most recent commit to be merged to Tomcat is:
-2ee2bff580c7138545377628074173412c27290c (2023-01-02)
+2ee2bff580c7138545377628074173412c27290c (2023-01-03)
 
 Codec
 -----
@@ -45,7 +45,7 @@ Unused code is removed
 Sub-tree:
 src/main/java/org/apache/commons/codec
 The SHA1 ID / tag for the most recent commit to be merged to Tomcat is:
-ae32a3f2fa6b722b8ad67bd125a52edb78932314 (2022-11-29)
+f03cbd3ba741758ead9f59bc07e6688a739a4813 (2023-01-03)
 Note: Only classes required for Base64 encoding/decoding. The rest are removed.
 
 FileUpload
diff --git a/java/org/apache/tomcat/util/codec/binary/BaseNCodec.java b/java/org/apache/tomcat/util/codec/binary/BaseNCodec.java
index 09e647ff97..0bfcf7312a 100644
--- a/java/org/apache/tomcat/util/codec/binary/BaseNCodec.java
+++ b/java/org/apache/tomcat/util/codec/binary/BaseNCodec.java
@@ -16,6 +16,8 @@
  */
 package org.apache.tomcat.util.codec.binary;
 
+import java.util.Arrays;
+
 import org.apache.tomcat.util.buf.HexUtils;
 import org.apache.tomcat.util.res.StringManager;
 
@@ -206,8 +208,7 @@ public abstract class BaseNCodec {
             newCapacity = createPositiveCapacity(minCapacity);
         }
 
-        final byte[] b = new byte[newCapacity];
-        System.arraycopy(context.buffer, 0, b, 0, context.buffer.length);
+        final byte[] b = Arrays.copyOf(context.buffer, newCapacity);
         context.buffer = b;
         return b;
     }
diff --git a/webapps/docs/changelog.xml b/webapps/docs/changelog.xml
index 43cc1364aa..34e7d3ec84 100644
--- a/webapps/docs/changelog.xml
+++ b/webapps/docs/changelog.xml
@@ -147,9 +147,13 @@
         Jakarta EE to 1.0.6. (markt)
       </update>
       <update>
-        Update the internal fork of Apache Commons BCEL to 2ee2bff (2023-01-02,
+        Update the internal fork of Apache Commons BCEL to 2ee2bff (2023-01-03,
         6.7.1-SNAPSHOT). (markt)
       </update>
+      <update>
+        Update the internal fork of Apache Commons Codec to 3eafd6c (2023-01-03,
+        1.16-SNAPSHOT). (markt)
+      </update>
       <update>
         Update the internal fork of Apache Commons FileUpload to 34eb241
         (2023-01-03, 2.0-SNAPSHOT). (markt)


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


[tomcat] 01/04: Update packaged renamed fork of Commons File Upload

Posted by ma...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

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

commit 8a2285f13affa961cc65595aad999db5efae45ce
Author: Mark Thomas <ma...@apache.org>
AuthorDate: Tue Dec 13 17:55:34 2022 +0000

    Update packaged renamed fork of Commons File Upload
---
 MERGE.txt                                          |  2 +-
 java/org/apache/catalina/connector/Request.java    | 10 ++++-
 java/org/apache/tomcat/util/http/Parameters.java   |  5 +++
 .../util/http/fileupload/FileUploadBase.java       | 29 +++++++++++++
 .../impl/FileCountLimitExceededException.java      | 50 ++++++++++++++++++++++
 webapps/docs/changelog.xml                         |  4 ++
 webapps/docs/config/ajp.xml                        | 15 ++++---
 webapps/docs/config/http.xml                       | 15 ++++---
 8 files changed, 116 insertions(+), 14 deletions(-)

diff --git a/MERGE.txt b/MERGE.txt
index 8c1ed33662..41646180c2 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:
-aa8eff6f04c939fd99834360415b1ddb2f637cb1 (2022-11-29)
+34eb241c051b02eca3b0b1b04f67b3b4e6c3a24d (2023-02-03)
 
 Note: Tomcat's copy of fileupload also includes classes copied manually from
       Commons IO.
diff --git a/java/org/apache/catalina/connector/Request.java b/java/org/apache/catalina/connector/Request.java
index ecfc7aaa16..340d775b15 100644
--- a/java/org/apache/catalina/connector/Request.java
+++ b/java/org/apache/catalina/connector/Request.java
@@ -2816,8 +2816,9 @@ public class Request implements HttpServletRequest {
             }
         }
 
+        int maxParameterCount = getConnector().getMaxParameterCount();
         Parameters parameters = coyoteRequest.getParameters();
-        parameters.setLimit(getConnector().getMaxParameterCount());
+        parameters.setLimit(maxParameterCount);
 
         boolean success = false;
         try {
@@ -2869,6 +2870,13 @@ public class Request implements HttpServletRequest {
             upload.setFileItemFactory(factory);
             upload.setFileSizeMax(mce.getMaxFileSize());
             upload.setSizeMax(mce.getMaxRequestSize());
+            if (maxParameterCount > -1) {
+                // There is a limit. The limit for parts needs to be reduced by
+                // the number of parameters we have already parsed.
+                // Must be under the limit else parsing parameters would have
+                // triggered an exception.
+                upload.setFileCountMax(maxParameterCount - parameters.size());
+            }
 
             parts = new ArrayList<>();
             try {
diff --git a/java/org/apache/tomcat/util/http/Parameters.java b/java/org/apache/tomcat/util/http/Parameters.java
index ce765374e7..d233190ddb 100644
--- a/java/org/apache/tomcat/util/http/Parameters.java
+++ b/java/org/apache/tomcat/util/http/Parameters.java
@@ -125,6 +125,11 @@ public final class Parameters {
     }
 
 
+    public int size() {
+        return parameterCount;
+    }
+
+
     public void recycle() {
         parameterCount = 0;
         paramHashValues.clear();
diff --git a/java/org/apache/tomcat/util/http/fileupload/FileUploadBase.java b/java/org/apache/tomcat/util/http/fileupload/FileUploadBase.java
index acc4aa307f..d527313723 100644
--- a/java/org/apache/tomcat/util/http/fileupload/FileUploadBase.java
+++ b/java/org/apache/tomcat/util/http/fileupload/FileUploadBase.java
@@ -24,6 +24,7 @@ import java.util.Locale;
 import java.util.Map;
 import java.util.Objects;
 
+import org.apache.tomcat.util.http.fileupload.impl.FileCountLimitExceededException;
 import org.apache.tomcat.util.http.fileupload.impl.FileItemIteratorImpl;
 import org.apache.tomcat.util.http.fileupload.impl.FileUploadIOException;
 import org.apache.tomcat.util.http.fileupload.impl.IOFileUploadException;
@@ -103,6 +104,12 @@ public abstract class FileUploadBase {
      */
     private long fileSizeMax = -1;
 
+    /**
+     * The maximum permitted number of files that may be uploaded in a single
+     * request. A value of -1 indicates no maximum.
+     */
+    private long fileCountMax = -1;
+
     /**
      * The content encoding to use when reading part headers.
      */
@@ -179,6 +186,24 @@ public abstract class FileUploadBase {
         this.fileSizeMax = fileSizeMax;
     }
 
+    /**
+     * Returns the maximum number of files allowed in a single request.
+     *
+     * @return The maximum number of files allowed in a single request.
+     */
+    public long getFileCountMax() {
+        return fileCountMax;
+    }
+
+    /**
+     * Sets the maximum number of files allowed per request/
+     *
+     * @param fileCountMax The new limit. {@code -1} means no limit.
+     */
+    public void setFileCountMax(long fileCountMax) {
+        this.fileCountMax = fileCountMax;
+    }
+
     /**
      * Retrieves the character encoding used when reading the headers of an
      * individual part. When not specified, or {@code null}, the request
@@ -253,6 +278,10 @@ public abstract class FileUploadBase {
                     "No FileItemFactory has been set.");
             final byte[] buffer = new byte[Streams.DEFAULT_BUFFER_SIZE];
             while (iter.hasNext()) {
+                if (items.size() == fileCountMax) {
+                    // The next item will exceed the limit.
+                    throw new FileCountLimitExceededException(ATTACHMENT, getFileCountMax());
+                }
                 final FileItemStream item = iter.next();
                 // Don't use getName() here to prevent an InvalidFileNameException.
                 final String fileName = item.getName();
diff --git a/java/org/apache/tomcat/util/http/fileupload/impl/FileCountLimitExceededException.java b/java/org/apache/tomcat/util/http/fileupload/impl/FileCountLimitExceededException.java
new file mode 100644
index 0000000000..958f681276
--- /dev/null
+++ b/java/org/apache/tomcat/util/http/fileupload/impl/FileCountLimitExceededException.java
@@ -0,0 +1,50 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.tomcat.util.http.fileupload.impl;
+
+import org.apache.tomcat.util.http.fileupload.FileUploadException;
+
+/**
+ * This exception is thrown if a request contains more files than the specified
+ * limit.
+ */
+public class FileCountLimitExceededException extends FileUploadException {
+
+    private static final long serialVersionUID = 2408766352570556046L;
+
+    private final long limit;
+
+    /**
+     * Creates a new instance.
+     *
+     * @param message The detail message
+     * @param limit The limit that was exceeded
+     */
+    public FileCountLimitExceededException(final String message, final long limit) {
+        super(message);
+        this.limit = limit;
+    }
+
+    /**
+     * Retrieves the limit that was exceeded.
+     *
+     * @return The limit that was exceeded by the request
+     */
+    public long getLimit() {
+        return limit;
+    }
+}
diff --git a/webapps/docs/changelog.xml b/webapps/docs/changelog.xml
index 3cb080349f..9369ae0221 100644
--- a/webapps/docs/changelog.xml
+++ b/webapps/docs/changelog.xml
@@ -146,6 +146,10 @@
         Update the packaged version of the Apache Tomcat Migration Tool for
         Jakarta EE to 1.0.6. (markt)
       </update>
+      <update>
+        Update the internal fork of Apache Commons FileUpload to 34eb241
+        (2023-01-03, 2.0-SNAPSHOT). (markt)
+      </update>
     </changelog>
   </subsection>
 </section>
diff --git a/webapps/docs/config/ajp.xml b/webapps/docs/config/ajp.xml
index 0a3a260bf3..f4de8b0171 100644
--- a/webapps/docs/config/ajp.xml
+++ b/webapps/docs/config/ajp.xml
@@ -149,12 +149,15 @@
     </attribute>
 
     <attribute name="maxParameterCount" required="false">
-      <p>The maximum number of parameter and value pairs (GET plus POST) which
-      will be automatically parsed by the container. Parameter and value pairs
-      beyond this limit will be ignored. A value of less than 0 means no limit.
-      If not specified, a default of 10000 is used. Note that
-      <code>FailedRequestFilter</code> <a href="filter.html">filter</a> can be
-      used to reject requests that hit the limit.</p>
+      <p>The maximum total number of request parameters (including uploaded
+      files) obtained from the query string and, for POST requests, the request
+      body if the content type is
+      <code>application/x-www-form-urlencoded</code> or
+      <code>multipart/form-data</code>. Request parameters beyond this limit
+      will be ignored. A value of less than 0 means no limit. If not specified,
+      a default of 10000 is used. Note that <code>FailedRequestFilter</code>
+      <a href="filter.html">filter</a> can be used to reject requests that
+      exceed the limit.</p>
     </attribute>
 
     <attribute name="maxPostSize" required="false">
diff --git a/webapps/docs/config/http.xml b/webapps/docs/config/http.xml
index 5b9fc6c941..98e22bf0e8 100644
--- a/webapps/docs/config/http.xml
+++ b/webapps/docs/config/http.xml
@@ -145,12 +145,15 @@
     </attribute>
 
     <attribute name="maxParameterCount" required="false">
-      <p>The maximum number of parameter and value pairs (GET plus POST) which
-      will be automatically parsed by the container. Parameter and value pairs
-      beyond this limit will be ignored. A value of less than 0 means no limit.
-      If not specified, a default of 10000 is used. Note that
-      <code>FailedRequestFilter</code> <a href="filter.html">filter</a> can be
-      used to reject requests that hit the limit.</p>
+      <p>The maximum total number of request parameters (including uploaded
+      files) obtained from the query string and, for POST requests, the request
+      body if the content type is
+      <code>application/x-www-form-urlencoded</code> or
+      <code>multipart/form-data</code>. Request parameters beyond this limit
+      will be ignored. A value of less than 0 means no limit. If not specified,
+      a default of 10000 is used. Note that <code>FailedRequestFilter</code>
+      <a href="filter.html">filter</a> can be used to reject requests that
+      exceed the limit.</p>
     </attribute>
 
     <attribute name="maxPostSize" required="false">


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


[tomcat] 04/04: Update package renamed fork of Commons DBCP

Posted by ma...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

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

commit 474f8c942b596052b6257bd2a4384aedf22238e7
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 +-
 .../datasources/SharedPoolDataSourceFactory.java   |   2 +-
 .../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 +
 51 files changed, 1045 insertions(+), 1567 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/SharedPoolDataSourceFactory.java b/java/org/apache/tomcat/dbcp/dbcp2/datasources/SharedPoolDataSourceFactory.java
index 0b5819ec3b..b3feafe0e7 100644
--- a/java/org/apache/tomcat/dbcp/dbcp2/datasources/SharedPoolDataSourceFactory.java
+++ b/java/org/apache/tomcat/dbcp/dbcp2/datasources/SharedPoolDataSourceFactory.java
@@ -20,7 +20,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/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 34e7d3ec84..d7d2d4ff22 100644
--- a/webapps/docs/changelog.xml
+++ b/webapps/docs/changelog.xml
@@ -158,6 +158,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


[tomcat] 02/04: Update package renamed fork of Commons BCEL

Posted by ma...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

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

commit 3c0a637594f8ba5f99c1078c27319268ab4c9860
Author: Mark Thomas <ma...@apache.org>
AuthorDate: Tue Jan 3 12:28:02 2023 +0000

    Update package renamed fork of Commons BCEL
---
 MERGE.txt                                              |  2 +-
 .../tomcat/util/bcel/classfile/ConstantPool.java       | 18 +++++++++++-------
 webapps/docs/changelog.xml                             |  4 ++++
 3 files changed, 16 insertions(+), 8 deletions(-)

diff --git a/MERGE.txt b/MERGE.txt
index 41646180c2..de8e38d901 100644
--- a/MERGE.txt
+++ b/MERGE.txt
@@ -37,7 +37,7 @@ Unused code is removed
 Sub-tree:
 src/main/java/org/apache/bcel
 The SHA1 ID / tag for the most recent commit to be merged to Tomcat is:
-b015e90257850e810e57d1244664300f50de4a4c (2022-11-28)
+2ee2bff580c7138545377628074173412c27290c (2023-01-02)
 
 Codec
 -----
diff --git a/java/org/apache/tomcat/util/bcel/classfile/ConstantPool.java b/java/org/apache/tomcat/util/bcel/classfile/ConstantPool.java
index 468314f097..a9639e0be4 100644
--- a/java/org/apache/tomcat/util/bcel/classfile/ConstantPool.java
+++ b/java/org/apache/tomcat/util/bcel/classfile/ConstantPool.java
@@ -44,6 +44,7 @@ public class ConstantPool {
         constantPool = new Constant[constantPoolCount];
         /*
          * constantPool[0] is unused by the compiler and may be used freely by the implementation.
+         * constantPool[0] is currently unused by the implementation.
          */
         for (int i = 1; i < constantPoolCount; i++) {
             constantPool[i] = Constant.readConstant(input);
@@ -105,22 +106,25 @@ public class ConstantPool {
      * @throws ClassFormatException if index is invalid
      */
     public <T extends Constant> T getConstant(final int index, final Class<T> castTo) throws ClassFormatException {
-        if (index >= constantPool.length || index < 0) {
+        if (index >= constantPool.length || index < 1) {
             throw new ClassFormatException("Invalid constant pool reference using index: " + index + ". Constant pool size is: " + constantPool.length);
         }
         if (constantPool[index] != null && !castTo.isAssignableFrom(constantPool[index].getClass())) {
             throw new ClassFormatException("Invalid constant pool reference at index: " + index +
                     ". Expected " + castTo + " but was " + constantPool[index].getClass());
         }
-        // Previous check ensures this won't throw a ClassCastException
-        final T c = castTo.cast(constantPool[index]);
-        // the 0th element is always null
-        if (c == null && index != 0) {
+        if (index > 1) {
             final Constant prev = constantPool[index - 1];
-            if (prev == null || prev.getTag() != Const.CONSTANT_Double && prev.getTag() != Const.CONSTANT_Long) {
-                throw new ClassFormatException("Constant pool at index " + index + " is null.");
+            if (prev != null && (prev.getTag() == Const.CONSTANT_Double || prev.getTag() == Const.CONSTANT_Long)) {
+                throw new ClassFormatException("Constant pool at index " + index + " is invalid. The index is unused due to the preceeding "
+                        + Const.getConstantName(prev.getTag()) + ".");
             }
         }
+        // Previous check ensures this won't throw a ClassCastException
+        final T c = castTo.cast(constantPool[index]);
+        if (c == null) {
+            throw new ClassFormatException("Constant pool at index " + index + " is null.");
+        }
         return c;
     }
 
diff --git a/webapps/docs/changelog.xml b/webapps/docs/changelog.xml
index 9369ae0221..43cc1364aa 100644
--- a/webapps/docs/changelog.xml
+++ b/webapps/docs/changelog.xml
@@ -146,6 +146,10 @@
         Update the packaged version of the Apache Tomcat Migration Tool for
         Jakarta EE to 1.0.6. (markt)
       </update>
+      <update>
+        Update the internal fork of Apache Commons BCEL to 2ee2bff (2023-01-02,
+        6.7.1-SNAPSHOT). (markt)
+      </update>
       <update>
         Update the internal fork of Apache Commons FileUpload to 34eb241
         (2023-01-03, 2.0-SNAPSHOT). (markt)


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