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 2024/12/03 12:12:46 UTC

(tomcat) 01/03: Update fork of Commons DBCP to 2.13.0

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

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

commit 30c42b2f0936ecd7b7dd2b44d82c2710b07764cb
Author: Mark Thomas <ma...@apache.org>
AuthorDate: Tue Dec 3 11:53:06 2024 +0000

    Update fork of Commons DBCP to 2.13.0
---
 MERGE.txt                                          |    4 +-
 .../apache/tomcat/dbcp/dbcp2/AbandonedTrace.java   |    2 +-
 .../apache/tomcat/dbcp/dbcp2/BasicDataSource.java  |  182 ++-
 .../tomcat/dbcp/dbcp2/BasicDataSourceFactory.java  |   36 +-
 java/org/apache/tomcat/dbcp/dbcp2/Constants.java   |   20 +-
 .../apache/tomcat/dbcp/dbcp2/DataSourceMXBean.java |   10 +
 .../tomcat/dbcp/dbcp2/DelegatingConnection.java    |   44 +-
 .../dbcp/dbcp2/DelegatingDatabaseMetaData.java     | 1382 ++++----------------
 .../dbcp/dbcp2/DelegatingPreparedStatement.java    |    5 +-
 .../tomcat/dbcp/dbcp2/DelegatingStatement.java     |    5 +-
 .../tomcat/dbcp/dbcp2/DriverConnectionFactory.java |   10 +-
 .../dbcp/dbcp2/PoolableCallableStatement.java      |    2 +-
 .../tomcat/dbcp/dbcp2/PoolableConnection.java      |   53 +-
 .../dbcp/dbcp2/PoolableConnectionFactory.java      |  149 ++-
 .../dbcp/dbcp2/PoolableConnectionMXBean.java       |  124 ++
 .../dbcp/dbcp2/PoolablePreparedStatement.java      |    2 +-
 .../tomcat/dbcp/dbcp2/PoolingConnection.java       |    6 +-
 .../tomcat/dbcp/dbcp2/PoolingDataSource.java       |   13 +-
 .../apache/tomcat/dbcp/dbcp2/PoolingDriver.java    |   15 +-
 java/org/apache/tomcat/dbcp/dbcp2/Utils.java       |   47 +-
 .../dbcp/dbcp2/cpdsadapter/DriverAdapterCPDS.java  |    2 +-
 .../dbcp/dbcp2/cpdsadapter/PStmtKeyCPDS.java       |    4 +-
 .../dbcp2/cpdsadapter/PooledConnectionImpl.java    |    8 +-
 .../dbcp2/datasources/InstanceKeyDataSource.java   |   26 +-
 .../datasources/InstanceKeyDataSourceFactory.java  |   90 +-
 .../datasources/KeyedCPDSConnectionFactory.java    |    3 +-
 .../dbcp2/datasources/PerUserPoolDataSource.java   |  304 ++---
 .../tomcat/dbcp/dbcp2/datasources/PoolKey.java     |    2 +
 .../dbcp2/datasources/PooledConnectionAndInfo.java |    9 -
 .../dbcp2/datasources/SharedPoolDataSource.java    |   34 +-
 .../datasources/SharedPoolDataSourceFactory.java   |    8 +
 .../tomcat/dbcp/dbcp2/datasources/UserPassKey.java |    4 -
 .../dbcp/dbcp2/managed/BasicManagedDataSource.java |   15 +-
 .../dbcp2/managed/LocalXAConnectionFactory.java    |   17 +-
 .../dbcp/dbcp2/managed/ManagedConnection.java      |   14 +-
 .../dbcp2/managed/PoolableManagedConnection.java   |   41 +-
 .../managed/PoolableManagedConnectionFactory.java  |    4 +-
 .../dbcp/dbcp2/managed/TransactionContext.java     |    1 -
 .../dbcp2/managed/TransactionContextListener.java  |    7 +-
 .../dbcp/dbcp2/managed/XAConnectionFactory.java    |    8 +-
 .../tomcat/dbcp/dbcp2/managed/package-info.java    |    2 +-
 .../org/apache/tomcat/dbcp/dbcp2/package-info.java |    2 +-
 webapps/docs/changelog.xml                         |    3 +
 43 files changed, 1174 insertions(+), 1545 deletions(-)

diff --git a/MERGE.txt b/MERGE.txt
index 352231a2ae..8fc200f810 100644
--- a/MERGE.txt
+++ b/MERGE.txt
@@ -44,7 +44,7 @@ FileUpload
 Unused code is removed
 Branch: 1.x
 Sub-tree:
-src/main/java/org/apache/commons/fileupload2
+src/main/java/org/apache/commons/fileupload
 The SHA1 ID / tag for the most recent commit to be merged to Tomcat is:
 7a8c3241cfa8d036452cd4fc3f92d57cff189bca (2023-09-16)
 
@@ -66,4 +66,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.12.0 (2024-03-04)
+rel/commons-dbcp-2.13.0 (2024-12-02)
diff --git a/java/org/apache/tomcat/dbcp/dbcp2/AbandonedTrace.java b/java/org/apache/tomcat/dbcp/dbcp2/AbandonedTrace.java
index bff2021fd7..d4130ae581 100644
--- a/java/org/apache/tomcat/dbcp/dbcp2/AbandonedTrace.java
+++ b/java/org/apache/tomcat/dbcp/dbcp2/AbandonedTrace.java
@@ -158,7 +158,7 @@ public class AbandonedTrace implements TrackedUse, AutoCloseable {
      *            AbandonedTrace parent object.
      */
     private void init(final AbandonedTrace parent) {
-        AbandonedTrace.add(parent, this);
+        add(parent, this);
     }
 
     /**
diff --git a/java/org/apache/tomcat/dbcp/dbcp2/BasicDataSource.java b/java/org/apache/tomcat/dbcp/dbcp2/BasicDataSource.java
index 790141e622..043369b22b 100644
--- a/java/org/apache/tomcat/dbcp/dbcp2/BasicDataSource.java
+++ b/java/org/apache/tomcat/dbcp/dbcp2/BasicDataSource.java
@@ -27,12 +27,14 @@ import java.sql.SQLFeatureNotSupportedException;
 import java.time.Duration;
 import java.util.Collection;
 import java.util.Collections;
+import java.util.LinkedHashSet;
 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.Collector;
 import java.util.stream.Collectors;
 import java.util.stream.Stream;
 
@@ -55,7 +57,6 @@ import org.apache.tomcat.dbcp.pool2.impl.GenericObjectPoolConfig;
 
 /**
  * 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
  * one-stop solution for basic requirements.
@@ -318,6 +319,13 @@ public class BasicDataSource implements DataSource, BasicDataSourceMXBean, MBean
 
     private volatile Set<String> disconnectionSqlCodes;
 
+    /**
+     * A collection of SQL State codes that are not considered fatal disconnection codes.
+     *
+     * @since 2.13.0
+     */
+    private volatile Set<String> disconnectionIgnoreSqlCodes;
+
     private boolean fastFailValidation;
 
     /**
@@ -601,6 +609,7 @@ public class BasicDataSource implements DataSource, BasicDataSourceMXBean, MBean
             connectionFactory.setDefaultQueryTimeout(getDefaultQueryTimeoutDuration());
             connectionFactory.setFastFailValidation(fastFailValidation);
             connectionFactory.setDisconnectionSqlCodes(disconnectionSqlCodes);
+            connectionFactory.setDisconnectionIgnoreSqlCodes(disconnectionIgnoreSqlCodes);
             validateConnectionFactory(connectionFactory);
         } catch (final RuntimeException e) {
             throw e;
@@ -813,7 +822,33 @@ public class BasicDataSource implements DataSource, BasicDataSourceMXBean, MBean
     }
 
     /**
-     * Gets the set of SQL_STATE codes considered to signal fatal conditions.
+     * Gets the set of SQL State codes that are not considered fatal disconnection codes.
+     * <p>
+     * This method returns the set of SQL State codes that have been specified to be ignored
+     * when determining if a {@link SQLException} signals a disconnection. These codes will not
+     * trigger a disconnection even if they match other disconnection criteria.
+     * </p>
+     *
+     * @return a set of SQL State codes that should be ignored for disconnection checks, or an empty set if none have been specified.
+     * @since 2.13.0
+     */
+    public Set<String> getDisconnectionIgnoreSqlCodes() {
+        final Set<String> result = disconnectionIgnoreSqlCodes;
+        return result == null ? Collections.emptySet() : result;
+    }
+
+    /**
+     * Provides the same data as {@link #getDisconnectionIgnoreSqlCodes()} but in an array, so it is accessible via JMX.
+     *
+     * @since 2.13.0
+     */
+    @Override
+    public String[] getDisconnectionIgnoreSqlCodesAsArray() {
+        return getDisconnectionIgnoreSqlCodes().toArray(Utils.EMPTY_STRING_ARRAY);
+    }
+
+    /**
+     * Gets the set of SQL State codes considered to signal fatal conditions.
      *
      * @return fatal disconnection state codes
      * @see #setDisconnectionSqlCodes(Collection)
@@ -910,10 +945,11 @@ public class BasicDataSource implements DataSource, BasicDataSourceMXBean, MBean
 
     /**
      * True means that validation will fail immediately for connections that have previously thrown SQLExceptions with
-     * SQL_STATE indicating fatal disconnection errors.
+     * SQL State indicating fatal disconnection errors.
      *
      * @return true if connections created by this datasource will fast fail validation.
      * @see #setDisconnectionSqlCodes(Collection)
+     * @see #setDisconnectionIgnoreSqlCodes(Collection)
      * @since 2.1
      */
     @Override
@@ -1405,7 +1441,7 @@ public class BasicDataSource implements DataSource, BasicDataSourceMXBean, MBean
      * Gets the JDBC connection {code userName} property.
      *
      * @return the {code userName} passed to the JDBC driver to establish connections
-     * @deprecated Replaced with DataSourceMXBean.getUserName()
+     * @deprecated Use {@link #getUserName()}.
      */
     @Deprecated
     @Override
@@ -1567,10 +1603,10 @@ public class BasicDataSource implements DataSource, BasicDataSourceMXBean, MBean
     }
 
     /**
-     * Logs the given throwable.
-     * @param message TODO
-     * @param throwable the throwable.
+     * Logs the given message and throwable.
      *
+     * @param message value to be log.
+     * @param throwable the throwable.
      * @since 2.7.0
      */
     protected void log(final String message, final Throwable throwable) {
@@ -1688,8 +1724,8 @@ public class BasicDataSource implements DataSource, BasicDataSourceMXBean, MBean
      * the underlying connection. (Default: false)
      * <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>
+     * time one of the following methods is invoked: {@link #getConnection()}, {@link #setLogWriter(PrintWriter)},
+     * {@link #setLoginTimeout(int)}, {@link #getLoginTimeout()}, {@link #getLogWriter()}.
      * </p>
      *
      * @param allow Access to the underlying connection is granted when true.
@@ -1745,8 +1781,8 @@ public class BasicDataSource implements DataSource, BasicDataSourceMXBean, MBean
      * Sets the collection of SQL statements to be executed when a physical connection is first created.
      * <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>
+     * time one of the following methods is invoked: {@link #getConnection()}, {@link #setLogWriter(PrintWriter)},
+     * {@link #setLoginTimeout(int)}, {@link #getLoginTimeout()}, {@link #getLogWriter()}.
      * </p>
      *
      * @param connectionInitSqls Collection of SQL statements to execute on connection creation
@@ -1761,8 +1797,8 @@ public class BasicDataSource implements DataSource, BasicDataSourceMXBean, MBean
      * Sets the list of SQL statements to be executed when a physical connection is first created.
      * <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>
+     * time one of the following methods is invoked: {@link #getConnection()}, {@link #setLogWriter(PrintWriter)},
+     * {@link #setLoginTimeout(int)}, {@link #getLoginTimeout()}, {@link #getLogWriter()}.
      * </p>
      *
      * @param connectionInitSqls List of SQL statements to execute on connection creation
@@ -1812,8 +1848,8 @@ public class BasicDataSource implements DataSource, BasicDataSourceMXBean, MBean
      * Sets default auto-commit state of connections returned by this datasource.
      * <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>
+     * time one of the following methods is invoked: {@link #getConnection()}, {@link #setLogWriter(PrintWriter)},
+     * {@link #setLoginTimeout(int)}, {@link #getLoginTimeout()}, {@link #getLogWriter()}.
      * </p>
      *
      * @param defaultAutoCommit default auto-commit value
@@ -1826,8 +1862,8 @@ public class BasicDataSource implements DataSource, BasicDataSourceMXBean, MBean
      * Sets the default catalog.
      * <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>
+     * time one of the following methods is invoked: {@link #getConnection()}, {@link #setLogWriter(PrintWriter)},
+     * {@link #setLoginTimeout(int)}, {@link #getLoginTimeout()}, {@link #getLogWriter()}.
      * </p>
      *
      * @param defaultCatalog the default catalog
@@ -1863,8 +1899,8 @@ public class BasicDataSource implements DataSource, BasicDataSourceMXBean, MBean
      * Sets defaultReadonly 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,
-     * setLoginTimeout, getLoginTimeout, getLogWriter.</code>
+     * time one of the following methods is invoked: {@link #getConnection()}, {@link #setLogWriter(PrintWriter)},
+     * {@link #setLoginTimeout(int)}, {@link #getLoginTimeout()}, {@link #getLogWriter()}.
      * </p>
      *
      * @param defaultReadOnly default read-only value
@@ -1877,8 +1913,8 @@ public class BasicDataSource implements DataSource, BasicDataSourceMXBean, MBean
      * Sets the default schema.
      * <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>
+     * time one of the following methods is invoked: {@link #getConnection()}, {@link #setLogWriter(PrintWriter)},
+     * {@link #setLoginTimeout(int)}, {@link #getLoginTimeout()}, {@link #getLogWriter()}.
      * </p>
      *
      * @param defaultSchema the default catalog
@@ -1892,8 +1928,8 @@ public class BasicDataSource implements DataSource, BasicDataSourceMXBean, MBean
      * Sets the default transaction isolation state for returned connections.
      * <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>
+     * time one of the following methods is invoked: {@link #getConnection()}, {@link #setLogWriter(PrintWriter)},
+     * {@link #setLoginTimeout(int)}, {@link #getLoginTimeout()}, {@link #getLogWriter()}.
      * </p>
      *
      * @param defaultTransactionIsolation the default transaction isolation state
@@ -1904,11 +1940,41 @@ public class BasicDataSource implements DataSource, BasicDataSourceMXBean, MBean
     }
 
     /**
-     * Sets the SQL_STATE codes considered to signal fatal conditions.
+     * Sets the SQL State codes that should be ignored when determining fatal disconnection conditions.
+     * <p>
+     * This method allows you to specify a collection of SQL State codes that will be excluded from
+     * disconnection checks. These codes will not trigger the "fatally disconnected" status even if they
+     * match the typical disconnection criteria. This can be useful in scenarios where certain SQL State
+     * codes (e.g., specific codes starting with "08") are known to be non-fatal in your environment.
+     * </p>
+     * <p>
+     * The effect of this method is similar to the one described in {@link #setDisconnectionSqlCodes(Collection)},
+     * but instead of setting codes that signal fatal disconnections, it defines codes that should be ignored
+     * during such checks.
+     * </p>
+     * <p>
+     * Note: This method currently has no effect once the pool has been initialized. The pool is initialized the first
+     * time one of the following methods is invoked: {@code getConnection, setLogwriter, setLoginTimeout,
+     * getLoginTimeout, getLogWriter}.
+     * </p>
+     *
+     * @param disconnectionIgnoreSqlCodes SQL State codes that should be ignored in disconnection checks
+     * @throws IllegalArgumentException if any SQL state codes overlap with those in {@link #disconnectionSqlCodes}.
+     * @since 2.13.0
+     */
+    public void setDisconnectionIgnoreSqlCodes(final Collection<String> disconnectionIgnoreSqlCodes) {
+        Utils.checkSqlCodes(disconnectionIgnoreSqlCodes, this.disconnectionSqlCodes);
+        final Set<String> collect = Utils.isEmpty(disconnectionIgnoreSqlCodes) ? null
+                : disconnectionIgnoreSqlCodes.stream().filter(s -> !isEmpty(s)).collect(toLinkedHashSet());
+        this.disconnectionIgnoreSqlCodes = Utils.isEmpty(collect) ? null : collect;
+    }
+
+    /**
+     * Sets the SQL State codes considered to signal fatal conditions.
      * <p>
      * 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
+     * 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
      * isValid or validation query).
      * </p>
@@ -1921,12 +1987,14 @@ public class BasicDataSource implements DataSource, BasicDataSourceMXBean, MBean
      * setLoginTimeout, getLoginTimeout, getLogWriter}.
      * </p>
      *
-     * @param disconnectionSqlCodes SQL_STATE codes considered to signal fatal conditions
+     * @param disconnectionSqlCodes SQL State codes considered to signal fatal conditions
      * @since 2.1
+     * @throws IllegalArgumentException if any SQL state codes overlap with those in {@link #disconnectionIgnoreSqlCodes}.
      */
     public void setDisconnectionSqlCodes(final Collection<String> disconnectionSqlCodes) {
+        Utils.checkSqlCodes(disconnectionSqlCodes, this.disconnectionIgnoreSqlCodes);
         final Set<String> collect = Utils.isEmpty(disconnectionSqlCodes) ? null
-                : disconnectionSqlCodes.stream().filter(s -> !isEmpty(s)).collect(Collectors.toSet());
+                : disconnectionSqlCodes.stream().filter(s -> !isEmpty(s)).collect(toLinkedHashSet());
         this.disconnectionSqlCodes = Utils.isEmpty(collect) ? null : collect;
     }
 
@@ -1934,8 +2002,8 @@ public class BasicDataSource implements DataSource, BasicDataSourceMXBean, MBean
      * Sets the JDBC Driver instance to use for this pool.
      * <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>
+     * time one of the following methods is invoked: {@link #getConnection()}, {@link #setLogWriter(PrintWriter)},
+     * {@link #setLoginTimeout(int)}, {@link #getLoginTimeout()}, {@link #getLogWriter()}.
      * </p>
      *
      * @param driver The JDBC Driver instance to use for this pool.
@@ -1948,8 +2016,8 @@ public class BasicDataSource implements DataSource, BasicDataSourceMXBean, MBean
      * Sets the class loader to be used to load the JDBC driver.
      * <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>
+     * time one of the following methods is invoked: {@link #getConnection()}, {@link #setLogWriter(PrintWriter)},
+     * {@link #setLoginTimeout(int)}, {@link #getLoginTimeout()}, {@link #getLogWriter()}.
      * </p>
      *
      * @param driverClassLoader the class loader with which to load the JDBC driver
@@ -1962,8 +2030,8 @@ public class BasicDataSource implements DataSource, BasicDataSourceMXBean, MBean
      * Sets the JDBC driver class name.
      * <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>
+     * time one of the following methods is invoked: {@link #getConnection()}, {@link #setLogWriter(PrintWriter)},
+     * {@link #setLoginTimeout(int)}, {@link #getLoginTimeout()}, {@link #getLogWriter()}.
      * </p>
      *
      * @param driverClassName the class name of the JDBC driver
@@ -2021,8 +2089,8 @@ public class BasicDataSource implements DataSource, BasicDataSourceMXBean, MBean
      * Sets the initial size of the connection pool.
      * <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>
+     * time one of the following methods is invoked: {@link #getConnection()}, {@link #setLogWriter(PrintWriter)},
+     * {@link #setLoginTimeout(int)}, {@link #getLoginTimeout()}, {@link #getLogWriter()}.
      * </p>
      *
      * @param initialSize the number of connections created when the pool is initialized
@@ -2114,8 +2182,8 @@ public class BasicDataSource implements DataSource, BasicDataSourceMXBean, MBean
      * infinite lifetime.
      * <p>
      * Note: this method currently has no effect once the pool has been initialized. The pool is initialized the first
-     * time one of the following methods is invoked: <code>getConnection, setLogwriter,
-     * setLoginTimeout, getLoginTimeout, getLogWriter.</code>
+     * time one of the following methods is invoked: {@link #getConnection()}, {@link #setLogWriter(PrintWriter)},
+     * {@link #setLoginTimeout(int)}, {@link #getLoginTimeout()}, {@link #getLogWriter()}.
      * </p>
      *
      * @param maxConnDuration The maximum permitted lifetime of a connection.
@@ -2130,8 +2198,8 @@ public class BasicDataSource implements DataSource, BasicDataSourceMXBean, MBean
      * infinite lifetime.
      * <p>
      * Note: this method currently has no effect once the pool has been initialized. The pool is initialized the first
-     * time one of the following methods is invoked: <code>getConnection, setLogwriter,
-     * setLoginTimeout, getLoginTimeout, getLogWriter.</code>
+     * time one of the following methods is invoked: {@link #getConnection()}, {@link #setLogWriter(PrintWriter)},
+     * {@link #setLoginTimeout(int)}, {@link #getLoginTimeout()}, {@link #getLogWriter()}.
      * </p>
      *
      * @param maxConnLifetimeMillis The maximum permitted lifetime of a connection in milliseconds.
@@ -2158,8 +2226,8 @@ public class BasicDataSource implements DataSource, BasicDataSourceMXBean, MBean
      * 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,
-     * setLoginTimeout, getLoginTimeout, getLogWriter.</code>
+     * time one of the following methods is invoked: {@link #getConnection()}, {@link #setLogWriter(PrintWriter)},
+     * {@link #setLoginTimeout(int)}, {@link #getLoginTimeout()}, {@link #getLogWriter()}.
      * </p>
      *
      * @param maxOpenStatements the new maximum number of prepared statements
@@ -2256,8 +2324,8 @@ public class BasicDataSource implements DataSource, BasicDataSourceMXBean, MBean
      * Sets the {code password}.
      * <p>
      * Note: this method currently has no effect once the pool has been initialized. The pool is initialized the first
-     * time one of the following methods is invoked: <code>getConnection, setLogwriter,
-     * setLoginTimeout, getLoginTimeout, getLogWriter.</code>
+     * time one of the following methods is invoked: {@link #getConnection()}, {@link #setLogWriter(PrintWriter)},
+     * {@link #setLoginTimeout(int)}, {@link #getLoginTimeout()}, {@link #getLogWriter()}.
      * </p>
      *
      * @param password new value for the password
@@ -2270,8 +2338,8 @@ public class BasicDataSource implements DataSource, BasicDataSourceMXBean, MBean
      * Sets whether to pool statements or not.
      * <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>
+     * time one of the following methods is invoked: {@link #getConnection()}, {@link #setLogWriter(PrintWriter)},
+     * {@link #setLoginTimeout(int)}, {@link #getLoginTimeout()}, {@link #getLogWriter()}.
      * </p>
      *
      * @param poolingStatements pooling on or off
@@ -2442,8 +2510,8 @@ public class BasicDataSource implements DataSource, BasicDataSourceMXBean, MBean
      * 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>
+     * time one of the following methods is invoked: {@link #getConnection()}, {@link #setLogWriter(PrintWriter)},
+     * {@link #setLoginTimeout(int)}, {@link #getLoginTimeout()}, {@link #getLogWriter()}.
      * </p>
      *
      * @param connectionString the new value for the JDBC connection connectionString
@@ -2456,8 +2524,8 @@ public class BasicDataSource implements DataSource, BasicDataSourceMXBean, MBean
      * Sets the {code userName}.
      * <p>
      * Note: this method currently has no effect once the pool has been initialized. The pool is initialized the first
-     * time one of the following methods is invoked: <code>getConnection, setLogwriter,
-     * setLoginTimeout, getLoginTimeout, getLogWriter.</code>
+     * time one of the following methods is invoked: {@link #getConnection()}, {@link #setLogWriter(PrintWriter)},
+     * {@link #setLoginTimeout(int)}, {@link #getLoginTimeout()}, {@link #getLogWriter()}.
      * </p>
      *
      * @param userName the new value for the JDBC connection user name
@@ -2470,8 +2538,8 @@ public class BasicDataSource implements DataSource, BasicDataSourceMXBean, MBean
      * Sets the {code validationQuery}.
      * <p>
      * Note: this method currently has no effect once the pool has been initialized. The pool is initialized the first
-     * time one of the following methods is invoked: <code>getConnection, setLogwriter,
-     * setLoginTimeout, getLoginTimeout, getLogWriter.</code>
+     * time one of the following methods is invoked: {@link #getConnection()}, {@link #setLogWriter(PrintWriter)},
+     * {@link #setLoginTimeout(int)}, {@link #getLoginTimeout()}, {@link #getLogWriter()}.
      * </p>
      *
      * @param validationQuery the new value for the validation query
@@ -2485,8 +2553,8 @@ public class BasicDataSource implements DataSource, BasicDataSourceMXBean, MBean
      * response from the database when executing a validation query. Use a value less than or equal to 0 for no timeout.
      * <p>
      * Note: this method currently has no effect once the pool has been initialized. The pool is initialized the first
-     * time one of the following methods is invoked: <code>getConnection, setLogwriter,
-     * setLoginTimeout, getLoginTimeout, getLogWriter.</code>
+     * time one of the following methods is invoked: {@link #getConnection()}, {@link #setLogWriter(PrintWriter)},
+     * {@link #setLoginTimeout(int)}, {@link #getLoginTimeout()}, {@link #getLogWriter()}.
      * </p>
      *
      * @param validationQueryTimeoutDuration new validation query timeout value in seconds
@@ -2501,8 +2569,8 @@ public class BasicDataSource implements DataSource, BasicDataSourceMXBean, MBean
      * response from the database when executing a validation query. Use a value less than or equal to 0 for no timeout.
      * <p>
      * Note: this method currently has no effect once the pool has been initialized. The pool is initialized the first
-     * time one of the following methods is invoked: <code>getConnection, setLogwriter,
-     * setLoginTimeout, getLoginTimeout, getLogWriter.</code>
+     * time one of the following methods is invoked: {@link #getConnection()}, {@link #setLogWriter(PrintWriter)},
+     * {@link #setLoginTimeout(int)}, {@link #getLoginTimeout()}, {@link #getLogWriter()}.
      * </p>
      *
      * @param validationQueryTimeoutSeconds new validation query timeout value in seconds
@@ -2545,6 +2613,10 @@ public class BasicDataSource implements DataSource, BasicDataSourceMXBean, MBean
         }
     }
 
+    private Collector<String, ?, LinkedHashSet<String>> toLinkedHashSet() {
+        return Collectors.toCollection(LinkedHashSet::new);
+    }
+
     @Override
     public <T> T unwrap(final Class<T> iface) throws SQLException {
         if (isWrapperFor(iface)) {
diff --git a/java/org/apache/tomcat/dbcp/dbcp2/BasicDataSourceFactory.java b/java/org/apache/tomcat/dbcp/dbcp2/BasicDataSourceFactory.java
index 8e736624a9..56c9b19ba6 100644
--- a/java/org/apache/tomcat/dbcp/dbcp2/BasicDataSourceFactory.java
+++ b/java/org/apache/tomcat/dbcp/dbcp2/BasicDataSourceFactory.java
@@ -120,6 +120,18 @@ public class BasicDataSourceFactory implements ObjectFactory {
      */
     private static final String PROP_DISCONNECTION_SQL_CODES = "disconnectionSqlCodes";
 
+    /**
+     * Property key for specifying the SQL State codes that should be ignored during disconnection checks.
+     * <p>
+     * The value for this property must be a comma-separated string of SQL State codes, where each code represents
+     * a state that will be excluded from being treated as a fatal disconnection. The expected format is a series
+     * of SQL State codes separated by commas, with no spaces between them (e.g., "08003,08004").
+     * </p>
+     * @since 2.13.0
+     */
+    private static final String PROP_DISCONNECTION_IGNORE_SQL_CODES = "disconnectionIgnoreSqlCodes";
+
+
     /*
      * Block with obsolete properties from DBCP 1.x. Warn users that these are ignored and they should use the 2.x
      * properties.
@@ -149,8 +161,8 @@ public class BasicDataSourceFactory implements ObjectFactory {
             PROP_CLEAR_STATEMENT_POOL_ON_RETURN,
             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_REGISTER_CONNECTION_MBEAN, PROP_CONNECTION_FACTORY_CLASS_NAME);
+            PROP_DEFAULT_QUERY_TIMEOUT, PROP_FAST_FAIL_VALIDATION, PROP_DISCONNECTION_SQL_CODES, PROP_DISCONNECTION_IGNORE_SQL_CODES,
+            PROP_JMX_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
@@ -227,17 +239,23 @@ public class BasicDataSourceFactory implements ObjectFactory {
         getOptional(properties, PROP_DEFAULT_TRANSACTION_ISOLATION).ifPresent(value -> {
             value = value.toUpperCase(Locale.ROOT);
             int level = PoolableConnectionFactory.UNKNOWN_TRANSACTION_ISOLATION;
-            if ("NONE".equals(value)) {
+            switch (value) {
+            case "NONE":
                 level = Connection.TRANSACTION_NONE;
-            } else if ("READ_COMMITTED".equals(value)) {
+                break;
+            case "READ_COMMITTED":
                 level = Connection.TRANSACTION_READ_COMMITTED;
-            } else if ("READ_UNCOMMITTED".equals(value)) {
+                break;
+            case "READ_UNCOMMITTED":
                 level = Connection.TRANSACTION_READ_UNCOMMITTED;
-            } else if ("REPEATABLE_READ".equals(value)) {
+                break;
+            case "REPEATABLE_READ":
                 level = Connection.TRANSACTION_REPEATABLE_READ;
-            } else if ("SERIALIZABLE".equals(value)) {
+                break;
+            case "SERIALIZABLE":
                 level = Connection.TRANSACTION_SERIALIZABLE;
-            } else {
+                break;
+            default:
                 try {
                     level = Integer.parseInt(value);
                 } catch (final NumberFormatException e) {
@@ -246,6 +264,7 @@ public class BasicDataSourceFactory implements ObjectFactory {
                     System.err.println("using default value of database driver");
                     level = PoolableConnectionFactory.UNKNOWN_TRANSACTION_ISOLATION;
                 }
+                break;
             }
             dataSource.setDefaultTransactionIsolation(level);
         });
@@ -302,6 +321,7 @@ public class BasicDataSourceFactory implements ObjectFactory {
         acceptDurationOfSeconds(properties, PROP_DEFAULT_QUERY_TIMEOUT, dataSource::setDefaultQueryTimeout);
         acceptBoolean(properties, PROP_FAST_FAIL_VALIDATION, dataSource::setFastFailValidation);
         getOptional(properties, PROP_DISCONNECTION_SQL_CODES).ifPresent(v -> dataSource.setDisconnectionSqlCodes(parseList(v, ',')));
+        getOptional(properties, PROP_DISCONNECTION_IGNORE_SQL_CODES).ifPresent(v -> dataSource.setDisconnectionIgnoreSqlCodes(parseList(v, ',')));
         acceptString(properties, PROP_CONNECTION_FACTORY_CLASS_NAME, dataSource::setConnectionFactoryClassName);
 
         // DBCP-215
diff --git a/java/org/apache/tomcat/dbcp/dbcp2/Constants.java b/java/org/apache/tomcat/dbcp/dbcp2/Constants.java
index da83187dad..e96e0e53a4 100644
--- a/java/org/apache/tomcat/dbcp/dbcp2/Constants.java
+++ b/java/org/apache/tomcat/dbcp/dbcp2/Constants.java
@@ -23,13 +23,29 @@ package org.apache.tomcat.dbcp.dbcp2;
  */
 public class Constants {
 
+    /**
+     * Constant used to build JMX strings.
+     */
     public static final String JMX_CONNECTION_POOL_BASE_EXT = ",connectionpool=";
+
+    /**
+     * Constant used to build JMX strings.
+     */
     public static final String JMX_CONNECTION_POOL_PREFIX = "connections";
 
-    public static final String JMX_CONNECTION_BASE_EXT = JMX_CONNECTION_POOL_BASE_EXT + JMX_CONNECTION_POOL_PREFIX
-            + ",connection=";
+    /**
+     * Constant used to build JMX strings.
+     */
+    public static final String JMX_CONNECTION_BASE_EXT = JMX_CONNECTION_POOL_BASE_EXT + JMX_CONNECTION_POOL_PREFIX + ",connection=";
 
+    /**
+     * Constant used to build JMX strings.
+     */
     public static final String JMX_STATEMENT_POOL_BASE_EXT = JMX_CONNECTION_BASE_EXT;
+
+    /**
+     * Constant used to build JMX strings.
+     */
     public static final String JMX_STATEMENT_POOL_PREFIX = ",statementpool=statements";
 
     /**
diff --git a/java/org/apache/tomcat/dbcp/dbcp2/DataSourceMXBean.java b/java/org/apache/tomcat/dbcp/dbcp2/DataSourceMXBean.java
index de2afe062c..2f40c52b81 100644
--- a/java/org/apache/tomcat/dbcp/dbcp2/DataSourceMXBean.java
+++ b/java/org/apache/tomcat/dbcp/dbcp2/DataSourceMXBean.java
@@ -85,6 +85,16 @@ public interface DataSourceMXBean {
      */
     int getDefaultTransactionIsolation();
 
+    /**
+     * See {@link BasicDataSource#getDisconnectionIgnoreSqlCodesAsArray()}.
+     *
+     * @return {@link BasicDataSource#getDisconnectionIgnoreSqlCodesAsArray()}.
+     * @since 2.13.0
+     */
+    default String[] getDisconnectionIgnoreSqlCodesAsArray() {
+        return Utils.EMPTY_STRING_ARRAY;
+    }
+
     /**
      * See {@link BasicDataSource#getDisconnectionSqlCodesAsArray()}.
      *
diff --git a/java/org/apache/tomcat/dbcp/dbcp2/DelegatingConnection.java b/java/org/apache/tomcat/dbcp/dbcp2/DelegatingConnection.java
index 3d9624dc94..cf47cb1e86 100644
--- a/java/org/apache/tomcat/dbcp/dbcp2/DelegatingConnection.java
+++ b/java/org/apache/tomcat/dbcp/dbcp2/DelegatingConnection.java
@@ -95,6 +95,9 @@ public class DelegatingConnection<C extends Connection> extends AbandonedTrace i
         }
     }
 
+    /**
+     * Marks this instance as used and delegates to a wrapped {@link DelegatingConnection#activate()}.
+     */
     protected void activate() {
         closed = false;
         setLastUsed();
@@ -103,6 +106,11 @@ public class DelegatingConnection<C extends Connection> extends AbandonedTrace i
         }
     }
 
+    /**
+     * Throws a SQLException if this connection is not open.
+     *
+     * @throws SQLException Thrown if this connection is not open.
+     */
     protected void checkOpen() throws SQLException {
         if (closed) {
             if (null != connection) {
@@ -314,7 +322,13 @@ public class DelegatingConnection<C extends Connection> extends AbandonedTrace i
     }
 
     /**
-     * Returns the state caching flag.
+     * Gets whether to cache properties. The cached properties are:
+     * <ul>
+     * <li>auto-commit</li>
+     * <li>catalog</li>
+     * <li>schema</li>
+     * <li>read-only</li>
+     * </ul>
      *
      * @return the state caching flag
      */
@@ -573,6 +587,11 @@ public class DelegatingConnection<C extends Connection> extends AbandonedTrace i
         return closed || connection == null || connection.isClosed();
     }
 
+    /**
+     * Tests the raw internal closed state.
+     *
+     * @return the raw internal closed state.
+     */
     protected boolean isClosedInternal() {
         return closed;
     }
@@ -644,6 +663,11 @@ public class DelegatingConnection<C extends Connection> extends AbandonedTrace i
         }
     }
 
+    /**
+     * Clears the list of objects being traced by this object.
+     *
+     * @throws SQLException Thrown if not all traced objects were closed.
+     */
     protected void passivate() throws SQLException {
         // The JDBC specification requires that a Connection close any open
         // Statements when it is closed.
@@ -812,10 +836,15 @@ public class DelegatingConnection<C extends Connection> extends AbandonedTrace i
     }
 
     /**
-     * Sets the state caching flag.
+     * Sets whether to cache properties. The cached properties are:
+     * <ul>
+     * <li>auto-commit</li>
+     * <li>catalog</li>
+     * <li>schema</li>
+     * <li>read-only</li>
+     * </ul>
      *
-     * @param cacheState
-     *            The new value for the state caching flag
+     * @param cacheState The new value for the state caching flag
      */
     public void setCacheState(final boolean cacheState) {
         this.cacheState = cacheState;
@@ -859,6 +888,11 @@ public class DelegatingConnection<C extends Connection> extends AbandonedTrace i
         }
     }
 
+    /**
+     * Sets the raw internal closed state.
+     *
+     * @param closed the raw internal closed state.
+     */
     protected void setClosedInternal(final boolean closed) {
         this.closed = closed;
     }
@@ -960,7 +994,7 @@ public class DelegatingConnection<C extends Connection> extends AbandonedTrace i
         try {
             Jdbc41Bridge.setSchema(connection, schema);
             if (cacheState) {
-                cachedSchema = connection.getSchema();
+                cachedSchema = Jdbc41Bridge.getSchema(connection);
             }
         } catch (final SQLException e) {
             cachedSchema = null;
diff --git a/java/org/apache/tomcat/dbcp/dbcp2/DelegatingDatabaseMetaData.java b/java/org/apache/tomcat/dbcp/dbcp2/DelegatingDatabaseMetaData.java
index 79543e401b..21b27f18e0 100644
--- a/java/org/apache/tomcat/dbcp/dbcp2/DelegatingDatabaseMetaData.java
+++ b/java/org/apache/tomcat/dbcp/dbcp2/DelegatingDatabaseMetaData.java
@@ -22,14 +22,15 @@ import java.sql.ResultSet;
 import java.sql.RowIdLifetime;
 import java.sql.SQLException;
 import java.util.Objects;
+import java.util.concurrent.Callable;
 
 /**
  * <p>
  * A base delegating implementation of {@link DatabaseMetaData}.
  * </p>
  * <p>
- * Methods that create {@link ResultSet} objects are wrapped to create {@link DelegatingResultSet} objects and the
- * remaining methods simply call the corresponding method on the "delegate" provided in the constructor.
+ * Methods that create {@link ResultSet} objects are wrapped to create {@link DelegatingResultSet} objects and the remaining methods simply call the
+ * corresponding method on the "delegate" provided in the constructor.
  * </p>
  *
  * @since 2.0
@@ -45,190 +46,115 @@ public class DelegatingDatabaseMetaData implements DatabaseMetaData {
     /**
      * Constructs a new instance for the given delegating connection and database meta data.
      *
-     * @param connection
-     *            the delegating connection
-     * @param databaseMetaData
-     *            the database meta data
+     * @param connection       the delegating connection
+     * @param databaseMetaData the database meta data
      */
-    public DelegatingDatabaseMetaData(final DelegatingConnection<?> connection,
-            final DatabaseMetaData databaseMetaData) {
+    public DelegatingDatabaseMetaData(final DelegatingConnection<?> connection, final DatabaseMetaData databaseMetaData) {
         this.connection = Objects.requireNonNull(connection, "connection");
         this.databaseMetaData = Objects.requireNonNull(databaseMetaData, "databaseMetaData");
     }
 
     @Override
     public boolean allProceduresAreCallable() throws SQLException {
-        try {
-            return databaseMetaData.allProceduresAreCallable();
-        } catch (final SQLException e) {
-            handleException(e);
-            return false;
-        }
+        return getB(databaseMetaData::allProceduresAreCallable);
     }
 
     @Override
     public boolean allTablesAreSelectable() throws SQLException {
-        try {
-            return databaseMetaData.allTablesAreSelectable();
-        } catch (final SQLException e) {
-            handleException(e);
-            return false;
-        }
+        return getB(databaseMetaData::allTablesAreSelectable);
     }
 
     @Override
     public boolean autoCommitFailureClosesAllResultSets() throws SQLException {
-        try {
-            return databaseMetaData.autoCommitFailureClosesAllResultSets();
-        } catch (final SQLException e) {
-            handleException(e);
-            return false;
-        }
+        return getB(databaseMetaData::autoCommitFailureClosesAllResultSets);
     }
 
     @Override
     public boolean dataDefinitionCausesTransactionCommit() throws SQLException {
-        try {
-            return databaseMetaData.dataDefinitionCausesTransactionCommit();
-        } catch (final SQLException e) {
-            handleException(e);
-            return false;
-        }
+        return getB(databaseMetaData::dataDefinitionCausesTransactionCommit);
     }
 
     @Override
     public boolean dataDefinitionIgnoredInTransactions() throws SQLException {
-        try {
-            return databaseMetaData.dataDefinitionIgnoredInTransactions();
-        } catch (final SQLException e) {
-            handleException(e);
-            return false;
-        }
+        return getB(databaseMetaData::dataDefinitionIgnoredInTransactions);
     }
 
     @Override
     public boolean deletesAreDetected(final int type) throws SQLException {
-        try {
-            return databaseMetaData.deletesAreDetected(type);
-        } catch (final SQLException e) {
-            handleException(e);
-            return false;
-        }
+        return getB(() -> databaseMetaData.deletesAreDetected(type));
     }
 
     @Override
     public boolean doesMaxRowSizeIncludeBlobs() throws SQLException {
-        try {
-            return databaseMetaData.doesMaxRowSizeIncludeBlobs();
-        } catch (final SQLException e) {
-            handleException(e);
-            return false;
-        }
+        return getB(databaseMetaData::doesMaxRowSizeIncludeBlobs);
     }
 
     @Override
     public boolean generatedKeyAlwaysReturned() throws SQLException {
         connection.checkOpen();
+        return getB(() -> Jdbc41Bridge.generatedKeyAlwaysReturned(databaseMetaData));
+    }
+
+    private <T> T get(final Callable<T> s) throws SQLException {
+        return get(s, null);
+    }
+
+    private <T> T get(final Callable<T> s, final T defaultValue) throws SQLException {
         try {
-            return Jdbc41Bridge.generatedKeyAlwaysReturned(databaseMetaData);
-        } catch (final SQLException e) {
-            handleException(e);
-            return false;
+            return s.call();
+        } catch (final Exception e) {
+            if (e instanceof SQLException) {
+                handleException((SQLException) e);
+            }
+            return defaultValue;
         }
     }
 
     @Override
-    public ResultSet getAttributes(final String catalog, final String schemaPattern, final String typeNamePattern,
-            final String attributeNamePattern) throws SQLException {
-        connection.checkOpen();
-        try {
-            return DelegatingResultSet.wrapResultSet(connection,
-                    databaseMetaData.getAttributes(catalog, schemaPattern, typeNamePattern, attributeNamePattern));
-        } catch (final SQLException e) {
-            handleException(e);
-            throw new AssertionError();
-        }
+    public ResultSet getAttributes(final String catalog, final String schemaPattern, final String typeNamePattern, final String attributeNamePattern)
+            throws SQLException {
+        return getRS(() -> databaseMetaData.getAttributes(catalog, schemaPattern, typeNamePattern, attributeNamePattern));
+    }
+
+    private boolean getB(final Callable<Boolean> s) throws SQLException {
+        return get(s, false);
     }
 
     @Override
-    public ResultSet getBestRowIdentifier(final String catalog, final String schema, final String table,
-            final int scope, final boolean nullable) throws SQLException {
-        connection.checkOpen();
-        try {
-            return DelegatingResultSet.wrapResultSet(connection,
-                    databaseMetaData.getBestRowIdentifier(catalog, schema, table, scope, nullable));
-        } catch (final SQLException e) {
-            handleException(e);
-            throw new AssertionError();
-        }
+    public ResultSet getBestRowIdentifier(final String catalog, final String schema, final String table, final int scope, final boolean nullable)
+            throws SQLException {
+        return getRS(() -> databaseMetaData.getBestRowIdentifier(catalog, schema, table, scope, nullable));
     }
 
     @Override
     public ResultSet getCatalogs() throws SQLException {
-        connection.checkOpen();
-        try {
-            return DelegatingResultSet.wrapResultSet(connection, databaseMetaData.getCatalogs());
-        } catch (final SQLException e) {
-            handleException(e);
-            throw new AssertionError();
-        }
+        return getRS(databaseMetaData::getCatalogs);
     }
 
     @Override
     public String getCatalogSeparator() throws SQLException {
-        try {
-            return databaseMetaData.getCatalogSeparator();
-        } catch (final SQLException e) {
-            handleException(e);
-            throw new AssertionError();
-        }
+        return get(databaseMetaData::getCatalogSeparator);
     }
 
     @Override
     public String getCatalogTerm() throws SQLException {
-        try {
-            return databaseMetaData.getCatalogTerm();
-        } catch (final SQLException e) {
-            handleException(e);
-            throw new AssertionError();
-        }
+        return get(databaseMetaData::getCatalogTerm);
     }
 
     @Override
     public ResultSet getClientInfoProperties() throws SQLException {
-        connection.checkOpen();
-        try {
-            return DelegatingResultSet.wrapResultSet(connection, databaseMetaData.getClientInfoProperties());
-        } catch (final SQLException e) {
-            handleException(e);
-            throw new AssertionError();
-        }
+        return getRS(databaseMetaData::getClientInfoProperties);
     }
 
     @Override
-    public ResultSet getColumnPrivileges(final String catalog, final String schema, final String table,
-            final String columnNamePattern) throws SQLException {
-        connection.checkOpen();
-        try {
-            return DelegatingResultSet.wrapResultSet(connection,
-                    databaseMetaData.getColumnPrivileges(catalog, schema, table, columnNamePattern));
-        } catch (final SQLException e) {
-            handleException(e);
-            throw new AssertionError();
-        }
+    public ResultSet getColumnPrivileges(final String catalog, final String schema, final String table, final String columnNamePattern) throws SQLException {
+        return getRS(() -> databaseMetaData.getColumnPrivileges(catalog, schema, table, columnNamePattern));
     }
 
     @Override
-    public ResultSet getColumns(final String catalog, final String schemaPattern, final String tableNamePattern,
-            final String columnNamePattern) throws SQLException {
-        connection.checkOpen();
-        try {
-            return DelegatingResultSet.wrapResultSet(connection,
-                    databaseMetaData.getColumns(catalog, schemaPattern, tableNamePattern, columnNamePattern));
-        } catch (final SQLException e) {
-            handleException(e);
-            throw new AssertionError();
-        }
+    public ResultSet getColumns(final String catalog, final String schemaPattern, final String tableNamePattern, final String columnNamePattern)
+            throws SQLException {
+        return getRS(() -> databaseMetaData.getColumns(catalog, schemaPattern, tableNamePattern, columnNamePattern));
     }
 
     @Override
@@ -237,66 +163,34 @@ public class DelegatingDatabaseMetaData implements DatabaseMetaData {
     }
 
     @Override
-    public ResultSet getCrossReference(final String parentCatalog, final String parentSchema, final String parentTable,
-            final String foreignCatalog, final String foreignSchema, final String foreignTable) throws SQLException {
-        connection.checkOpen();
-        try {
-            return DelegatingResultSet.wrapResultSet(connection, databaseMetaData.getCrossReference(parentCatalog,
-                    parentSchema, parentTable, foreignCatalog, foreignSchema, foreignTable));
-        } catch (final SQLException e) {
-            handleException(e);
-            throw new AssertionError();
-        }
+    public ResultSet getCrossReference(final String parentCatalog, final String parentSchema, final String parentTable, final String foreignCatalog,
+            final String foreignSchema, final String foreignTable) throws SQLException {
+        return getRS(() -> databaseMetaData.getCrossReference(parentCatalog, parentSchema, parentTable, foreignCatalog, foreignSchema, foreignTable));
     }
 
     @Override
     public int getDatabaseMajorVersion() throws SQLException {
-        try {
-            return databaseMetaData.getDatabaseMajorVersion();
-        } catch (final SQLException e) {
-            handleException(e);
-            return 0;
-        }
+        return getI(databaseMetaData::getDatabaseMajorVersion);
     }
 
     @Override
     public int getDatabaseMinorVersion() throws SQLException {
-        try {
-            return databaseMetaData.getDatabaseMinorVersion();
-        } catch (final SQLException e) {
-            handleException(e);
-            return 0;
-        }
+        return getI(databaseMetaData::getDatabaseMinorVersion);
     }
 
     @Override
     public String getDatabaseProductName() throws SQLException {
-        try {
-            return databaseMetaData.getDatabaseProductName();
-        } catch (final SQLException e) {
-            handleException(e);
-            throw new AssertionError();
-        }
+        return get(databaseMetaData::getDatabaseProductName);
     }
 
     @Override
     public String getDatabaseProductVersion() throws SQLException {
-        try {
-            return databaseMetaData.getDatabaseProductVersion();
-        } catch (final SQLException e) {
-            handleException(e);
-            throw new AssertionError();
-        }
+        return get(databaseMetaData::getDatabaseProductVersion);
     }
 
     @Override
     public int getDefaultTransactionIsolation() throws SQLException {
-        try {
-            return databaseMetaData.getDefaultTransactionIsolation();
-        } catch (final SQLException e) {
-            handleException(e);
-            return 0;
-        }
+        return getI(databaseMetaData::getDefaultTransactionIsolation);
     }
 
     /**
@@ -320,119 +214,63 @@ public class DelegatingDatabaseMetaData implements DatabaseMetaData {
 
     @Override
     public String getDriverName() throws SQLException {
-        try {
-            return databaseMetaData.getDriverName();
-        } catch (final SQLException e) {
-            handleException(e);
-            throw new AssertionError();
-        }
+        return get(databaseMetaData::getDriverName);
     }
 
     @Override
     public String getDriverVersion() throws SQLException {
-        try {
-            return databaseMetaData.getDriverVersion();
-        } catch (final SQLException e) {
-            handleException(e);
-            throw new AssertionError();
-        }
+        return get(databaseMetaData::getDriverVersion);
     }
 
     @Override
-    public ResultSet getExportedKeys(final String catalog, final String schema, final String table)
-            throws SQLException {
-        connection.checkOpen();
-        try {
-            return DelegatingResultSet.wrapResultSet(connection,
-                    databaseMetaData.getExportedKeys(catalog, schema, table));
-        } catch (final SQLException e) {
-            handleException(e);
-            throw new AssertionError();
-        }
+    public ResultSet getExportedKeys(final String catalog, final String schema, final String table) throws SQLException {
+        return getRS(() -> databaseMetaData.getExportedKeys(catalog, schema, table));
     }
 
     @Override
     public String getExtraNameCharacters() throws SQLException {
-        try {
-            return databaseMetaData.getExtraNameCharacters();
-        } catch (final SQLException e) {
-            handleException(e);
-            throw new AssertionError();
-        }
+        return get(databaseMetaData::getExtraNameCharacters);
     }
 
     @Override
-    public ResultSet getFunctionColumns(final String catalog, final String schemaPattern,
-            final String functionNamePattern, final String columnNamePattern) throws SQLException {
-        connection.checkOpen();
-        try {
-            return DelegatingResultSet.wrapResultSet(connection, databaseMetaData.getFunctionColumns(catalog,
-                    schemaPattern, functionNamePattern, columnNamePattern));
-        } catch (final SQLException e) {
-            handleException(e);
-            throw new AssertionError();
-        }
+    public ResultSet getFunctionColumns(final String catalog, final String schemaPattern, final String functionNamePattern, final String columnNamePattern)
+            throws SQLException {
+        return getRS(() -> databaseMetaData.getFunctionColumns(catalog, schemaPattern, functionNamePattern, columnNamePattern));
     }
 
     @Override
-    public ResultSet getFunctions(final String catalog, final String schemaPattern, final String functionNamePattern)
-            throws SQLException {
-        connection.checkOpen();
-        try {
-            return DelegatingResultSet.wrapResultSet(connection,
-                    databaseMetaData.getFunctions(catalog, schemaPattern, functionNamePattern));
-        } catch (final SQLException e) {
-            handleException(e);
-            throw new AssertionError();
-        }
+    public ResultSet getFunctions(final String catalog, final String schemaPattern, final String functionNamePattern) throws SQLException {
+        return getRS(() -> databaseMetaData.getFunctions(catalog, schemaPattern, functionNamePattern));
+    }
+
+    private int getI(final Callable<Integer> s) throws SQLException {
+        return get(s, 0);
     }
 
     @Override
     public String getIdentifierQuoteString() throws SQLException {
-        try {
-            return databaseMetaData.getIdentifierQuoteString();
-        } catch (final SQLException e) {
-            handleException(e);
-            throw new AssertionError();
-        }
+        return get(databaseMetaData::getIdentifierQuoteString);
     }
 
     @Override
-    public ResultSet getImportedKeys(final String catalog, final String schema, final String table)
-            throws SQLException {
-        connection.checkOpen();
-        try {
-            return DelegatingResultSet.wrapResultSet(connection,
-                    databaseMetaData.getImportedKeys(catalog, schema, table));
-        } catch (final SQLException e) {
-            handleException(e);
-            throw new AssertionError();
-        }
+    public ResultSet getImportedKeys(final String catalog, final String schema, final String table) throws SQLException {
+        return getRS(() -> databaseMetaData.getImportedKeys(catalog, schema, table));
     }
 
     @Override
-    public ResultSet getIndexInfo(final String catalog, final String schema, final String table, final boolean unique,
-            final boolean approximate) throws SQLException {
-        connection.checkOpen();
-        try {
-            return DelegatingResultSet.wrapResultSet(connection,
-                    databaseMetaData.getIndexInfo(catalog, schema, table, unique, approximate));
-        } catch (final SQLException e) {
-            handleException(e);
-            throw new AssertionError();
-        }
+    public ResultSet getIndexInfo(final String catalog, final String schema, final String table, final boolean unique, final boolean approximate)
+            throws SQLException {
+        return getRS(() -> databaseMetaData.getIndexInfo(catalog, schema, table, unique, approximate));
     }
 
     /**
-     * If my underlying {@link ResultSet} is not a {@code DelegatingResultSet}, returns it, otherwise recursively
-     * invokes this method on my delegate.
+     * If my underlying {@link ResultSet} is not a {@code DelegatingResultSet}, returns it, otherwise recursively invokes this method on my delegate.
      * <p>
-     * Hence this method will return the first delegate that is not a {@code DelegatingResultSet}, or {@code null} when
-     * no non-{@code DelegatingResultSet} delegate can be found by traversing this chain.
+     * Hence this method will return the first delegate that is not a {@code DelegatingResultSet}, or {@code null} when no non-{@code DelegatingResultSet}
+     * delegate can be found by traversing this chain.
      * </p>
      * <p>
-     * This method is useful when you may have nested {@code DelegatingResultSet}s, and you want to make sure to obtain
-     * a "genuine" {@link ResultSet}.
+     * This method is useful when you may have nested {@code DelegatingResultSet}s, and you want to make sure to obtain a "genuine" {@link ResultSet}.
      * </p>
      *
      * @return the innermost database meta data.
@@ -450,142 +288,76 @@ public class DelegatingDatabaseMetaData implements DatabaseMetaData {
 
     @Override
     public int getJDBCMajorVersion() throws SQLException {
-        try {
-            return databaseMetaData.getJDBCMajorVersion();
-        } catch (final SQLException e) {
-            handleException(e);
-            return 0;
-        }
+        return getI(databaseMetaData::getJDBCMajorVersion);
     }
 
     @Override
     public int getJDBCMinorVersion() throws SQLException {
-        try {
-            return databaseMetaData.getJDBCMinorVersion();
-        } catch (final SQLException e) {
-            handleException(e);
-            return 0;
-        }
+        return getI(databaseMetaData::getJDBCMinorVersion);
+    }
+
+    private long getL(final Callable<Long> s) throws SQLException {
+        return get(s, 0L);
     }
 
     @Override
     public int getMaxBinaryLiteralLength() throws SQLException {
-        try {
-            return databaseMetaData.getMaxBinaryLiteralLength();
-        } catch (final SQLException e) {
-            handleException(e);
-            return 0;
-        }
+        return getI(databaseMetaData::getMaxBinaryLiteralLength);
     }
 
     @Override
     public int getMaxCatalogNameLength() throws SQLException {
-        try {
-            return databaseMetaData.getMaxCatalogNameLength();
-        } catch (final SQLException e) {
-            handleException(e);
-            return 0;
-        }
+        return getI(databaseMetaData::getMaxCatalogNameLength);
     }
 
     @Override
     public int getMaxCharLiteralLength() throws SQLException {
-        try {
-            return databaseMetaData.getMaxCharLiteralLength();
-        } catch (final SQLException e) {
-            handleException(e);
-            return 0;
-        }
+        return getI(databaseMetaData::getMaxCharLiteralLength);
     }
 
     @Override
     public int getMaxColumnNameLength() throws SQLException {
-        try {
-            return databaseMetaData.getMaxColumnNameLength();
-        } catch (final SQLException e) {
-            handleException(e);
-            return 0;
-        }
+        return getI(databaseMetaData::getMaxColumnNameLength);
     }
 
     @Override
     public int getMaxColumnsInGroupBy() throws SQLException {
-        try {
-            return databaseMetaData.getMaxColumnsInGroupBy();
-        } catch (final SQLException e) {
-            handleException(e);
-            return 0;
-        }
+        return getI(databaseMetaData::getMaxColumnsInGroupBy);
     }
 
     @Override
     public int getMaxColumnsInIndex() throws SQLException {
-        try {
-            return databaseMetaData.getMaxColumnsInIndex();
-        } catch (final SQLException e) {
-            handleException(e);
-            return 0;
-        }
+        return getI(databaseMetaData::getMaxColumnsInIndex);
     }
 
     @Override
     public int getMaxColumnsInOrderBy() throws SQLException {
-        try {
-            return databaseMetaData.getMaxColumnsInOrderBy();
-        } catch (final SQLException e) {
-            handleException(e);
-            return 0;
-        }
+        return getI(databaseMetaData::getMaxColumnsInOrderBy);
     }
 
     @Override
     public int getMaxColumnsInSelect() throws SQLException {
-        try {
-            return databaseMetaData.getMaxColumnsInSelect();
-        } catch (final SQLException e) {
-            handleException(e);
-            return 0;
-        }
+        return getI(databaseMetaData::getMaxColumnsInSelect);
     }
 
     @Override
     public int getMaxColumnsInTable() throws SQLException {
-        try {
-            return databaseMetaData.getMaxColumnsInTable();
-        } catch (final SQLException e) {
-            handleException(e);
-            return 0;
-        }
+        return getI(databaseMetaData::getMaxColumnsInTable);
     }
 
     @Override
     public int getMaxConnections() throws SQLException {
-        try {
-            return databaseMetaData.getMaxConnections();
-        } catch (final SQLException e) {
-            handleException(e);
-            return 0;
-        }
+        return getI(databaseMetaData::getMaxConnections);
     }
 
     @Override
     public int getMaxCursorNameLength() throws SQLException {
-        try {
-            return databaseMetaData.getMaxCursorNameLength();
-        } catch (final SQLException e) {
-            handleException(e);
-            return 0;
-        }
+        return getI(databaseMetaData::getMaxCursorNameLength);
     }
 
     @Override
     public int getMaxIndexLength() throws SQLException {
-        try {
-            return databaseMetaData.getMaxIndexLength();
-        } catch (final SQLException e) {
-            handleException(e);
-            return 0;
-        }
+        return getI(databaseMetaData::getMaxIndexLength);
     }
 
     /**
@@ -593,397 +365,198 @@ public class DelegatingDatabaseMetaData implements DatabaseMetaData {
      */
     @Override
     public long getMaxLogicalLobSize() throws SQLException {
-        try {
-            return databaseMetaData.getMaxLogicalLobSize();
-        } catch (final SQLException e) {
-            handleException(e);
-            return 0;
-        }
+        return getL(databaseMetaData::getMaxLogicalLobSize);
     }
 
     @Override
     public int getMaxProcedureNameLength() throws SQLException {
-        try {
-            return databaseMetaData.getMaxProcedureNameLength();
-        } catch (final SQLException e) {
-            handleException(e);
-            return 0;
-        }
+        return getI(databaseMetaData::getMaxProcedureNameLength);
     }
 
     @Override
     public int getMaxRowSize() throws SQLException {
-        try {
-            return databaseMetaData.getMaxRowSize();
-        } catch (final SQLException e) {
-            handleException(e);
-            return 0;
-        }
+        return getI(databaseMetaData::getMaxRowSize);
     }
 
     @Override
     public int getMaxSchemaNameLength() throws SQLException {
-        try {
-            return databaseMetaData.getMaxSchemaNameLength();
-        } catch (final SQLException e) {
-            handleException(e);
-            return 0;
-        }
+        return getI(databaseMetaData::getMaxSchemaNameLength);
     }
 
     @Override
     public int getMaxStatementLength() throws SQLException {
-        try {
-            return databaseMetaData.getMaxStatementLength();
-        } catch (final SQLException e) {
-            handleException(e);
-            return 0;
-        }
+        return getI(databaseMetaData::getMaxStatementLength);
     }
 
     @Override
     public int getMaxStatements() throws SQLException {
-        try {
-            return databaseMetaData.getMaxStatements();
-        } catch (final SQLException e) {
-            handleException(e);
-            return 0;
-        }
+        return getI(databaseMetaData::getMaxStatements);
     }
 
     @Override
     public int getMaxTableNameLength() throws SQLException {
-        try {
-            return databaseMetaData.getMaxTableNameLength();
-        } catch (final SQLException e) {
-            handleException(e);
-            return 0;
-        }
+        return getI(databaseMetaData::getMaxTableNameLength);
     }
 
     @Override
     public int getMaxTablesInSelect() throws SQLException {
-        try {
-            return databaseMetaData.getMaxTablesInSelect();
-        } catch (final SQLException e) {
-            handleException(e);
-            return 0;
-        }
+        return getI(databaseMetaData::getMaxTablesInSelect);
     }
 
     @Override
     public int getMaxUserNameLength() throws SQLException {
-        try {
-            return databaseMetaData.getMaxUserNameLength();
-        } catch (final SQLException e) {
-            handleException(e);
-            return 0;
-        }
+        return getI(databaseMetaData::getMaxUserNameLength);
     }
 
     @Override
     public String getNumericFunctions() throws SQLException {
-        try {
-            return databaseMetaData.getNumericFunctions();
-        } catch (final SQLException e) {
-            handleException(e);
-            throw new AssertionError();
-        }
+        return get(databaseMetaData::getNumericFunctions);
     }
 
     @Override
     public ResultSet getPrimaryKeys(final String catalog, final String schema, final String table) throws SQLException {
-        connection.checkOpen();
-        try {
-            return DelegatingResultSet.wrapResultSet(connection,
-                    databaseMetaData.getPrimaryKeys(catalog, schema, table));
-        } catch (final SQLException e) {
-            handleException(e);
-            throw new AssertionError();
-        }
+        return getRS(() -> databaseMetaData.getPrimaryKeys(catalog, schema, table));
     }
 
     @Override
-    public ResultSet getProcedureColumns(final String catalog, final String schemaPattern,
-            final String procedureNamePattern, final String columnNamePattern) throws SQLException {
-        connection.checkOpen();
-        try {
-            return DelegatingResultSet.wrapResultSet(connection, databaseMetaData.getProcedureColumns(catalog,
-                    schemaPattern, procedureNamePattern, columnNamePattern));
-        } catch (final SQLException e) {
-            handleException(e);
-            throw new AssertionError();
-        }
+    public ResultSet getProcedureColumns(final String catalog, final String schemaPattern, final String procedureNamePattern, final String columnNamePattern)
+            throws SQLException {
+        return getRS(() -> databaseMetaData.getProcedureColumns(catalog, schemaPattern, procedureNamePattern, columnNamePattern));
     }
 
     @Override
-    public ResultSet getProcedures(final String catalog, final String schemaPattern, final String procedureNamePattern)
-            throws SQLException {
-        connection.checkOpen();
-        try {
-            return DelegatingResultSet.wrapResultSet(connection,
-                    databaseMetaData.getProcedures(catalog, schemaPattern, procedureNamePattern));
-        } catch (final SQLException e) {
-            handleException(e);
-            throw new AssertionError();
-        }
+    public ResultSet getProcedures(final String catalog, final String schemaPattern, final String procedureNamePattern) throws SQLException {
+        return getRS(() -> databaseMetaData.getProcedures(catalog, schemaPattern, procedureNamePattern));
     }
 
     @Override
     public String getProcedureTerm() throws SQLException {
-        try {
-            return databaseMetaData.getProcedureTerm();
-        } catch (final SQLException e) {
-            handleException(e);
-            throw new AssertionError();
-        }
+        return get(databaseMetaData::getProcedureTerm);
     }
 
     @Override
-    public ResultSet getPseudoColumns(final String catalog, final String schemaPattern, final String tableNamePattern,
-            final String columnNamePattern) throws SQLException {
-        connection.checkOpen();
-        try {
-            return DelegatingResultSet.wrapResultSet(connection, Jdbc41Bridge.getPseudoColumns(databaseMetaData,
-                    catalog, schemaPattern, tableNamePattern, columnNamePattern));
-        } catch (final SQLException e) {
-            handleException(e);
-            throw new AssertionError();
-        }
+    public ResultSet getPseudoColumns(final String catalog, final String schemaPattern, final String tableNamePattern, final String columnNamePattern)
+            throws SQLException {
+        return getRS(() -> Jdbc41Bridge.getPseudoColumns(databaseMetaData, catalog, schemaPattern, tableNamePattern, columnNamePattern));
     }
 
     @Override
     public int getResultSetHoldability() throws SQLException {
-        try {
-            return databaseMetaData.getResultSetHoldability();
-        } catch (final SQLException e) {
-            handleException(e);
-            return 0;
-        }
+        return getI(databaseMetaData::getResultSetHoldability);
     }
 
     @Override
     public RowIdLifetime getRowIdLifetime() throws SQLException {
-        try {
-            return databaseMetaData.getRowIdLifetime();
-        } catch (final SQLException e) {
-            handleException(e);
-            throw new AssertionError();
-        }
+        return get(databaseMetaData::getRowIdLifetime);
+    }
+
+    @SuppressWarnings("resource")
+    private ResultSet getRS(final Callable<ResultSet> s) throws SQLException {
+        connection.checkOpen();
+        return DelegatingResultSet.wrapResultSet(connection, get(s));
     }
 
     @Override
     public ResultSet getSchemas() throws SQLException {
-        connection.checkOpen();
-        try {
-            return DelegatingResultSet.wrapResultSet(connection, databaseMetaData.getSchemas());
-        } catch (final SQLException e) {
-            handleException(e);
-            throw new AssertionError();
-        }
+        return getRS(databaseMetaData::getSchemas);
     }
 
     @Override
     public ResultSet getSchemas(final String catalog, final String schemaPattern) throws SQLException {
-        connection.checkOpen();
-        try {
-            return DelegatingResultSet.wrapResultSet(connection, databaseMetaData.getSchemas(catalog, schemaPattern));
-        } catch (final SQLException e) {
-            handleException(e);
-            throw new AssertionError();
-        }
+        return getRS(() -> databaseMetaData.getSchemas(catalog, schemaPattern));
     }
 
     @Override
     public String getSchemaTerm() throws SQLException {
-        try {
-            return databaseMetaData.getSchemaTerm();
-        } catch (final SQLException e) {
-            handleException(e);
-            throw new AssertionError();
-        }
+        return get(databaseMetaData::getSchemaTerm);
     }
 
     @Override
     public String getSearchStringEscape() throws SQLException {
-        try {
-            return databaseMetaData.getSearchStringEscape();
-        } catch (final SQLException e) {
-            handleException(e);
-            throw new AssertionError();
-        }
+        return get(databaseMetaData::getSearchStringEscape);
     }
 
     @Override
     public String getSQLKeywords() throws SQLException {
-        try {
-            return databaseMetaData.getSQLKeywords();
-        } catch (final SQLException e) {
-            handleException(e);
-            throw new AssertionError();
-        }
+        return get(databaseMetaData::getSQLKeywords);
     }
 
     @Override
     public int getSQLStateType() throws SQLException {
-        try {
-            return databaseMetaData.getSQLStateType();
-        } catch (final SQLException e) {
-            handleException(e);
-            return 0;
-        }
+        return getI(databaseMetaData::getSQLStateType);
     }
 
     @Override
     public String getStringFunctions() throws SQLException {
-        try {
-            return databaseMetaData.getStringFunctions();
-        } catch (final SQLException e) {
-            handleException(e);
-            throw new AssertionError();
-        }
+        return get(databaseMetaData::getStringFunctions);
     }
 
     @Override
-    public ResultSet getSuperTables(final String catalog, final String schemaPattern, final String tableNamePattern)
-            throws SQLException {
-        connection.checkOpen();
-        try {
-            return DelegatingResultSet.wrapResultSet(connection,
-                    databaseMetaData.getSuperTables(catalog, schemaPattern, tableNamePattern));
-        } catch (final SQLException e) {
-            handleException(e);
-            throw new AssertionError();
-        }
+    public ResultSet getSuperTables(final String catalog, final String schemaPattern, final String tableNamePattern) throws SQLException {
+        return getRS(() -> databaseMetaData.getSuperTables(catalog, schemaPattern, tableNamePattern));
     }
 
     @Override
-    public ResultSet getSuperTypes(final String catalog, final String schemaPattern, final String typeNamePattern)
-            throws SQLException {
-        connection.checkOpen();
-        try {
-            return DelegatingResultSet.wrapResultSet(connection,
-                    databaseMetaData.getSuperTypes(catalog, schemaPattern, typeNamePattern));
-        } catch (final SQLException e) {
-            handleException(e);
-            throw new AssertionError();
-        }
+    public ResultSet getSuperTypes(final String catalog, final String schemaPattern, final String typeNamePattern) throws SQLException {
+        return getRS(() -> databaseMetaData.getSuperTypes(catalog, schemaPattern, typeNamePattern));
     }
 
     @Override
     public String getSystemFunctions() throws SQLException {
-        try {
-            return databaseMetaData.getSystemFunctions();
-        } catch (final SQLException e) {
-            handleException(e);
-            throw new AssertionError();
-        }
+        return get(databaseMetaData::getSystemFunctions);
     }
 
     @Override
-    public ResultSet getTablePrivileges(final String catalog, final String schemaPattern, final String tableNamePattern)
-            throws SQLException {
-        connection.checkOpen();
-        try {
-            return DelegatingResultSet.wrapResultSet(connection,
-                    databaseMetaData.getTablePrivileges(catalog, schemaPattern, tableNamePattern));
-        } catch (final SQLException e) {
-            handleException(e);
-            throw new AssertionError();
-        }
+    public ResultSet getTablePrivileges(final String catalog, final String schemaPattern, final String tableNamePattern) throws SQLException {
+        return getRS(() -> databaseMetaData.getTablePrivileges(catalog, schemaPattern, tableNamePattern));
     }
 
     @Override
-    public ResultSet getTables(final String catalog, final String schemaPattern, final String tableNamePattern,
-            final String[] types) throws SQLException {
-        connection.checkOpen();
-        try {
-            return DelegatingResultSet.wrapResultSet(connection,
-                    databaseMetaData.getTables(catalog, schemaPattern, tableNamePattern, types));
-        } catch (final SQLException e) {
-            handleException(e);
-            throw new AssertionError();
-        }
+    public ResultSet getTables(final String catalog, final String schemaPattern, final String tableNamePattern, final String[] types) throws SQLException {
+        return getRS(() -> databaseMetaData.getTables(catalog, schemaPattern, tableNamePattern, types));
     }
 
     @Override
     public ResultSet getTableTypes() throws SQLException {
-        connection.checkOpen();
-        try {
-            return DelegatingResultSet.wrapResultSet(connection, databaseMetaData.getTableTypes());
-        } catch (final SQLException e) {
-            handleException(e);
-            throw new AssertionError();
-        }
+        return getRS(databaseMetaData::getTableTypes);
     }
 
     @Override
     public String getTimeDateFunctions() throws SQLException {
-        try {
-            return databaseMetaData.getTimeDateFunctions();
-        } catch (final SQLException e) {
-            handleException(e);
-            throw new AssertionError();
-        }
+        return get(databaseMetaData::getTimeDateFunctions);
     }
 
     @Override
     public ResultSet getTypeInfo() throws SQLException {
-        connection.checkOpen();
-        try {
-            return DelegatingResultSet.wrapResultSet(connection, databaseMetaData.getTypeInfo());
-        } catch (final SQLException e) {
-            handleException(e);
-            throw new AssertionError();
-        }
+        return getRS(databaseMetaData::getTypeInfo);
     }
 
     @Override
-    public ResultSet getUDTs(final String catalog, final String schemaPattern, final String typeNamePattern,
-            final int[] types) throws SQLException {
-        connection.checkOpen();
-        try {
-            return DelegatingResultSet.wrapResultSet(connection,
-                    databaseMetaData.getUDTs(catalog, schemaPattern, typeNamePattern, types));
-        } catch (final SQLException e) {
-            handleException(e);
-            throw new AssertionError();
-        }
+    public ResultSet getUDTs(final String catalog, final String schemaPattern, final String typeNamePattern, final int[] types) throws SQLException {
+        return getRS(() -> databaseMetaData.getUDTs(catalog, schemaPattern, typeNamePattern, types));
     }
 
     @Override
     public String getURL() throws SQLException {
-        try {
-            return databaseMetaData.getURL();
-        } catch (final SQLException e) {
-            handleException(e);
-            throw new AssertionError();
-        }
+        return get(databaseMetaData::getURL);
     }
 
     @Override
     public String getUserName() throws SQLException {
-        try {
-            return databaseMetaData.getUserName();
-        } catch (final SQLException e) {
-            handleException(e);
-            throw new AssertionError();
-        }
+        return get(databaseMetaData::getUserName);
     }
 
     @Override
-    public ResultSet getVersionColumns(final String catalog, final String schema, final String table)
-            throws SQLException {
-        connection.checkOpen();
-        try {
-            return DelegatingResultSet.wrapResultSet(connection,
-                    databaseMetaData.getVersionColumns(catalog, schema, table));
-        } catch (final SQLException e) {
-            handleException(e);
-            throw new AssertionError();
-        }
+    public ResultSet getVersionColumns(final String catalog, final String schema, final String table) throws SQLException {
+        return getRS(() -> databaseMetaData.getVersionColumns(catalog, schema, table));
     }
 
+    /**
+     * Delegates to the connection's {@link DelegatingConnection#handleException(SQLException)}.
+     *
+     * @param e the exception to throw or delegate.
+     * @throws SQLException the exception to throw.
+     */
     protected void handleException(final SQLException e) throws SQLException {
         if (connection == null) {
             throw e;
@@ -993,32 +566,17 @@ public class DelegatingDatabaseMetaData implements DatabaseMetaData {
 
     @Override
     public boolean insertsAreDetected(final int type) throws SQLException {
-        try {
-            return databaseMetaData.insertsAreDetected(type);
-        } catch (final SQLException e) {
-            handleException(e);
-            return false;
-        }
+        return getB(() -> databaseMetaData.insertsAreDetected(type));
     }
 
     @Override
     public boolean isCatalogAtStart() throws SQLException {
-        try {
-            return databaseMetaData.isCatalogAtStart();
-        } catch (final SQLException e) {
-            handleException(e);
-            return false;
-        }
+        return getB(databaseMetaData::isCatalogAtStart);
     }
 
     @Override
     public boolean isReadOnly() throws SQLException {
-        try {
-            return databaseMetaData.isReadOnly();
-        } catch (final SQLException e) {
-            handleException(e);
-            return false;
-        }
+        return getB(databaseMetaData::isReadOnly);
     }
 
     @Override
@@ -1034,632 +592,318 @@ public class DelegatingDatabaseMetaData implements DatabaseMetaData {
 
     @Override
     public boolean locatorsUpdateCopy() throws SQLException {
-        try {
-            return databaseMetaData.locatorsUpdateCopy();
-        } catch (final SQLException e) {
-            handleException(e);
-            return false;
-        }
+        return getB(databaseMetaData::locatorsUpdateCopy);
     }
 
     @Override
     public boolean nullPlusNonNullIsNull() throws SQLException {
-        try {
-            return databaseMetaData.nullPlusNonNullIsNull();
-        } catch (final SQLException e) {
-            handleException(e);
-            return false;
-        }
+        return getB(databaseMetaData::nullPlusNonNullIsNull);
     }
 
     @Override
     public boolean nullsAreSortedAtEnd() throws SQLException {
-        try {
-            return databaseMetaData.nullsAreSortedAtEnd();
-        } catch (final SQLException e) {
-            handleException(e);
-            return false;
-        }
+        return getB(databaseMetaData::nullsAreSortedAtEnd);
     }
 
     @Override
     public boolean nullsAreSortedAtStart() throws SQLException {
-        try {
-            return databaseMetaData.nullsAreSortedAtStart();
-        } catch (final SQLException e) {
-            handleException(e);
-            return false;
-        }
+        return getB(databaseMetaData::nullsAreSortedAtStart);
     }
 
     @Override
     public boolean nullsAreSortedHigh() throws SQLException {
-        try {
-            return databaseMetaData.nullsAreSortedHigh();
-        } catch (final SQLException e) {
-            handleException(e);
-            return false;
-        }
+        return getB(databaseMetaData::nullsAreSortedHigh);
     }
 
     @Override
     public boolean nullsAreSortedLow() throws SQLException {
-        try {
-            return databaseMetaData.nullsAreSortedLow();
-        } catch (final SQLException e) {
-            handleException(e);
-            return false;
-        }
+        return getB(databaseMetaData::nullsAreSortedLow);
     }
 
     @Override
     public boolean othersDeletesAreVisible(final int type) throws SQLException {
-        try {
-            return databaseMetaData.othersDeletesAreVisible(type);
-        } catch (final SQLException e) {
-            handleException(e);
-            return false;
-        }
+        return getB(() -> databaseMetaData.othersDeletesAreVisible(type));
     }
 
     @Override
     public boolean othersInsertsAreVisible(final int type) throws SQLException {
-        try {
-            return databaseMetaData.othersInsertsAreVisible(type);
-        } catch (final SQLException e) {
-            handleException(e);
-            return false;
-        }
+        return getB(() -> databaseMetaData.othersInsertsAreVisible(type));
     }
 
     @Override
     public boolean othersUpdatesAreVisible(final int type) throws SQLException {
-        try {
-            return databaseMetaData.othersUpdatesAreVisible(type);
-        } catch (final SQLException e) {
-            handleException(e);
-            return false;
-        }
+        return getB(() -> databaseMetaData.othersUpdatesAreVisible(type));
     }
 
     @Override
     public boolean ownDeletesAreVisible(final int type) throws SQLException {
-        try {
-            return databaseMetaData.ownDeletesAreVisible(type);
-        } catch (final SQLException e) {
-            handleException(e);
-            return false;
-        }
+        return getB(() -> databaseMetaData.ownDeletesAreVisible(type));
     }
 
     @Override
     public boolean ownInsertsAreVisible(final int type) throws SQLException {
-        try {
-            return databaseMetaData.ownInsertsAreVisible(type);
-        } catch (final SQLException e) {
-            handleException(e);
-            return false;
-        }
+        return getB(() -> databaseMetaData.ownInsertsAreVisible(type));
     }
 
     @Override
     public boolean ownUpdatesAreVisible(final int type) throws SQLException {
-        try {
-            return databaseMetaData.ownUpdatesAreVisible(type);
-        } catch (final SQLException e) {
-            handleException(e);
-            return false;
-        }
+        return getB(() -> databaseMetaData.ownUpdatesAreVisible(type));
     }
 
     @Override
     public boolean storesLowerCaseIdentifiers() throws SQLException {
-        try {
-            return databaseMetaData.storesLowerCaseIdentifiers();
-        } catch (final SQLException e) {
-            handleException(e);
-            return false;
-        }
+        return getB(databaseMetaData::storesLowerCaseIdentifiers);
     }
 
     @Override
     public boolean storesLowerCaseQuotedIdentifiers() throws SQLException {
-        try {
-            return databaseMetaData.storesLowerCaseQuotedIdentifiers();
-        } catch (final SQLException e) {
-            handleException(e);
-            return false;
-        }
+        return getB(databaseMetaData::storesLowerCaseQuotedIdentifiers);
     }
 
     @Override
     public boolean storesMixedCaseIdentifiers() throws SQLException {
-        try {
-            return databaseMetaData.storesMixedCaseIdentifiers();
-        } catch (final SQLException e) {
-            handleException(e);
-            return false;
-        }
+        return getB(databaseMetaData::storesMixedCaseIdentifiers);
     }
 
     @Override
     public boolean storesMixedCaseQuotedIdentifiers() throws SQLException {
-        try {
-            return databaseMetaData.storesMixedCaseQuotedIdentifiers();
-        } catch (final SQLException e) {
-            handleException(e);
-            return false;
-        }
+        return getB(databaseMetaData::storesMixedCaseQuotedIdentifiers);
     }
 
     @Override
     public boolean storesUpperCaseIdentifiers() throws SQLException {
-        try {
-            return databaseMetaData.storesUpperCaseIdentifiers();
-        } catch (final SQLException e) {
-            handleException(e);
-            return false;
-        }
+        return getB(databaseMetaData::storesUpperCaseIdentifiers);
+
     }
 
     @Override
     public boolean storesUpperCaseQuotedIdentifiers() throws SQLException {
-        try {
-            return databaseMetaData.storesUpperCaseQuotedIdentifiers();
-        } catch (final SQLException e) {
-            handleException(e);
-            return false;
-        }
+        return getB(databaseMetaData::storesUpperCaseQuotedIdentifiers);
     }
 
     @Override
     public boolean supportsAlterTableWithAddColumn() throws SQLException {
-        try {
-            return databaseMetaData.supportsAlterTableWithAddColumn();
-        } catch (final SQLException e) {
-            handleException(e);
-            return false;
-        }
+        return getB(databaseMetaData::supportsAlterTableWithAddColumn);
     }
 
     @Override
     public boolean supportsAlterTableWithDropColumn() throws SQLException {
-        try {
-            return databaseMetaData.supportsAlterTableWithDropColumn();
-        } catch (final SQLException e) {
-            handleException(e);
-            return false;
-        }
+        return getB(databaseMetaData::supportsAlterTableWithDropColumn);
     }
 
     @Override
     public boolean supportsANSI92EntryLevelSQL() throws SQLException {
-        try {
-            return databaseMetaData.supportsANSI92EntryLevelSQL();
-        } catch (final SQLException e) {
-            handleException(e);
-            return false;
-        }
+        return getB(databaseMetaData::supportsANSI92EntryLevelSQL);
     }
 
     @Override
     public boolean supportsANSI92FullSQL() throws SQLException {
-        try {
-            return databaseMetaData.supportsANSI92FullSQL();
-        } catch (final SQLException e) {
-            handleException(e);
-            return false;
-        }
+        return getB(databaseMetaData::supportsANSI92FullSQL);
     }
 
     @Override
     public boolean supportsANSI92IntermediateSQL() throws SQLException {
-        try {
-            return databaseMetaData.supportsANSI92IntermediateSQL();
-        } catch (final SQLException e) {
-            handleException(e);
-            return false;
-        }
+        return getB(databaseMetaData::supportsANSI92IntermediateSQL);
     }
 
     @Override
     public boolean supportsBatchUpdates() throws SQLException {
-        try {
-            return databaseMetaData.supportsBatchUpdates();
-        } catch (final SQLException e) {
-            handleException(e);
-            return false;
-        }
+        return getB(databaseMetaData::supportsBatchUpdates);
     }
 
     @Override
     public boolean supportsCatalogsInDataManipulation() throws SQLException {
-        try {
-            return databaseMetaData.supportsCatalogsInDataManipulation();
-        } catch (final SQLException e) {
-            handleException(e);
-            return false;
-        }
+        return getB(databaseMetaData::supportsCatalogsInDataManipulation);
     }
 
     @Override
     public boolean supportsCatalogsInIndexDefinitions() throws SQLException {
-        try {
-            return databaseMetaData.supportsCatalogsInIndexDefinitions();
-        } catch (final SQLException e) {
-            handleException(e);
-            return false;
-        }
+        return getB(databaseMetaData::supportsCatalogsInIndexDefinitions);
     }
 
     @Override
     public boolean supportsCatalogsInPrivilegeDefinitions() throws SQLException {
-        try {
-            return databaseMetaData.supportsCatalogsInPrivilegeDefinitions();
-        } catch (final SQLException e) {
-            handleException(e);
-            return false;
-        }
+        return getB(databaseMetaData::supportsCatalogsInPrivilegeDefinitions);
     }
 
     @Override
     public boolean supportsCatalogsInProcedureCalls() throws SQLException {
-        try {
-            return databaseMetaData.supportsCatalogsInProcedureCalls();
-        } catch (final SQLException e) {
-            handleException(e);
-            return false;
-        }
+        return getB(databaseMetaData::supportsCatalogsInProcedureCalls);
     }
 
     @Override
     public boolean supportsCatalogsInTableDefinitions() throws SQLException {
-        try {
-            return databaseMetaData.supportsCatalogsInTableDefinitions();
-        } catch (final SQLException e) {
-            handleException(e);
-            return false;
-        }
+        return getB(databaseMetaData::supportsCatalogsInTableDefinitions);
     }
 
     @Override
     public boolean supportsColumnAliasing() throws SQLException {
-        try {
-            return databaseMetaData.supportsColumnAliasing();
-        } catch (final SQLException e) {
-            handleException(e);
-            return false;
-        }
+        return getB(databaseMetaData::supportsColumnAliasing);
     }
 
     @Override
     public boolean supportsConvert() throws SQLException {
-        try {
-            return databaseMetaData.supportsConvert();
-        } catch (final SQLException e) {
-            handleException(e);
-            return false;
-        }
+        return getB(databaseMetaData::supportsConvert);
     }
 
     @Override
     public boolean supportsConvert(final int fromType, final int toType) throws SQLException {
-        try {
-            return databaseMetaData.supportsConvert(fromType, toType);
-        } catch (final SQLException e) {
-            handleException(e);
-            return false;
-        }
+        return getB(() -> databaseMetaData.supportsConvert(fromType, toType));
     }
 
     @Override
     public boolean supportsCoreSQLGrammar() throws SQLException {
-        try {
-            return databaseMetaData.supportsCoreSQLGrammar();
-        } catch (final SQLException e) {
-            handleException(e);
-            return false;
-        }
+        return getB(databaseMetaData::supportsCoreSQLGrammar);
     }
 
     @Override
     public boolean supportsCorrelatedSubqueries() throws SQLException {
-        try {
-            return databaseMetaData.supportsCorrelatedSubqueries();
-        } catch (final SQLException e) {
-            handleException(e);
-            return false;
-        }
+        return getB(databaseMetaData::supportsCorrelatedSubqueries);
     }
 
     @Override
     public boolean supportsDataDefinitionAndDataManipulationTransactions() throws SQLException {
-        try {
-            return databaseMetaData.supportsDataDefinitionAndDataManipulationTransactions();
-        } catch (final SQLException e) {
-            handleException(e);
-            return false;
-        }
+        return getB(databaseMetaData::supportsDataDefinitionAndDataManipulationTransactions);
     }
 
     @Override
     public boolean supportsDataManipulationTransactionsOnly() throws SQLException {
-        try {
-            return databaseMetaData.supportsDataManipulationTransactionsOnly();
-        } catch (final SQLException e) {
-            handleException(e);
-            return false;
-        }
+        return getB(databaseMetaData::supportsDataManipulationTransactionsOnly);
     }
 
     @Override
     public boolean supportsDifferentTableCorrelationNames() throws SQLException {
-        try {
-            return databaseMetaData.supportsDifferentTableCorrelationNames();
-        } catch (final SQLException e) {
-            handleException(e);
-            return false;
-        }
+        return getB(databaseMetaData::supportsDifferentTableCorrelationNames);
     }
 
     @Override
     public boolean supportsExpressionsInOrderBy() throws SQLException {
-        try {
-            return databaseMetaData.supportsExpressionsInOrderBy();
-        } catch (final SQLException e) {
-            handleException(e);
-            return false;
-        }
+        return getB(databaseMetaData::supportsExpressionsInOrderBy);
     }
 
     @Override
     public boolean supportsExtendedSQLGrammar() throws SQLException {
-        try {
-            return databaseMetaData.supportsExtendedSQLGrammar();
-        } catch (final SQLException e) {
-            handleException(e);
-            return false;
-        }
+        return getB(databaseMetaData::supportsExtendedSQLGrammar);
     }
 
     @Override
     public boolean supportsFullOuterJoins() throws SQLException {
-        try {
-            return databaseMetaData.supportsFullOuterJoins();
-        } catch (final SQLException e) {
-            handleException(e);
-            return false;
-        }
+        return getB(databaseMetaData::supportsFullOuterJoins);
     }
 
     @Override
     public boolean supportsGetGeneratedKeys() throws SQLException {
-        try {
-            return databaseMetaData.supportsGetGeneratedKeys();
-        } catch (final SQLException e) {
-            handleException(e);
-            return false;
-        }
+        return getB(databaseMetaData::supportsGetGeneratedKeys);
     }
 
     @Override
     public boolean supportsGroupBy() throws SQLException {
-        try {
-            return databaseMetaData.supportsGroupBy();
-        } catch (final SQLException e) {
-            handleException(e);
-            return false;
-        }
+        return getB(databaseMetaData::supportsGroupBy);
     }
 
     @Override
     public boolean supportsGroupByBeyondSelect() throws SQLException {
-        try {
-            return databaseMetaData.supportsGroupByBeyondSelect();
-        } catch (final SQLException e) {
-            handleException(e);
-            return false;
-        }
+        return getB(databaseMetaData::supportsGroupByBeyondSelect);
     }
 
     @Override
     public boolean supportsGroupByUnrelated() throws SQLException {
-        try {
-            return databaseMetaData.supportsGroupByUnrelated();
-        } catch (final SQLException e) {
-            handleException(e);
-            return false;
-        }
+        return getB(databaseMetaData::supportsGroupByUnrelated);
     }
 
     @Override
     public boolean supportsIntegrityEnhancementFacility() throws SQLException {
-        try {
-            return databaseMetaData.supportsIntegrityEnhancementFacility();
-        } catch (final SQLException e) {
-            handleException(e);
-            return false;
-        }
+        return getB(databaseMetaData::supportsIntegrityEnhancementFacility);
     }
 
     @Override
     public boolean supportsLikeEscapeClause() throws SQLException {
-        try {
-            return databaseMetaData.supportsLikeEscapeClause();
-        } catch (final SQLException e) {
-            handleException(e);
-            return false;
-        }
+        return getB(databaseMetaData::supportsLikeEscapeClause);
     }
 
     @Override
     public boolean supportsLimitedOuterJoins() throws SQLException {
-        try {
-            return databaseMetaData.supportsLimitedOuterJoins();
-        } catch (final SQLException e) {
-            handleException(e);
-            return false;
-        }
+        return getB(databaseMetaData::supportsLimitedOuterJoins);
     }
 
     @Override
     public boolean supportsMinimumSQLGrammar() throws SQLException {
-        try {
-            return databaseMetaData.supportsMinimumSQLGrammar();
-        } catch (final SQLException e) {
-            handleException(e);
-            return false;
-        }
+        return getB(databaseMetaData::supportsMinimumSQLGrammar);
     }
 
     @Override
     public boolean supportsMixedCaseIdentifiers() throws SQLException {
-        try {
-            return databaseMetaData.supportsMixedCaseIdentifiers();
-        } catch (final SQLException e) {
-            handleException(e);
-            return false;
-        }
+        return getB(databaseMetaData::supportsMixedCaseIdentifiers);
     }
 
     @Override
     public boolean supportsMixedCaseQuotedIdentifiers() throws SQLException {
-        try {
-            return databaseMetaData.supportsMixedCaseQuotedIdentifiers();
-        } catch (final SQLException e) {
-            handleException(e);
-            return false;
-        }
+        return getB(databaseMetaData::supportsMixedCaseQuotedIdentifiers);
     }
 
     @Override
     public boolean supportsMultipleOpenResults() throws SQLException {
-        try {
-            return databaseMetaData.supportsMultipleOpenResults();
-        } catch (final SQLException e) {
-            handleException(e);
-            return false;
-        }
+        return getB(databaseMetaData::supportsMultipleOpenResults);
     }
 
     @Override
     public boolean supportsMultipleResultSets() throws SQLException {
-        try {
-            return databaseMetaData.supportsMultipleResultSets();
-        } catch (final SQLException e) {
-            handleException(e);
-            return false;
-        }
+        return getB(databaseMetaData::supportsMultipleResultSets);
     }
 
     @Override
     public boolean supportsMultipleTransactions() throws SQLException {
-        try {
-            return databaseMetaData.supportsMultipleTransactions();
-        } catch (final SQLException e) {
-            handleException(e);
-            return false;
-        }
+        return getB(databaseMetaData::supportsMultipleTransactions);
     }
 
     @Override
     public boolean supportsNamedParameters() throws SQLException {
-        try {
-            return databaseMetaData.supportsNamedParameters();
-        } catch (final SQLException e) {
-            handleException(e);
-            return false;
-        }
+        return getB(databaseMetaData::supportsNamedParameters);
     }
 
     @Override
     public boolean supportsNonNullableColumns() throws SQLException {
-        try {
-            return databaseMetaData.supportsNonNullableColumns();
-        } catch (final SQLException e) {
-            handleException(e);
-            return false;
-        }
+        return getB(databaseMetaData::supportsNonNullableColumns);
     }
 
     @Override
     public boolean supportsOpenCursorsAcrossCommit() throws SQLException {
-        try {
-            return databaseMetaData.supportsOpenCursorsAcrossCommit();
-        } catch (final SQLException e) {
-            handleException(e);
-            return false;
-        }
+        return getB(databaseMetaData::supportsOpenCursorsAcrossCommit);
     }
 
     @Override
     public boolean supportsOpenCursorsAcrossRollback() throws SQLException {
-        try {
-            return databaseMetaData.supportsOpenCursorsAcrossRollback();
-        } catch (final SQLException e) {
-            handleException(e);
-            return false;
-        }
+        return getB(databaseMetaData::supportsOpenCursorsAcrossRollback);
     }
 
     @Override
     public boolean supportsOpenStatementsAcrossCommit() throws SQLException {
-        try {
-            return databaseMetaData.supportsOpenStatementsAcrossCommit();
-        } catch (final SQLException e) {
-            handleException(e);
-            return false;
-        }
+        return getB(databaseMetaData::supportsOpenStatementsAcrossCommit);
     }
 
     @Override
     public boolean supportsOpenStatementsAcrossRollback() throws SQLException {
-        try {
-            return databaseMetaData.supportsOpenStatementsAcrossRollback();
-        } catch (final SQLException e) {
-            handleException(e);
-            return false;
-        }
+        return getB(databaseMetaData::supportsOpenStatementsAcrossRollback);
     }
 
     @Override
     public boolean supportsOrderByUnrelated() throws SQLException {
-        try {
-            return databaseMetaData.supportsOrderByUnrelated();
-        } catch (final SQLException e) {
-            handleException(e);
-            return false;
-        }
+        return getB(databaseMetaData::supportsOrderByUnrelated);
     }
 
     @Override
     public boolean supportsOuterJoins() throws SQLException {
-        try {
-            return databaseMetaData.supportsOuterJoins();
-        } catch (final SQLException e) {
-            handleException(e);
-            return false;
-        }
+        return getB(databaseMetaData::supportsOuterJoins);
     }
 
     @Override
     public boolean supportsPositionedDelete() throws SQLException {
-        try {
-            return databaseMetaData.supportsPositionedDelete();
-        } catch (final SQLException e) {
-            handleException(e);
-            return false;
-        }
+        return getB(databaseMetaData::supportsPositionedDelete);
     }
 
     @Override
     public boolean supportsPositionedUpdate() throws SQLException {
-        try {
-            return databaseMetaData.supportsPositionedUpdate();
-        } catch (final SQLException e) {
-            handleException(e);
-            return false;
-        }
+        return getB(databaseMetaData::supportsPositionedUpdate);
     }
 
     /**
@@ -1667,232 +911,117 @@ public class DelegatingDatabaseMetaData implements DatabaseMetaData {
      */
     @Override
     public boolean supportsRefCursors() throws SQLException {
-        try {
-            return databaseMetaData.supportsRefCursors();
-        } catch (final SQLException e) {
-            handleException(e);
-            return false;
-        }
+        return getB(databaseMetaData::supportsRefCursors);
     }
 
     @Override
     public boolean supportsResultSetConcurrency(final int type, final int concurrency) throws SQLException {
-        try {
-            return databaseMetaData.supportsResultSetConcurrency(type, concurrency);
-        } catch (final SQLException e) {
-            handleException(e);
-            return false;
-        }
+        return getB(() -> databaseMetaData.supportsResultSetConcurrency(type, concurrency));
     }
 
     @Override
     public boolean supportsResultSetHoldability(final int holdability) throws SQLException {
-        try {
-            return databaseMetaData.supportsResultSetHoldability(holdability);
-        } catch (final SQLException e) {
-            handleException(e);
-            return false;
-        }
+        return getB(() -> databaseMetaData.supportsResultSetHoldability(holdability));
     }
 
     @Override
     public boolean supportsResultSetType(final int type) throws SQLException {
-        try {
-            return databaseMetaData.supportsResultSetType(type);
-        } catch (final SQLException e) {
-            handleException(e);
-            return false;
-        }
+        return getB(() -> databaseMetaData.supportsResultSetType(type));
     }
 
     @Override
     public boolean supportsSavepoints() throws SQLException {
-        try {
-            return databaseMetaData.supportsSavepoints();
-        } catch (final SQLException e) {
-            handleException(e);
-            return false;
-        }
+        return getB(databaseMetaData::supportsSavepoints);
     }
 
     @Override
     public boolean supportsSchemasInDataManipulation() throws SQLException {
-        try {
-            return databaseMetaData.supportsSchemasInDataManipulation();
-        } catch (final SQLException e) {
-            handleException(e);
-            return false;
-        }
+        return getB(databaseMetaData::supportsSchemasInDataManipulation);
     }
 
     @Override
     public boolean supportsSchemasInIndexDefinitions() throws SQLException {
-        try {
-            return databaseMetaData.supportsSchemasInIndexDefinitions();
-        } catch (final SQLException e) {
-            handleException(e);
-            return false;
-        }
+        return getB(databaseMetaData::supportsSchemasInIndexDefinitions);
     }
 
     @Override
     public boolean supportsSchemasInPrivilegeDefinitions() throws SQLException {
-        try {
-            return databaseMetaData.supportsSchemasInPrivilegeDefinitions();
-        } catch (final SQLException e) {
-            handleException(e);
-            return false;
-        }
+        return getB(databaseMetaData::supportsSchemasInPrivilegeDefinitions);
     }
 
     @Override
     public boolean supportsSchemasInProcedureCalls() throws SQLException {
-        try {
-            return databaseMetaData.supportsSchemasInProcedureCalls();
-        } catch (final SQLException e) {
-            handleException(e);
-            return false;
-        }
+        return getB(databaseMetaData::supportsSchemasInProcedureCalls);
     }
 
     @Override
     public boolean supportsSchemasInTableDefinitions() throws SQLException {
-        try {
-            return databaseMetaData.supportsSchemasInTableDefinitions();
-        } catch (final SQLException e) {
-            handleException(e);
-            return false;
-        }
+        return getB(databaseMetaData::supportsSchemasInTableDefinitions);
     }
 
     @Override
     public boolean supportsSelectForUpdate() throws SQLException {
-        try {
-            return databaseMetaData.supportsSelectForUpdate();
-        } catch (final SQLException e) {
-            handleException(e);
-            return false;
-        }
+        return getB(databaseMetaData::supportsSelectForUpdate);
     }
 
     @Override
     public boolean supportsStatementPooling() throws SQLException {
-        try {
-            return databaseMetaData.supportsStatementPooling();
-        } catch (final SQLException e) {
-            handleException(e);
-            return false;
-        }
+        return getB(databaseMetaData::supportsStatementPooling);
     }
 
     @Override
     public boolean supportsStoredFunctionsUsingCallSyntax() throws SQLException {
-        try {
-            return databaseMetaData.supportsStoredFunctionsUsingCallSyntax();
-        } catch (final SQLException e) {
-            handleException(e);
-            return false;
-        }
+        return getB(databaseMetaData::supportsStoredFunctionsUsingCallSyntax);
     }
 
     @Override
     public boolean supportsStoredProcedures() throws SQLException {
-        try {
-            return databaseMetaData.supportsStoredProcedures();
-        } catch (final SQLException e) {
-            handleException(e);
-            return false;
-        }
+        return getB(databaseMetaData::supportsStoredProcedures);
     }
 
     @Override
     public boolean supportsSubqueriesInComparisons() throws SQLException {
-        try {
-            return databaseMetaData.supportsSubqueriesInComparisons();
-        } catch (final SQLException e) {
-            handleException(e);
-            return false;
-        }
+        return getB(databaseMetaData::supportsSubqueriesInComparisons);
     }
 
     @Override
     public boolean supportsSubqueriesInExists() throws SQLException {
-        try {
-            return databaseMetaData.supportsSubqueriesInExists();
-        } catch (final SQLException e) {
-            handleException(e);
-            return false;
-        }
+        return getB(databaseMetaData::supportsSubqueriesInExists);
     }
 
     @Override
     public boolean supportsSubqueriesInIns() throws SQLException {
-        try {
-            return databaseMetaData.supportsSubqueriesInIns();
-        } catch (final SQLException e) {
-            handleException(e);
-            return false;
-        }
+        return getB(databaseMetaData::supportsSubqueriesInIns);
     }
 
     @Override
     public boolean supportsSubqueriesInQuantifieds() throws SQLException {
-        try {
-            return databaseMetaData.supportsSubqueriesInQuantifieds();
-        } catch (final SQLException e) {
-            handleException(e);
-            return false;
-        }
+        return getB(databaseMetaData::supportsSubqueriesInQuantifieds);
     }
 
     @Override
     public boolean supportsTableCorrelationNames() throws SQLException {
-        try {
-            return databaseMetaData.supportsTableCorrelationNames();
-        } catch (final SQLException e) {
-            handleException(e);
-            return false;
-        }
+        return getB(databaseMetaData::supportsTableCorrelationNames);
     }
 
     @Override
     public boolean supportsTransactionIsolationLevel(final int level) throws SQLException {
-        try {
-            return databaseMetaData.supportsTransactionIsolationLevel(level);
-        } catch (final SQLException e) {
-            handleException(e);
-            return false;
-        }
+        return getB(() -> databaseMetaData.supportsTransactionIsolationLevel(level));
     }
 
     @Override
     public boolean supportsTransactions() throws SQLException {
-        try {
-            return databaseMetaData.supportsTransactions();
-        } catch (final SQLException e) {
-            handleException(e);
-            return false;
-        }
+        return getB(databaseMetaData::supportsTransactions);
     }
 
     @Override
     public boolean supportsUnion() throws SQLException {
-        try {
-            return databaseMetaData.supportsUnion();
-        } catch (final SQLException e) {
-            handleException(e);
-            return false;
-        }
+        return getB(databaseMetaData::supportsUnion);
     }
 
     @Override
     public boolean supportsUnionAll() throws SQLException {
-        try {
-            return databaseMetaData.supportsUnionAll();
-        } catch (final SQLException e) {
-            handleException(e);
-            return false;
-        }
+        return getB(databaseMetaData::supportsUnionAll);
     }
 
     @Override
@@ -1908,31 +1037,16 @@ public class DelegatingDatabaseMetaData implements DatabaseMetaData {
 
     @Override
     public boolean updatesAreDetected(final int type) throws SQLException {
-        try {
-            return databaseMetaData.updatesAreDetected(type);
-        } catch (final SQLException e) {
-            handleException(e);
-            return false;
-        }
+        return getB(() -> databaseMetaData.updatesAreDetected(type));
     }
 
     @Override
     public boolean usesLocalFilePerTable() throws SQLException {
-        try {
-            return databaseMetaData.usesLocalFilePerTable();
-        } catch (final SQLException e) {
-            handleException(e);
-            return false;
-        }
+        return getB(databaseMetaData::usesLocalFilePerTable);
     }
 
     @Override
     public boolean usesLocalFiles() throws SQLException {
-        try {
-            return databaseMetaData.usesLocalFiles();
-        } catch (final SQLException e) {
-            handleException(e);
-            return false;
-        }
+        return getB(databaseMetaData::usesLocalFiles);
     }
 }
diff --git a/java/org/apache/tomcat/dbcp/dbcp2/DelegatingPreparedStatement.java b/java/org/apache/tomcat/dbcp/dbcp2/DelegatingPreparedStatement.java
index 8c0cc3200a..0864bdad89 100644
--- a/java/org/apache/tomcat/dbcp/dbcp2/DelegatingPreparedStatement.java
+++ b/java/org/apache/tomcat/dbcp/dbcp2/DelegatingPreparedStatement.java
@@ -32,12 +32,12 @@ import java.sql.RowId;
 import java.sql.SQLException;
 import java.sql.SQLType;
 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;
+import java.util.Objects;
 
 /**
  * A base delegating implementation of {@link PreparedStatement}.
@@ -712,7 +712,6 @@ public class DelegatingPreparedStatement extends DelegatingStatement implements
      */
     @Override
     public synchronized String toString() {
-        final Statement statement = getDelegate();
-        return statement == null ? "NULL" : statement.toString();
+        return Objects.toString(getDelegate(), "NULL");
     }
 }
diff --git a/java/org/apache/tomcat/dbcp/dbcp2/DelegatingStatement.java b/java/org/apache/tomcat/dbcp/dbcp2/DelegatingStatement.java
index abfedf3590..faf06e18be 100644
--- a/java/org/apache/tomcat/dbcp/dbcp2/DelegatingStatement.java
+++ b/java/org/apache/tomcat/dbcp/dbcp2/DelegatingStatement.java
@@ -23,6 +23,7 @@ import java.sql.SQLWarning;
 import java.sql.Statement;
 import java.util.ArrayList;
 import java.util.List;
+import java.util.Objects;
 
 /**
  * A base delegating implementation of {@link Statement}.
@@ -93,7 +94,7 @@ public class DelegatingStatement extends AbandonedTrace implements Statement {
 
     protected void checkOpen() throws SQLException {
         if (isClosed()) {
-            throw new SQLException(this.getClass().getName() + " with address: \"" + this.toString() + "\" is closed.");
+            throw new SQLException(this.getClass().getName() + " with address: \"" + toString() + "\" is closed.");
         }
     }
 
@@ -795,7 +796,7 @@ public class DelegatingStatement extends AbandonedTrace implements Statement {
      */
     @Override
     public synchronized String toString() {
-        return statement == null ? "NULL" : statement.toString();
+        return Objects.toString(statement, "NULL");
     }
 
     @Override
diff --git a/java/org/apache/tomcat/dbcp/dbcp2/DriverConnectionFactory.java b/java/org/apache/tomcat/dbcp/dbcp2/DriverConnectionFactory.java
index a5752919b2..b14cc26958 100644
--- a/java/org/apache/tomcat/dbcp/dbcp2/DriverConnectionFactory.java
+++ b/java/org/apache/tomcat/dbcp/dbcp2/DriverConnectionFactory.java
@@ -53,6 +53,8 @@ public class DriverConnectionFactory implements ConnectionFactory {
     }
 
     /**
+     * Gets the connection String.
+     *
      * @return The connection String.
      * @since 2.6.0
      */
@@ -61,7 +63,9 @@ public class DriverConnectionFactory implements ConnectionFactory {
     }
 
     /**
-     * @return The Driver.
+     * Gets the JDBC Driver.
+     *
+     * @return The JDBC Driver.
      * @since 2.6.0
      */
     public Driver getDriver() {
@@ -69,7 +73,9 @@ public class DriverConnectionFactory implements ConnectionFactory {
     }
 
     /**
-     * @return The Properties.
+     * Gets the properties.
+     *
+     * @return The properties.
      * @since 2.6.0
      */
     public Properties getProperties() {
diff --git a/java/org/apache/tomcat/dbcp/dbcp2/PoolableCallableStatement.java b/java/org/apache/tomcat/dbcp/dbcp2/PoolableCallableStatement.java
index 0bb84c0347..d2a2161362 100644
--- a/java/org/apache/tomcat/dbcp/dbcp2/PoolableCallableStatement.java
+++ b/java/org/apache/tomcat/dbcp/dbcp2/PoolableCallableStatement.java
@@ -76,7 +76,7 @@ public class PoolableCallableStatement extends DelegatingCallableStatement {
     @Override
     public void activate() throws SQLException {
         setClosedInternal(false);
-        AbandonedTrace.add(getConnectionInternal(), this);
+        add(getConnectionInternal(), this);
         super.activate();
     }
 
diff --git a/java/org/apache/tomcat/dbcp/dbcp2/PoolableConnection.java b/java/org/apache/tomcat/dbcp/dbcp2/PoolableConnection.java
index 0459d176db..532091d957 100644
--- a/java/org/apache/tomcat/dbcp/dbcp2/PoolableConnection.java
+++ b/java/org/apache/tomcat/dbcp/dbcp2/PoolableConnection.java
@@ -71,11 +71,19 @@ public class PoolableConnection extends DelegatingConnection<Connection> impleme
     private boolean fatalSqlExceptionThrown;
 
     /**
-     * SQL_STATE codes considered to signal fatal conditions. Overrides the defaults in
+     * SQL State codes considered to signal fatal conditions. Overrides the defaults in
      * {@link Utils#getDisconnectionSqlCodes()} (plus anything starting with {@link Utils#DISCONNECTION_SQL_CODE_PREFIX}).
      */
     private final Collection<String> disconnectionSqlCodes;
 
+    /**
+     * A collection of SQL State codes that are not considered fatal disconnection codes.
+     *
+     * @since 2.13.0
+     */
+    private final Collection<String> disconnectionIgnoreSqlCodes;
+
+
     /** Whether or not to fast fail validation after fatal connection errors */
     private final boolean fastFailValidation;
 
@@ -102,18 +110,43 @@ public class PoolableConnection extends DelegatingConnection<Connection> impleme
      * @param jmxObjectName
      *            JMX name
      * @param disconnectSqlCodes
-     *            SQL_STATE codes considered fatal disconnection errors
+     *            SQL State codes considered fatal disconnection errors
      * @param fastFailValidation
      *            true means fatal disconnection errors cause subsequent validations to fail immediately (no attempt to
      *            run query or isValid)
      */
+    public PoolableConnection(final Connection conn, final ObjectPool<PoolableConnection> pool,
+                              final ObjectName jmxObjectName, final Collection<String> disconnectSqlCodes,
+                              final boolean fastFailValidation) {
+        this(conn, pool, jmxObjectName, disconnectSqlCodes, null, fastFailValidation);
+    }
+
+    /**
+     * Creates a new {@link PoolableConnection} instance.
+     *
+     * @param conn
+     *            my underlying connection
+     * @param pool
+     *            the pool to which I should return when closed
+     * @param jmxObjectName
+     *            JMX name
+     * @param disconnectSqlCodes
+     *            SQL State codes considered fatal disconnection errors
+     * @param disconnectionIgnoreSqlCodes
+     *            SQL State codes that should be ignored when determining fatal disconnection errors
+     * @param fastFailValidation
+     *            true means fatal disconnection errors cause subsequent validations to fail immediately (no attempt to
+     *            run query or isValid)
+     * @since 2.13.0
+     */
     public PoolableConnection(final Connection conn, final ObjectPool<PoolableConnection> pool,
             final ObjectName jmxObjectName, final Collection<String> disconnectSqlCodes,
-            final boolean fastFailValidation) {
+            final Collection<String> disconnectionIgnoreSqlCodes, final boolean fastFailValidation) {
         super(conn);
         this.pool = pool;
         this.jmxObjectName = ObjectNameWrapper.wrap(jmxObjectName);
         this.disconnectionSqlCodes = disconnectSqlCodes;
+        this.disconnectionIgnoreSqlCodes = disconnectionIgnoreSqlCodes;
         this.fastFailValidation = fastFailValidation;
 
         if (jmxObjectName != null) {
@@ -139,7 +172,7 @@ public class PoolableConnection extends DelegatingConnection<Connection> impleme
     }
 
     /**
-     * Returns me to my pool.
+     * Returns this instance to my containing pool.
      */
     @Override
     public void close() throws SQLException {
@@ -212,7 +245,7 @@ public class PoolableConnection extends DelegatingConnection<Connection> impleme
     }
 
     /**
-     * Expose the {@link #toString()} method via a bean getter, so it can be read as a property via JMX.
+     * Gets the value of the {@link #toString()} method via a bean getter, so it can be read as a property via JMX.
      */
     @Override
     public String getToString() {
@@ -255,7 +288,8 @@ public class PoolableConnection extends DelegatingConnection<Connection> impleme
      * 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#getDisconnectionSqlCodes()} and in this case anything starting with #{link
-     * Utils.DISCONNECTION_SQL_CODE_PREFIX} is considered a disconnection.
+     * Utils.DISCONNECTION_SQL_CODE_PREFIX} is considered a disconnection. Additionally, any SQL state
+     * listed in {@link #disconnectionIgnoreSqlCodes} will be ignored and not treated as fatal.
      * </p>
      *
      * @param e SQLException to be examined
@@ -265,8 +299,11 @@ public class PoolableConnection extends DelegatingConnection<Connection> impleme
         boolean fatalException = false;
         final String sqlState = e.getSQLState();
         if (sqlState != null) {
+            if (disconnectionIgnoreSqlCodes != null && disconnectionIgnoreSqlCodes.contains(sqlState)) {
+                return false;
+            }
             fatalException = disconnectionSqlCodes == null
-                ? sqlState.startsWith(Utils.DISCONNECTION_SQL_CODE_PREFIX) || Utils.getDisconnectionSqlCodes().contains(sqlState)
+                ? sqlState.startsWith(Utils.DISCONNECTION_SQL_CODE_PREFIX) || Utils.isDisconnectionSqlCode(sqlState)
                 : disconnectionSqlCodes.contains(sqlState);
         }
         return fatalException;
@@ -317,7 +354,7 @@ public class PoolableConnection extends DelegatingConnection<Connection> impleme
     }
 
     /**
-     * Actually close my underlying {@link Connection}.
+     * Closes the underlying {@link Connection}.
      */
     @Override
     public void reallyClose() throws SQLException {
diff --git a/java/org/apache/tomcat/dbcp/dbcp2/PoolableConnectionFactory.java b/java/org/apache/tomcat/dbcp/dbcp2/PoolableConnectionFactory.java
index 4ebede76c5..35bda67bee 100644
--- a/java/org/apache/tomcat/dbcp/dbcp2/PoolableConnectionFactory.java
+++ b/java/org/apache/tomcat/dbcp/dbcp2/PoolableConnectionFactory.java
@@ -64,6 +64,8 @@ public class PoolableConnectionFactory implements PooledObjectFactory<PoolableCo
 
     private Collection<String> disconnectionSqlCodes;
 
+    private Collection<String> disconnectionIgnoreSqlCodes;
+
     private boolean fastFailValidation = true;
 
     private volatile ObjectPool<PoolableConnection> pool;
@@ -147,14 +149,14 @@ public class PoolableConnectionFactory implements PooledObjectFactory<PoolableCo
     @Override
     public void destroyObject(final PooledObject<PoolableConnection> p, final DestroyMode mode) throws SQLException {
         if (mode == DestroyMode.ABANDONED) {
-            p.getObject().getInnermostDelegate().abort(Runnable::run);
+            Jdbc41Bridge.abort(p.getObject().getInnermostDelegate(), Runnable::run);
         } else {
             p.getObject().reallyClose();
         }
     }
 
     /**
-     * Gets the cache state.
+     * Gets the cache state to propagate in {@link #makeObject()}.
      *
      * @return The cache state.
      * @since 2.6.0.
@@ -173,11 +175,18 @@ public class PoolableConnectionFactory implements PooledObjectFactory<PoolableCo
         return connectionFactory;
     }
 
+    /**
+     * Gets how many connections were created in {@link #makeObject()}.
+     *
+     * @return the connection count.
+     */
     protected AtomicLong getConnectionIndex() {
         return connectionIndex;
     }
 
     /**
+     * Gets the collection of initialization SQL statements.
+     *
      * @return The collection of initialization SQL statements.
      * @since 2.6.0
      */
@@ -186,7 +195,9 @@ public class PoolableConnectionFactory implements PooledObjectFactory<PoolableCo
     }
 
     /**
-     * @return The data source JMX ObjectName
+     * Gets data source JMX ObjectName.
+     *
+     * @return The data source JMX ObjectName.
      * @since 2.6.0.
      */
     public ObjectName getDataSourceJmxName() {
@@ -194,7 +205,9 @@ public class PoolableConnectionFactory implements PooledObjectFactory<PoolableCo
     }
 
     /**
-     * @return The data source JMS ObjectName.
+     * Gets the data source JMX ObjectName.
+     *
+     * @return The data source JMX ObjectName.
      * @since 2.6.0
      */
     public ObjectName getDataSourceJmxObjectName() {
@@ -202,7 +215,9 @@ public class PoolableConnectionFactory implements PooledObjectFactory<PoolableCo
     }
 
     /**
-     * @return Default auto-commit value.
+     * Gets the Default auto-commit value.
+     *
+     * @return The default auto-commit value.
      * @since 2.6.0
      */
     public Boolean getDefaultAutoCommit() {
@@ -210,7 +225,9 @@ public class PoolableConnectionFactory implements PooledObjectFactory<PoolableCo
     }
 
     /**
-     * @return Default catalog.
+     * Gets the default catalog.
+     *
+     * @return The default catalog.
      * @since 2.6.0
      */
     public String getDefaultCatalog() {
@@ -218,7 +235,9 @@ public class PoolableConnectionFactory implements PooledObjectFactory<PoolableCo
     }
 
     /**
-     * @return Default query timeout in seconds.
+     * Gets the default query timeout in seconds.
+     *
+     * @return The default query timeout in seconds.
      * @deprecated Use {@link #getDefaultQueryTimeoutDuration()}.
      */
     @Deprecated
@@ -229,7 +248,7 @@ public class PoolableConnectionFactory implements PooledObjectFactory<PoolableCo
     /**
      * Gets the default query timeout Duration.
      *
-     * @return Default query timeout Duration.
+     * @return The default query timeout Duration.
      * @since 2.10.0
      */
     public Duration getDefaultQueryTimeoutDuration() {
@@ -237,7 +256,9 @@ public class PoolableConnectionFactory implements PooledObjectFactory<PoolableCo
     }
 
     /**
-     * @return Default query timeout in seconds.
+     * Gets the default query timeout in seconds.
+     *
+     * @return The default query timeout in seconds.
      * @since 2.6.0
      * @deprecated Use {@link #getDefaultQueryTimeoutDuration()}.
      */
@@ -247,7 +268,9 @@ public class PoolableConnectionFactory implements PooledObjectFactory<PoolableCo
     }
 
     /**
-     * @return Default read-only-value.
+     * Gets the default read-only-value.
+     *
+     * @return The default read-only-value.
      * @since 2.6.0
      */
     public Boolean getDefaultReadOnly() {
@@ -255,7 +278,9 @@ public class PoolableConnectionFactory implements PooledObjectFactory<PoolableCo
     }
 
     /**
-     * @return Default schema.
+     * Gets the default schema.
+     *
+     * @return The default schema.
      * @since 2.6.0
      */
     public String getDefaultSchema() {
@@ -263,7 +288,9 @@ public class PoolableConnectionFactory implements PooledObjectFactory<PoolableCo
     }
 
     /**
-     * @return Default transaction isolation.
+     * Gets the default transaction isolation.
+     *
+     * @return The default transaction isolation.
      * @since 2.6.0
      */
     public int getDefaultTransactionIsolation() {
@@ -271,11 +298,26 @@ public class PoolableConnectionFactory implements PooledObjectFactory<PoolableCo
     }
 
     /**
-     * SQL_STATE codes considered to signal fatal conditions.
+     * Gets the collection of SQL State codes that are not considered fatal disconnection codes.
+     * <p>
+     * This method returns the collection of SQL State codes that have been set to be ignored when
+     * determining if a {@link SQLException} signals a disconnection. These codes are excluded from
+     * being treated as fatal even if they match the typical disconnection criteria.
+     * </p>
+     *
+     * @return a {@link Collection} of SQL State codes that should be ignored for disconnection checks.
+     * @since 2.13.0
+     */
+    public Collection<String> getDisconnectionIgnoreSqlCodes() {
+        return disconnectionIgnoreSqlCodes;
+    }
+
+    /**
+     * Gets SQL State codes considered to signal fatal conditions.
      * <p>
      * 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,
+     * {@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
      * validation query).
      * </p>
@@ -283,7 +325,7 @@ public class PoolableConnectionFactory implements PooledObjectFactory<PoolableCo
      * If {@link #isFastFailValidation()} is {@code false} setting this property has no effect.
      * </p>
      *
-     * @return SQL_STATE codes overriding defaults
+     * @return SQL State codes overriding defaults
      * @since 2.1
      */
     public Collection<String> getDisconnectionSqlCodes() {
@@ -310,9 +352,15 @@ public class PoolableConnectionFactory implements PooledObjectFactory<PoolableCo
         return maxConnDuration.toMillis();
     }
 
+    /**
+     * Gets the maximum number of open prepared statements.
+     *
+     * @return The maximum number of open prepared statements.
+     */
     protected int getMaxOpenPreparedStatements() {
         return maxOpenPreparedStatements;
     }
+
     /**
      * Returns the {@link ObjectPool} in which {@link Connection}s are pooled.
      *
@@ -321,14 +369,20 @@ public class PoolableConnectionFactory implements PooledObjectFactory<PoolableCo
     public synchronized ObjectPool<PoolableConnection> getPool() {
         return pool;
     }
+
     /**
+     * Tests whether to pool statements.
+     *
      * @return Whether to pool statements.
      * @since 2.6.0.
      */
     public boolean getPoolStatements() {
         return poolStatements;
     }
+
     /**
+     * Gets the validation query.
+     *
      * @return Validation query.
      * @since 2.6.0
      */
@@ -358,6 +412,13 @@ public class PoolableConnectionFactory implements PooledObjectFactory<PoolableCo
         return (int) validationQueryTimeoutDuration.getSeconds();
     }
 
+    /**
+     * Initializes the given connection with the collection of SQL statements set in {@link #setConnectionInitSql(Collection)}.
+     *
+     * @param conn the connection to initialize.
+     * @throws SQLException if a database access error occurs or this method is called on a closed connection.
+     * @see #setConnectionInitSql(Collection)
+     */
     protected void initializeConnection(final Connection conn) throws SQLException {
         final Collection<String> sqls = connectionInitSqls;
         if (conn.isClosed()) {
@@ -373,7 +434,9 @@ public class PoolableConnectionFactory implements PooledObjectFactory<PoolableCo
     }
 
     /**
-     * @return Whether to auto-commit on return.
+     * Tests whether to set auto-commit on {@link #passivateObject(PooledObject)}.
+     *
+     * @return Whether to set auto-commit on {@link #passivateObject(PooledObject)}.
      * @since 2.6.0
      */
     public boolean isAutoCommitOnReturn() {
@@ -381,7 +444,9 @@ public class PoolableConnectionFactory implements PooledObjectFactory<PoolableCo
     }
 
     /**
-     * @return Whether to auto-commit on return.
+     * Tests whether to set auto-commit on {@link #passivateObject(PooledObject)}.
+     *
+     * @return Whether to set auto-commit on {@link #passivateObject(PooledObject)}.
      * @deprecated Use {@link #isAutoCommitOnReturn()}.
      */
     @Deprecated
@@ -391,7 +456,7 @@ public class PoolableConnectionFactory implements PooledObjectFactory<PoolableCo
 
     /**
      * True means that validation will fail immediately for connections that have previously thrown SQLExceptions with
-     * SQL_STATE indicating fatal disconnection errors.
+     * SQL State indicating fatal disconnection errors.
      *
      * @return true if connections created by this factory will fast fail validation.
      * @see #setDisconnectionSqlCodes(Collection)
@@ -403,6 +468,8 @@ public class PoolableConnectionFactory implements PooledObjectFactory<PoolableCo
     }
 
     /**
+     * Tests whether to rollback on return.
+     *
      * @return Whether to rollback on return.
      */
     public boolean isRollbackOnReturn() {
@@ -464,7 +531,8 @@ public class PoolableConnectionFactory implements PooledObjectFactory<PoolableCo
             }
         }
 
-        final PoolableConnection pc = new PoolableConnection(conn, pool, connJmxName, disconnectionSqlCodes, fastFailValidation);
+        final PoolableConnection pc = new PoolableConnection(conn, pool, connJmxName,
+                disconnectionSqlCodes, disconnectionIgnoreSqlCodes, fastFailValidation);
         pc.setCacheState(cacheState);
 
         return new DefaultPooledObject<>(pc);
@@ -500,10 +568,20 @@ public class PoolableConnectionFactory implements PooledObjectFactory<PoolableCo
         conn.passivate();
     }
 
+    /**
+     * Sets whether to set auto-commit on {@link #passivateObject(PooledObject)}.
+     *
+     * @param autoCommitOnReturn whether to set auto-commit.
+     */
     public void setAutoCommitOnReturn(final boolean autoCommitOnReturn) {
         this.autoCommitOnReturn = autoCommitOnReturn;
     }
 
+    /**
+     * Sets the cache state to propagate in {@link #makeObject()}.
+     *
+     * @param cacheState the cache state to propagate.
+     */
     public void setCacheState(final boolean cacheState) {
         this.cacheState = cacheState;
     }
@@ -602,18 +680,37 @@ public class PoolableConnectionFactory implements PooledObjectFactory<PoolableCo
     }
 
     /**
+     * Sets the disconnection SQL codes to ignore.
+     *
+     * @param disconnectionIgnoreSqlCodes
+     *            The collection of SQL State codes to be ignored.
+     * @see #getDisconnectionIgnoreSqlCodes()
+     * @throws IllegalArgumentException if any SQL state codes overlap with those in {@link #disconnectionSqlCodes}.
+     * @since 2.13.0
+     */
+    public void setDisconnectionIgnoreSqlCodes(final Collection<String> disconnectionIgnoreSqlCodes) {
+        Utils.checkSqlCodes(disconnectionIgnoreSqlCodes, this.disconnectionSqlCodes);
+        this.disconnectionIgnoreSqlCodes = disconnectionIgnoreSqlCodes;
+    }
+
+    /**
+     * Sets the disconnection SQL codes.
+     *
      * @param disconnectionSqlCodes
      *            The disconnection SQL codes.
      * @see #getDisconnectionSqlCodes()
      * @since 2.1
+     * @throws IllegalArgumentException if any SQL state codes overlap with those in {@link #disconnectionIgnoreSqlCodes}.
      */
     public void setDisconnectionSqlCodes(final Collection<String> disconnectionSqlCodes) {
+        Utils.checkSqlCodes(disconnectionSqlCodes, this.disconnectionIgnoreSqlCodes);
         this.disconnectionSqlCodes = disconnectionSqlCodes;
     }
 
     /**
-     * @param autoCommitOnReturn Whether to auto-commit on return.
-     * @deprecated Use {@link #setAutoCommitOnReturn(boolean)}.
+     * Sets whether to set auto-commit on {@link #passivateObject(PooledObject)}.
+     *
+     * @param autoCommitOnReturn whether to set auto-commit.
      */
     @Deprecated
     public void setEnableAutoCommitOnReturn(final boolean autoCommitOnReturn) {
@@ -690,10 +787,20 @@ public class PoolableConnectionFactory implements PooledObjectFactory<PoolableCo
         this.pool = pool;
     }
 
+    /**
+     * Sets whether to pool statements.
+     *
+     * @param poolStatements whether to pool statements.
+     */
     public void setPoolStatements(final boolean poolStatements) {
         this.poolStatements = poolStatements;
     }
 
+    /**
+     * Sets whether to rollback on return.
+     *
+     * @param rollbackOnReturn whether to rollback on return.
+     */
     public void setRollbackOnReturn(final boolean rollbackOnReturn) {
         this.rollbackOnReturn = rollbackOnReturn;
     }
diff --git a/java/org/apache/tomcat/dbcp/dbcp2/PoolableConnectionMXBean.java b/java/org/apache/tomcat/dbcp/dbcp2/PoolableConnectionMXBean.java
index f72c565607..e3b4bf8bf5 100644
--- a/java/org/apache/tomcat/dbcp/dbcp2/PoolableConnectionMXBean.java
+++ b/java/org/apache/tomcat/dbcp/dbcp2/PoolableConnectionMXBean.java
@@ -16,6 +16,7 @@
  */
 package org.apache.tomcat.dbcp.dbcp2;
 
+import java.sql.Connection;
 import java.sql.SQLException;
 
 /**
@@ -25,43 +26,166 @@ import java.sql.SQLException;
  */
 public interface PoolableConnectionMXBean {
 
+    /**
+     * Clears the cached state. Call when you know that the underlying connection may have been accessed directly.
+     */
     void clearCachedState();
 
+    /**
+     * See {@link Connection#clearWarnings()}.
+     *
+     * @throws SQLException See {@link Connection#clearWarnings()}.
+     */
     void clearWarnings() throws SQLException;
 
+    /**
+     * Returns this instance to my containing pool.
+     *
+     * @throws SQLException Throw if this instance cannot be returned.
+     */
     void close() throws SQLException;
 
+    /**
+     * See {@link Connection#getAutoCommit()}.
+     *
+     * @return See {@link Connection#getAutoCommit()}.
+     * @throws SQLException See {@link Connection#getAutoCommit()}.
+     */
     boolean getAutoCommit() throws SQLException;
 
+    /**
+     * Gets whether to cache properties. The cached properties are:
+     * <ul>
+     * <li>auto-commit</li>
+     * <li>catalog</li>
+     * <li>schema</li>
+     * <li>read-only</li>
+     * </ul>
+     *
+     * @return The value for the state caching flag.
+     */
     boolean getCacheState();
 
+    /**
+     * See {@link Connection#getCatalog()}.
+     *
+     * @return See {@link Connection#getCatalog()}.
+     * @throws SQLException See {@link Connection#getCatalog()}.
+     */
     String getCatalog() throws SQLException;
 
+    /**
+     * See {@link Connection#getHoldability()}.
+     *
+     * @return See {@link Connection#getHoldability()}.
+     * @throws SQLException See {@link Connection#getHoldability()}.
+     */
     int getHoldability() throws SQLException;
 
+    /**
+     * See {@link Connection#getSchema()}.
+     *
+     * @return See {@link Connection#getSchema()}.
+     * @throws SQLException See {@link Connection#getSchema()}.
+     */
     String getSchema() throws SQLException;
 
+    /**
+     * Gets the value of the {@link Object#toString()} method via a bean getter, so it can be read as a property via JMX.
+     *
+     * @return the value of the {@link Object#toString()}.
+     */
     String getToString();
 
+    /**
+     * See {@link Connection#getTransactionIsolation()}.
+     *
+     * @return See {@link Connection#getTransactionIsolation()}.
+     * @throws SQLException See {@link Connection#getTransactionIsolation()}.
+     */
     int getTransactionIsolation() throws SQLException;
 
+    /**
+     * See {@link Connection#isClosed()}.
+     *
+     * @return See {@link Connection#isClosed()}.
+     * @throws SQLException See {@link Connection#isClosed()}.
+     */
     boolean isClosed() throws SQLException;
 
+    /**
+     * See {@link Connection#isReadOnly()}.
+     *
+     * @return See {@link Connection#isReadOnly()}.
+     * @throws SQLException See {@link Connection#isReadOnly()}.
+     */
     boolean isReadOnly() throws SQLException;
 
+    /**
+     * Closes the underlying {@link Connection}.
+     *
+     * @throws SQLException Thrown if the connection can be closed.
+     */
     void reallyClose() throws SQLException;
 
+    /**
+     * See {@link Connection#setAutoCommit(boolean)}.
+     *
+     * @param autoCommit See {@link Connection#setAutoCommit(boolean)}.
+     * @throws SQLException See {@link Connection#setAutoCommit(boolean)}.
+     */
     void setAutoCommit(boolean autoCommit) throws SQLException;
 
+    /**
+     * Sets whether to cache properties. The cached properties are:
+     * <ul>
+     * <li>auto-commit</li>
+     * <li>catalog</li>
+     * <li>schema</li>
+     * <li>read-only</li>
+     * </ul>
+     *
+     * @param cacheState The new value for the state caching flag
+     */
     void setCacheState(boolean cacheState);
 
+    /**
+     * See {@link Connection#setCatalog(String)}.
+     *
+     * @param catalog See {@link Connection#setCatalog(String)}.
+     * @throws SQLException See {@link Connection#setCatalog(String)}.
+     */
     void setCatalog(String catalog) throws SQLException;
 
+    /**
+     * See {@link Connection#setHoldability(int)}.
+     *
+     * @param holdability {@link Connection#setHoldability(int)}.
+     * @throws SQLException See {@link Connection#setHoldability(int)}.
+     */
     void setHoldability(int holdability) throws SQLException;
 
+    /**
+     * See {@link Connection#setReadOnly(boolean)}.
+     *
+     * @param readOnly See {@link Connection#setReadOnly(boolean)}.
+     * @throws SQLException See {@link Connection#setReadOnly(boolean)}.
+     */
     void setReadOnly(boolean readOnly) throws SQLException;
 
+    /**
+     * See {@link Connection#setSchema(String)}.
+     *
+     * @param schema See {@link Connection#setSchema(String)}.
+     * @throws SQLException See {@link Connection#setSchema(String)}.
+     */
     void setSchema(String schema) throws SQLException;
 
+    /**
+     * See {@link Connection#setTransactionIsolation(int)}.
+     *
+     * @param level See {@link Connection#setTransactionIsolation(int)}.
+     * @throws SQLException See {@link Connection#setTransactionIsolation(int)}.
+     */
     void setTransactionIsolation(int level) throws SQLException;
 }
diff --git a/java/org/apache/tomcat/dbcp/dbcp2/PoolablePreparedStatement.java b/java/org/apache/tomcat/dbcp/dbcp2/PoolablePreparedStatement.java
index 6da7a269f9..9ff65986cc 100644
--- a/java/org/apache/tomcat/dbcp/dbcp2/PoolablePreparedStatement.java
+++ b/java/org/apache/tomcat/dbcp/dbcp2/PoolablePreparedStatement.java
@@ -74,7 +74,7 @@ public class PoolablePreparedStatement<K> extends DelegatingPreparedStatement {
     @Override
     public void activate() throws SQLException {
         setClosedInternal(false);
-        AbandonedTrace.add(getConnectionInternal(), this);
+        add(getConnectionInternal(), this);
         super.activate();
     }
 
diff --git a/java/org/apache/tomcat/dbcp/dbcp2/PoolingConnection.java b/java/org/apache/tomcat/dbcp/dbcp2/PoolingConnection.java
index fc609e6a98..fdee6560e2 100644
--- a/java/org/apache/tomcat/dbcp/dbcp2/PoolingConnection.java
+++ b/java/org/apache/tomcat/dbcp/dbcp2/PoolingConnection.java
@@ -122,7 +122,11 @@ public class PoolingConnection extends DelegatingConnection<Connection>
             }
         } finally {
             try {
-                getDelegateInternal().close();
+                @SuppressWarnings("resource")
+                final Connection delegateInternal = getDelegateInternal();
+                if (delegateInternal != null) {
+                    delegateInternal.close();
+                }
             } finally {
                 setClosedInternal(true);
             }
diff --git a/java/org/apache/tomcat/dbcp/dbcp2/PoolingDataSource.java b/java/org/apache/tomcat/dbcp/dbcp2/PoolingDataSource.java
index 7293a115ac..b7473e4f88 100644
--- a/java/org/apache/tomcat/dbcp/dbcp2/PoolingDataSource.java
+++ b/java/org/apache/tomcat/dbcp/dbcp2/PoolingDataSource.java
@@ -99,13 +99,12 @@ public class PoolingDataSource<C extends Connection> implements DataSource, Auto
      *            the given connection pool.
      */
     public PoolingDataSource(final ObjectPool<C> pool) {
-        Objects.requireNonNull(pool, "Pool must not be null.");
+        Objects.requireNonNull(pool, "pool");
         this.pool = pool;
         // Verify that pool's factory refers back to it. If not, log a warning and try to fix.
         if (this.pool instanceof GenericObjectPool<?>) {
-            final PoolableConnectionFactory pcf = (PoolableConnectionFactory) ((GenericObjectPool<?>) this.pool)
-                    .getFactory();
-            Objects.requireNonNull(pcf, "PoolableConnectionFactory must not be null.");
+            final PoolableConnectionFactory pcf = (PoolableConnectionFactory) ((GenericObjectPool<?>) this.pool).getFactory();
+            Objects.requireNonNull(pcf, "this.pool.getFactory()");
             if (pcf.getPool() != this.pool) {
                 log.warn(Utils.getMessage("poolingDataSource.factoryConfig"));
                 @SuppressWarnings("unchecked") // PCF must have a pool of PCs
@@ -165,7 +164,6 @@ public class PoolingDataSource<C extends Connection> implements DataSource, Auto
         throw new UnsupportedOperationException();
     }
 
-    // --- DataSource methods -----------------------------------------
 
     /**
      * Throws {@link UnsupportedOperationException}.
@@ -194,6 +192,11 @@ public class PoolingDataSource<C extends Connection> implements DataSource, Auto
         throw new SQLFeatureNotSupportedException();
     }
 
+    /**
+     * Gets the backing object pool.
+     *
+     * @return the backing object pool.
+     */
     protected ObjectPool<C> getPool() {
         return pool;
     }
diff --git a/java/org/apache/tomcat/dbcp/dbcp2/PoolingDriver.java b/java/org/apache/tomcat/dbcp/dbcp2/PoolingDriver.java
index d758825198..b7bb3551cb 100644
--- a/java/org/apache/tomcat/dbcp/dbcp2/PoolingDriver.java
+++ b/java/org/apache/tomcat/dbcp/dbcp2/PoolingDriver.java
@@ -87,14 +87,24 @@ public class PoolingDriver implements Driver {
     /** The map of registered pools. */
     protected static final HashMap<String, ObjectPool<? extends Connection>> pools = new HashMap<>();
 
-    /** My URL prefix */
+    /**
+     * The Apache Commons connection string prefix {@value}.
+     */
     public static final String URL_PREFIX = "jdbc:apache:commons:dbcp:";
 
+    /**
+     * The String length of {@link #URL_PREFIX}.
+     */
     protected static final int URL_PREFIX_LEN = URL_PREFIX.length();
 
-    // version numbers
+    /**
+     * Major version number.
+     */
     protected static final int MAJOR_VERSION = 1;
 
+    /**
+     * Minor version number.
+     */
     protected static final int MINOR_VERSION = 0;
 
     /** Controls access to the underlying connection */
@@ -146,7 +156,6 @@ public class PoolingDriver implements Driver {
     public Connection connect(final String url, final Properties info) throws SQLException {
         if (acceptsURL(url)) {
             final ObjectPool<? extends Connection> pool = getConnectionPool(url.substring(URL_PREFIX_LEN));
-
             try {
                 final Connection conn = pool.borrowObject();
                 if (conn == null) {
diff --git a/java/org/apache/tomcat/dbcp/dbcp2/Utils.java b/java/org/apache/tomcat/dbcp/dbcp2/Utils.java
index 470ba74d10..fffdb187f3 100644
--- a/java/org/apache/tomcat/dbcp/dbcp2/Utils.java
+++ b/java/org/apache/tomcat/dbcp/dbcp2/Utils.java
@@ -41,7 +41,9 @@ public final class Utils {
     private static final ResourceBundle messages = ResourceBundle
         .getBundle(Utils.class.getPackage().getName() + ".LocalStrings");
 
-    /** Any SQL_STATE starting with this value is considered a fatal disconnect */
+    /**
+     * Any SQL_STATE starting with this value is considered a fatal disconnect.
+     */
     public static final String DISCONNECTION_SQL_CODE_PREFIX = "08";
 
     /**
@@ -72,6 +74,27 @@ public final class Utils {
         DISCONNECTION_SQL_CODES.add("JZ0C1"); // Sybase disconnect error
     }
 
+    /**
+     * Checks for conflicts between two collections.
+     * <p>
+     * If any overlap is found between the two provided collections, an {@link IllegalArgumentException} is thrown.
+     * </p>
+     *
+     * @param codes1 The first collection of SQL state codes.
+     * @param codes2 The second collection of SQL state codes.
+     * @throws IllegalArgumentException if any codes overlap between the two collections.
+     * @since 2.13.0
+     */
+    static void checkSqlCodes(final Collection<String> codes1, final Collection<String> codes2) {
+        if (codes1 != null && codes2 != null) {
+            final Set<String> test = new HashSet<>(codes1);
+            test.retainAll(codes2);
+            if (!test.isEmpty()) {
+                throw new IllegalArgumentException(test + " cannot be in both disconnectionSqlCodes and disconnectionIgnoreSqlCodes.");
+            }
+        }
+    }
+
     /**
      * Clones the given char[] if not null.
      *
@@ -171,7 +194,7 @@ public final class Utils {
      * <li>JZ0C0 (Sybase disconnect error)</li>
      * <li>JZ0C1 (Sybase disconnect error)</li>
      * </ul>
-     * @return SQL codes of fatal connection errors.
+     * @return A copy SQL codes of fatal connection errors.
      * @since 2.10.0
      */
     public static Set<String> getDisconnectionSqlCodes() {
@@ -204,6 +227,17 @@ public final class Utils {
         return mf.format(args, new StringBuffer(), null).toString();
     }
 
+    /**
+     * Checks if the given SQL state corresponds to a fatal connection error.
+     *
+     * @param sqlState the SQL state to check.
+     * @return true if the SQL state is a fatal connection error, false otherwise.
+     * @since 2.13.0
+     */
+    static boolean isDisconnectionSqlCode(final String sqlState) {
+        return DISCONNECTION_SQL_CODES.contains(sqlState);
+    }
+
     static boolean isEmpty(final Collection<?> collection) {
         return collection == null || collection.isEmpty();
     }
@@ -228,11 +262,18 @@ public final class Utils {
         return value == null ? null : String.valueOf(value);
     }
 
+    /**
+     * Throws a LifetimeExceededException if the given pooled object's lifetime has exceeded a maximum duration.
+     *
+     * @param p           The pooled object to test.
+     * @param maxDuration The maximum lifetime.
+     * @throws LifetimeExceededException Thrown if the given pooled object's lifetime has exceeded a maximum duration.
+     */
     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));
+                throw new LifetimeExceededException(getMessage("connectionFactory.lifetimeExceeded", lifetimeDuration, maxDuration));
             }
         }
     }
diff --git a/java/org/apache/tomcat/dbcp/dbcp2/cpdsadapter/DriverAdapterCPDS.java b/java/org/apache/tomcat/dbcp/dbcp2/cpdsadapter/DriverAdapterCPDS.java
index 3007a6cdcb..77d07da6b5 100644
--- a/java/org/apache/tomcat/dbcp/dbcp2/cpdsadapter/DriverAdapterCPDS.java
+++ b/java/org/apache/tomcat/dbcp/dbcp2/cpdsadapter/DriverAdapterCPDS.java
@@ -698,7 +698,7 @@ public class DriverAdapterCPDS implements ConnectionPoolDataSource, Referenceabl
      * <p>
      * When a negative value is supplied,
      * {@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.
+     * I.e., when the value is <em>-n</em>, roughly one <em>n</em>th of the idle objects will be tested per run.
      * </p>
      *
      * @param numTestsPerEvictionRun number of statements to examine per run
diff --git a/java/org/apache/tomcat/dbcp/dbcp2/cpdsadapter/PStmtKeyCPDS.java b/java/org/apache/tomcat/dbcp/dbcp2/cpdsadapter/PStmtKeyCPDS.java
index 68e871ea14..69e14e3871 100644
--- a/java/org/apache/tomcat/dbcp/dbcp2/cpdsadapter/PStmtKeyCPDS.java
+++ b/java/org/apache/tomcat/dbcp/dbcp2/cpdsadapter/PStmtKeyCPDS.java
@@ -16,10 +16,12 @@
  */
 package org.apache.tomcat.dbcp.dbcp2.cpdsadapter;
 
+import java.sql.PreparedStatement;
+
 import org.apache.tomcat.dbcp.dbcp2.PStmtKey;
 
 /**
- * A key uniquely identifying a {@link java.sql.PreparedStatement PreparedStatement}.
+ * A key uniquely identifying a {@link PreparedStatement}.
  *
  * @since 2.0
  * @deprecated Use {@link PStmtKey}.
diff --git a/java/org/apache/tomcat/dbcp/dbcp2/cpdsadapter/PooledConnectionImpl.java b/java/org/apache/tomcat/dbcp/dbcp2/cpdsadapter/PooledConnectionImpl.java
index 15e45c2bb0..e19b932152 100644
--- a/java/org/apache/tomcat/dbcp/dbcp2/cpdsadapter/PooledConnectionImpl.java
+++ b/java/org/apache/tomcat/dbcp/dbcp2/cpdsadapter/PooledConnectionImpl.java
@@ -44,7 +44,7 @@ import org.apache.tomcat.dbcp.pool2.PooledObject;
 import org.apache.tomcat.dbcp.pool2.impl.DefaultPooledObject;
 
 /**
- * Implementation of {@link PooledConnection} that is returned by {@link DriverAdapterCPDS}.
+ * Implements {@link PooledConnection} that is returned by {@link DriverAdapterCPDS}.
  *
  * @since 2.0
  */
@@ -119,6 +119,9 @@ final class PooledConnectionImpl
         pooledObject.getObject().activate();
     }
 
+    /**
+     * {@inheritDoc}
+     */
     @Override
     public void addConnectionEventListener(final ConnectionEventListener listener) {
         if (!eventListeners.contains(listener)) {
@@ -671,6 +674,9 @@ final class PooledConnectionImpl
         }
     }
 
+    /**
+     * {@inheritDoc}
+     */
     @Override
     public void removeConnectionEventListener(final ConnectionEventListener listener) {
         eventListeners.remove(listener);
diff --git a/java/org/apache/tomcat/dbcp/dbcp2/datasources/InstanceKeyDataSource.java b/java/org/apache/tomcat/dbcp/dbcp2/datasources/InstanceKeyDataSource.java
index 6ea4ed2a9d..d7f4325fcf 100644
--- a/java/org/apache/tomcat/dbcp/dbcp2/datasources/InstanceKeyDataSource.java
+++ b/java/org/apache/tomcat/dbcp/dbcp2/datasources/InstanceKeyDataSource.java
@@ -1109,18 +1109,15 @@ public abstract class InstanceKeyDataSource implements DataSource, Referenceable
 
     /**
      * <p>
-     * Sets the maximum permitted lifetime of a connection. A value of zero or less indicates an
-     * infinite lifetime.
+     * Sets the maximum permitted lifetime of a connection. A value of zero or less indicates an infinite lifetime.
      * </p>
      * <p>
-     * Note: this method currently has no effect once the pool has been initialized. The pool is initialized the first
-     * time one of the following methods is invoked: <code>getConnection, setLogwriter,
-     * setLoginTimeout, getLoginTimeout, getLogWriter.</code>
+     * 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: {@link #getConnection()}, {@link #setLogWriter(PrintWriter)}, {@link #setLoginTimeout(Duration)}, {@link #getLoginTimeoutDuration()},
+     * {@link #getLogWriter()}.
      * </p>
      *
-     * @param maxConnLifetimeMillis
-     *            The maximum permitted lifetime of a connection. A value of zero or less indicates an
-     *            infinite lifetime.
+     * @param maxConnLifetimeMillis The maximum permitted lifetime of a connection. A value of zero or less indicates an infinite lifetime.
      * @since 2.9.0
      */
     public void setMaxConnLifetime(final Duration maxConnLifetimeMillis) {
@@ -1129,18 +1126,15 @@ public abstract class InstanceKeyDataSource implements DataSource, Referenceable
 
     /**
      * <p>
-     * Sets the maximum permitted lifetime of a connection in milliseconds. A value of zero or less indicates an
-     * infinite lifetime.
+     * Sets the maximum permitted lifetime of a connection in milliseconds. A value of zero or less indicates an infinite lifetime.
      * </p>
      * <p>
-     * Note: this method currently has no effect once the pool has been initialized. The pool is initialized the first
-     * time one of the following methods is invoked: <code>getConnection, setLogwriter,
-     * setLoginTimeout, getLoginTimeout, getLogWriter.</code>
+     * 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: {@link #getConnection()}, {@link #setLogWriter(PrintWriter)}, {@link #setLoginTimeout(Duration)}, {@link #getLoginTimeoutDuration()},
+     * {@link #getLogWriter()}.
      * </p>
      *
-     * @param maxConnLifetimeMillis
-     *            The maximum permitted lifetime of a connection in milliseconds. A value of zero or less indicates an
-     *            infinite lifetime.
+     * @param maxConnLifetimeMillis The maximum permitted lifetime of a connection in milliseconds. A value of zero or less indicates an infinite lifetime.
      * @deprecated Use {@link #setMaxConnLifetime(Duration)}.
      */
     @Deprecated
diff --git a/java/org/apache/tomcat/dbcp/dbcp2/datasources/InstanceKeyDataSourceFactory.java b/java/org/apache/tomcat/dbcp/dbcp2/datasources/InstanceKeyDataSourceFactory.java
index 325fad1b78..4185a050ed 100644
--- a/java/org/apache/tomcat/dbcp/dbcp2/datasources/InstanceKeyDataSourceFactory.java
+++ b/java/org/apache/tomcat/dbcp/dbcp2/datasources/InstanceKeyDataSourceFactory.java
@@ -77,7 +77,6 @@ abstract class InstanceKeyDataSourceFactory implements ObjectFactory {
      *            Data to deserialize to create the configuration parameter.
      *
      * @return The Object created by deserializing the data.
-     *
      * @throws ClassNotFoundException
      *            If a class cannot be found during the deserialization of a configuration parameter.
      * @throws IOException
@@ -117,6 +116,10 @@ abstract class InstanceKeyDataSourceFactory implements ObjectFactory {
         }
     }
 
+    private Boolean booleanValueOf(RefAddr refAddr) {
+        return Boolean.valueOf(toString(refAddr));
+    }
+
     /**
      * Creates an instance of the subclass and sets any properties contained in the Reference.
      *
@@ -124,7 +127,6 @@ abstract class InstanceKeyDataSourceFactory implements ObjectFactory {
      *            The properties to be set on the created DataSource
      *
      * @return A configured DataSource of the appropriate type.
-     *
      * @throws ClassNotFoundException
      *            If a class cannot be found during the deserialization of a configuration parameter.
      * @throws IOException
@@ -145,7 +147,7 @@ abstract class InstanceKeyDataSourceFactory implements ObjectFactory {
             final Reference ref = (Reference) refObj;
             if (isCorrectClass(ref.getClassName())) {
                 final RefAddr refAddr = ref.get("instanceKey");
-                if (refAddr != null && refAddr.getContent() != null) {
+                if (hasContent(refAddr)) {
                     // object was bound to JNDI via Referenceable API.
                     obj = INSTANCE_MAP.get(refAddr.getContent());
                 } else {
@@ -171,6 +173,10 @@ abstract class InstanceKeyDataSourceFactory implements ObjectFactory {
         return obj;
     }
 
+    private boolean hasContent(final RefAddr refAddr) {
+        return refAddr != null && refAddr.getContent() != null;
+    }
+
     /**
      * Tests if className is the value returned from getClass().getName().toString().
      *
@@ -197,143 +203,151 @@ abstract class InstanceKeyDataSourceFactory implements ObjectFactory {
             throws IOException, ClassNotFoundException {
 
         RefAddr refAddr = ref.get("dataSourceName");
-        if (refAddr != null && refAddr.getContent() != null) {
+        if (hasContent(refAddr)) {
             ikds.setDataSourceName(toString(refAddr));
         }
 
         refAddr = ref.get("description");
-        if (refAddr != null && refAddr.getContent() != null) {
+        if (hasContent(refAddr)) {
             ikds.setDescription(toString(refAddr));
         }
 
         refAddr = ref.get("jndiEnvironment");
-        if (refAddr != null && refAddr.getContent() != null) {
+        if (hasContent(refAddr)) {
             final byte[] serialized = (byte[]) refAddr.getContent();
             ikds.setJndiEnvironment((Properties) deserialize(serialized));
         }
 
         refAddr = ref.get("loginTimeout");
-        if (refAddr != null && refAddr.getContent() != null) {
-            ikds.setLoginTimeout(Duration.ofSeconds(parseInt(refAddr)));
+        if (hasContent(refAddr)) {
+            ikds.setLoginTimeout(toDurationFromSeconds(refAddr));
         }
 
         // Pool properties
         refAddr = ref.get("blockWhenExhausted");
-        if (refAddr != null && refAddr.getContent() != null) {
+        if (hasContent(refAddr)) {
             ikds.setDefaultBlockWhenExhausted(parseBoolean(refAddr));
         }
 
         refAddr = ref.get("evictionPolicyClassName");
-        if (refAddr != null && refAddr.getContent() != null) {
+        if (hasContent(refAddr)) {
             ikds.setDefaultEvictionPolicyClassName(toString(refAddr));
         }
 
         // Pool properties
         refAddr = ref.get("lifo");
-        if (refAddr != null && refAddr.getContent() != null) {
+        if (hasContent(refAddr)) {
             ikds.setDefaultLifo(parseBoolean(refAddr));
         }
 
         refAddr = ref.get("maxIdlePerKey");
-        if (refAddr != null && refAddr.getContent() != null) {
+        if (hasContent(refAddr)) {
             ikds.setDefaultMaxIdle(parseInt(refAddr));
         }
 
         refAddr = ref.get("maxTotalPerKey");
-        if (refAddr != null && refAddr.getContent() != null) {
+        if (hasContent(refAddr)) {
             ikds.setDefaultMaxTotal(parseInt(refAddr));
         }
 
         refAddr = ref.get("maxWaitMillis");
-        if (refAddr != null && refAddr.getContent() != null) {
-            ikds.setDefaultMaxWait(Duration.ofMillis(parseLong(refAddr)));
+        if (hasContent(refAddr)) {
+            ikds.setDefaultMaxWait(toDurationFromMillis(refAddr));
         }
 
         refAddr = ref.get("minEvictableIdleTimeMillis");
-        if (refAddr != null && refAddr.getContent() != null) {
-            ikds.setDefaultMinEvictableIdle(Duration.ofMillis(parseLong(refAddr)));
+        if (hasContent(refAddr)) {
+            ikds.setDefaultMinEvictableIdle(toDurationFromMillis(refAddr));
         }
 
         refAddr = ref.get("minIdlePerKey");
-        if (refAddr != null && refAddr.getContent() != null) {
+        if (hasContent(refAddr)) {
             ikds.setDefaultMinIdle(parseInt(refAddr));
         }
 
         refAddr = ref.get("numTestsPerEvictionRun");
-        if (refAddr != null && refAddr.getContent() != null) {
+        if (hasContent(refAddr)) {
             ikds.setDefaultNumTestsPerEvictionRun(parseInt(refAddr));
         }
 
         refAddr = ref.get("softMinEvictableIdleTimeMillis");
-        if (refAddr != null && refAddr.getContent() != null) {
-            ikds.setDefaultSoftMinEvictableIdle(Duration.ofMillis(parseLong(refAddr)));
+        if (hasContent(refAddr)) {
+            ikds.setDefaultSoftMinEvictableIdle(toDurationFromMillis(refAddr));
         }
 
         refAddr = ref.get("testOnCreate");
-        if (refAddr != null && refAddr.getContent() != null) {
+        if (hasContent(refAddr)) {
             ikds.setDefaultTestOnCreate(parseBoolean(refAddr));
         }
 
         refAddr = ref.get("testOnBorrow");
-        if (refAddr != null && refAddr.getContent() != null) {
+        if (hasContent(refAddr)) {
             ikds.setDefaultTestOnBorrow(parseBoolean(refAddr));
         }
 
         refAddr = ref.get("testOnReturn");
-        if (refAddr != null && refAddr.getContent() != null) {
+        if (hasContent(refAddr)) {
             ikds.setDefaultTestOnReturn(parseBoolean(refAddr));
         }
 
         refAddr = ref.get("testWhileIdle");
-        if (refAddr != null && refAddr.getContent() != null) {
+        if (hasContent(refAddr)) {
             ikds.setDefaultTestWhileIdle(parseBoolean(refAddr));
         }
 
         refAddr = ref.get("timeBetweenEvictionRunsMillis");
-        if (refAddr != null && refAddr.getContent() != null) {
-            ikds.setDefaultDurationBetweenEvictionRuns(Duration.ofMillis(parseLong(refAddr)));
+        if (hasContent(refAddr)) {
+            ikds.setDefaultDurationBetweenEvictionRuns(toDurationFromMillis(refAddr));
         }
 
         // Connection factory properties
 
         refAddr = ref.get("validationQuery");
-        if (refAddr != null && refAddr.getContent() != null) {
+        if (hasContent(refAddr)) {
             ikds.setValidationQuery(toString(refAddr));
         }
 
         refAddr = ref.get("validationQueryTimeout");
-        if (refAddr != null && refAddr.getContent() != null) {
-            ikds.setValidationQueryTimeout(Duration.ofSeconds(parseInt(refAddr)));
+        if (hasContent(refAddr)) {
+            ikds.setValidationQueryTimeout(toDurationFromSeconds(refAddr));
         }
 
         refAddr = ref.get("rollbackAfterValidation");
-        if (refAddr != null && refAddr.getContent() != null) {
+        if (hasContent(refAddr)) {
             ikds.setRollbackAfterValidation(parseBoolean(refAddr));
         }
 
         refAddr = ref.get("maxConnLifetimeMillis");
-        if (refAddr != null && refAddr.getContent() != null) {
-            ikds.setMaxConnLifetime(Duration.ofMillis(parseLong(refAddr)));
+        if (hasContent(refAddr)) {
+            ikds.setMaxConnLifetime(toDurationFromMillis(refAddr));
         }
 
         // Connection properties
 
         refAddr = ref.get("defaultAutoCommit");
-        if (refAddr != null && refAddr.getContent() != null) {
-            ikds.setDefaultAutoCommit(Boolean.valueOf(toString(refAddr)));
+        if (hasContent(refAddr)) {
+            ikds.setDefaultAutoCommit(booleanValueOf(refAddr));
         }
 
         refAddr = ref.get("defaultTransactionIsolation");
-        if (refAddr != null && refAddr.getContent() != null) {
+        if (hasContent(refAddr)) {
             ikds.setDefaultTransactionIsolation(parseInt(refAddr));
         }
 
         refAddr = ref.get("defaultReadOnly");
-        if (refAddr != null && refAddr.getContent() != null) {
-            ikds.setDefaultReadOnly(Boolean.valueOf(toString(refAddr)));
+        if (hasContent(refAddr)) {
+            ikds.setDefaultReadOnly(booleanValueOf(refAddr));
         }
     }
 
+    private Duration toDurationFromMillis(RefAddr refAddr) {
+        return Duration.ofMillis(parseLong(refAddr));
+    }
+
+    private Duration toDurationFromSeconds(RefAddr refAddr) {
+        return Duration.ofSeconds(parseInt(refAddr));
+    }
+
     String toString(final RefAddr refAddr) {
         return refAddr.getContent().toString();
     }
diff --git a/java/org/apache/tomcat/dbcp/dbcp2/datasources/KeyedCPDSConnectionFactory.java b/java/org/apache/tomcat/dbcp/dbcp2/datasources/KeyedCPDSConnectionFactory.java
index a5ddd34b68..6743c15d18 100644
--- a/java/org/apache/tomcat/dbcp/dbcp2/datasources/KeyedCPDSConnectionFactory.java
+++ b/java/org/apache/tomcat/dbcp/dbcp2/datasources/KeyedCPDSConnectionFactory.java
@@ -38,8 +38,7 @@ import org.apache.tomcat.dbcp.pool2.PooledObject;
 import org.apache.tomcat.dbcp.pool2.impl.DefaultPooledObject;
 
 /**
- * A {@link KeyedPooledObjectFactory} that creates {@link org.apache.tomcat.dbcp.dbcp2.PoolableConnection
- * PoolableConnection}s.
+ * A {@link KeyedPooledObjectFactory} that creates {@link PoolableConnection}s.
  *
  * @since 2.0
  */
diff --git a/java/org/apache/tomcat/dbcp/dbcp2/datasources/PerUserPoolDataSource.java b/java/org/apache/tomcat/dbcp/dbcp2/datasources/PerUserPoolDataSource.java
index 5614e5948e..3d9dc292e1 100644
--- a/java/org/apache/tomcat/dbcp/dbcp2/datasources/PerUserPoolDataSource.java
+++ b/java/org/apache/tomcat/dbcp/dbcp2/datasources/PerUserPoolDataSource.java
@@ -24,6 +24,7 @@ import java.time.Duration;
 import java.util.HashMap;
 import java.util.Map;
 import java.util.NoSuchElementException;
+import java.util.function.Supplier;
 
 import javax.naming.NamingException;
 import javax.naming.Reference;
@@ -35,6 +36,7 @@ import org.apache.juli.logging.LogFactory;
 import org.apache.tomcat.dbcp.dbcp2.SwallowedExceptionLogger;
 import org.apache.tomcat.dbcp.dbcp2.Utils;
 import org.apache.tomcat.dbcp.pool2.ObjectPool;
+import org.apache.tomcat.dbcp.pool2.impl.EvictionPolicy;
 import org.apache.tomcat.dbcp.pool2.impl.GenericObjectPool;
 
 /**
@@ -67,23 +69,94 @@ public class PerUserPoolDataSource extends InstanceKeyDataSource {
         return new HashMap<>();
     }
 
+    /**
+     * Maps user names to a data source property: BlockWhenExhausted.
+     */
     private Map<String, Boolean> perUserBlockWhenExhausted;
+
+    /**
+     * Maps user names to a data source property: EvictionPolicyClassName.
+     */
     private Map<String, String> perUserEvictionPolicyClassName;
+
+    /**
+     * Maps user names to a data source property: Lifo.
+     */
     private Map<String, Boolean> perUserLifo;
+
+    /**
+     * Maps user names to a data source property: MaxIdle.
+     */
     private Map<String, Integer> perUserMaxIdle;
+
+    /**
+     * Maps user names to a data source property: MaxTotal.
+     */
     private Map<String, Integer> perUserMaxTotal;
+
+    /**
+     * Maps user names to a data source property: MaxWaitDuration.
+     */
     private Map<String, Duration> perUserMaxWaitDuration;
+
+    /**
+     * Maps user names to a data source property: MinEvictableIdleDuration.
+     */
     private Map<String, Duration> perUserMinEvictableIdleDuration;
+
+    /**
+     * Maps user names to a data source property: MinIdle.
+     */
     private Map<String, Integer> perUserMinIdle;
+
+    /**
+     * Maps user names to a data source property: NumTestsPerEvictionRun.
+     */
     private Map<String, Integer> perUserNumTestsPerEvictionRun;
+
+    /**
+     * Maps user names to a data source property: SoftMinEvictableIdleDuration.
+     */
     private Map<String, Duration> perUserSoftMinEvictableIdleDuration;
+
+    /**
+     * Maps user names to a data source property: TestOnCreate.
+     */
     private Map<String, Boolean> perUserTestOnCreate;
+
+    /**
+     * Maps user names to a data source property: TestOnBorrow.
+     */
     private Map<String, Boolean> perUserTestOnBorrow;
+
+    /**
+     * Maps user names to a data source property: TestOnReturn.
+     */
     private Map<String, Boolean> perUserTestOnReturn;
+
+    /**
+     * Maps user names to a data source property: TestWhileIdle.
+     */
     private Map<String, Boolean> perUserTestWhileIdle;
+
+    /**
+     * Maps user names to a data source property: DurationBetweenEvictionRuns.
+     */
     private Map<String, Duration> perUserDurationBetweenEvictionRuns;
+
+    /**
+     * Maps user names to a data source property: DefaultAutoCommit.
+     */
     private Map<String, Boolean> perUserDefaultAutoCommit;
+
+    /**
+     * Maps user names to a data source property: DefaultTransactionIsolation.
+     */
     private Map<String, Integer> perUserDefaultTransactionIsolation;
+
+    /**
+     * Maps user names to a data source property: DefaultReadOnly.
+     */
     private Map<String, Boolean> perUserDefaultReadOnly;
 
     /**
@@ -92,7 +165,7 @@ public class PerUserPoolDataSource extends InstanceKeyDataSource {
     private transient Map<PoolKey, PooledConnectionManager> managers = createMap();
 
     /**
-     * Default no-arg constructor for Serialization.
+     * Constructs a new instance.
      */
     public PerUserPoolDataSource() {
     }
@@ -103,6 +176,7 @@ public class PerUserPoolDataSource extends InstanceKeyDataSource {
      * @see org.apache.tomcat.dbcp.pool2.ObjectPool#clear()
      * @since 2.3.0
      */
+    @SuppressWarnings("resource") // does not allocate a pool
     public void clear() {
         managers.values().forEach(manager -> {
             try {
@@ -140,13 +214,34 @@ public class PerUserPoolDataSource extends InstanceKeyDataSource {
 
     }
 
+    /**
+     * Gets the user specific default value in a map for the specified user's pool.
+     *
+     * @param userName The user name key.
+     * @return The user specific value.
+     */
+    private <V> V get(final Map<String, V> map, final String userName) {
+        return map != null ? map.get(userName) : null;
+    }
+
+    /**
+     * Gets the user specific default value in a map for the specified user's pool.
+     *
+     * @param userName The user name key.
+     * @return The user specific value.
+     */
+    private <V> V get(final Map<String, V> map, final String userName, final Supplier<V> defaultSupplier) {
+        final V v = get(map, userName);
+        return v != null ? v : defaultSupplier.get();
+    }
+
     @Override
     protected PooledConnectionManager getConnectionManager(final UserPassKey upKey) {
         return managers.get(getPoolKey(upKey.getUserName()));
     }
 
     /**
-     * Gets the underlying pool but does NOT allocate it.
+     * Gets the underlying pre-allocated pool (does NOT allocate).
      *
      * @param manager A CPDSConnectionFactory.
      * @return the underlying pool.
@@ -206,14 +301,7 @@ public class PerUserPoolDataSource extends InstanceKeyDataSource {
      * @return The user specific value.
      */
     public boolean getPerUserBlockWhenExhausted(final String userName) {
-        Boolean value = null;
-        if (perUserBlockWhenExhausted != null) {
-            value = perUserBlockWhenExhausted.get(userName);
-        }
-        if (value == null) {
-            return getDefaultBlockWhenExhausted();
-        }
-        return value.booleanValue();
+        return get(perUserBlockWhenExhausted, userName, this::getDefaultBlockWhenExhausted);
     }
 
     /**
@@ -224,11 +312,7 @@ public class PerUserPoolDataSource extends InstanceKeyDataSource {
      * @return The user specific value.
      */
     public Boolean getPerUserDefaultAutoCommit(final String userName) {
-        Boolean value = null;
-        if (perUserDefaultAutoCommit != null) {
-            value = perUserDefaultAutoCommit.get(userName);
-        }
-        return value;
+        return get(perUserDefaultAutoCommit, userName);
     }
 
     /**
@@ -239,11 +323,7 @@ public class PerUserPoolDataSource extends InstanceKeyDataSource {
      * @return The user specific value.
      */
     public Boolean getPerUserDefaultReadOnly(final String userName) {
-        Boolean value = null;
-        if (perUserDefaultReadOnly != null) {
-            value = perUserDefaultReadOnly.get(userName);
-        }
-        return value;
+        return get(perUserDefaultReadOnly, userName);
     }
 
     /**
@@ -255,11 +335,7 @@ public class PerUserPoolDataSource extends InstanceKeyDataSource {
      * @return The user specific value.
      */
     public Integer getPerUserDefaultTransactionIsolation(final String userName) {
-        Integer value = null;
-        if (perUserDefaultTransactionIsolation != null) {
-            value = perUserDefaultTransactionIsolation.get(userName);
-        }
-        return value;
+        return get(perUserDefaultTransactionIsolation, userName);
     }
 
     /**
@@ -272,33 +348,22 @@ public class PerUserPoolDataSource extends InstanceKeyDataSource {
      * @since 2.10.0
      */
     public Duration getPerUserDurationBetweenEvictionRuns(final String userName) {
-        Duration value = null;
-        if (perUserDurationBetweenEvictionRuns != null) {
-            value = perUserDurationBetweenEvictionRuns.get(userName);
-        }
-        if (value == null) {
-            return getDefaultDurationBetweenEvictionRuns();
-        }
-        return value;
+        return get(perUserDurationBetweenEvictionRuns, userName, this::getDefaultDurationBetweenEvictionRuns);
     }
 
     /**
      * Gets the user specific value for {@link GenericObjectPool#getEvictionPolicyClassName()} for the specified user's
      * pool or the default if no user specific value is defined.
+     * <p>
+     * The class must implement {@link EvictionPolicy}.
+     * </p>
      *
      * @param userName
      *            The user name key.
      * @return The user specific value.
      */
     public String getPerUserEvictionPolicyClassName(final String userName) {
-        String value = null;
-        if (perUserEvictionPolicyClassName != null) {
-            value = perUserEvictionPolicyClassName.get(userName);
-        }
-        if (value == null) {
-            return getDefaultEvictionPolicyClassName();
-        }
-        return value;
+        return get(perUserEvictionPolicyClassName, userName, this::getDefaultEvictionPolicyClassName);
     }
 
     /**
@@ -310,14 +375,7 @@ public class PerUserPoolDataSource extends InstanceKeyDataSource {
      * @return The user specific value.
      */
     public boolean getPerUserLifo(final String userName) {
-        Boolean value = null;
-        if (perUserLifo != null) {
-            value = perUserLifo.get(userName);
-        }
-        if (value == null) {
-            return getDefaultLifo();
-        }
-        return value.booleanValue();
+        return get(perUserLifo, userName, this::getDefaultLifo);
     }
 
     /**
@@ -329,14 +387,7 @@ public class PerUserPoolDataSource extends InstanceKeyDataSource {
      * @return The user specific value.
      */
     public int getPerUserMaxIdle(final String userName) {
-        Integer value = null;
-        if (perUserMaxIdle != null) {
-            value = perUserMaxIdle.get(userName);
-        }
-        if (value == null) {
-            return getDefaultMaxIdle();
-        }
-        return value.intValue();
+        return get(perUserMaxIdle, userName, this::getDefaultMaxIdle);
     }
 
     /**
@@ -348,14 +399,7 @@ public class PerUserPoolDataSource extends InstanceKeyDataSource {
      * @return The user specific value.
      */
     public int getPerUserMaxTotal(final String userName) {
-        Integer value = null;
-        if (perUserMaxTotal != null) {
-            value = perUserMaxTotal.get(userName);
-        }
-        if (value == null) {
-            return getDefaultMaxTotal();
-        }
-        return value.intValue();
+        return get(perUserMaxTotal, userName, this::getDefaultMaxTotal);
     }
 
     /**
@@ -368,14 +412,7 @@ public class PerUserPoolDataSource extends InstanceKeyDataSource {
      * @since 2.10.0
      */
     public Duration getPerUserMaxWaitDuration(final String userName) {
-        Duration value = null;
-        if (perUserMaxWaitDuration != null) {
-            value = perUserMaxWaitDuration.get(userName);
-        }
-        if (value == null) {
-            return getDefaultMaxWait();
-        }
-        return value;
+        return get(perUserMaxWaitDuration, userName, this::getDefaultMaxWait);
     }
 
     /**
@@ -402,14 +439,7 @@ public class PerUserPoolDataSource extends InstanceKeyDataSource {
      * @since 2.10.0
      */
     public Duration getPerUserMinEvictableIdleDuration(final String userName) {
-        Duration value = null;
-        if (perUserMinEvictableIdleDuration != null) {
-            value = perUserMinEvictableIdleDuration.get(userName);
-        }
-        if (value == null) {
-            return getDefaultMinEvictableIdleDuration();
-        }
-        return value;
+        return get(perUserMinEvictableIdleDuration, userName, this::getDefaultMinEvictableIdleDuration);
     }
 
     /**
@@ -435,14 +465,7 @@ public class PerUserPoolDataSource extends InstanceKeyDataSource {
      * @return The user specific value.
      */
     public int getPerUserMinIdle(final String userName) {
-        Integer value = null;
-        if (perUserMinIdle != null) {
-            value = perUserMinIdle.get(userName);
-        }
-        if (value == null) {
-            return getDefaultMinIdle();
-        }
-        return value.intValue();
+        return get(perUserMinIdle, userName, this::getDefaultMinIdle);
     }
 
     /**
@@ -454,14 +477,7 @@ public class PerUserPoolDataSource extends InstanceKeyDataSource {
      * @return The user specific value.
      */
     public int getPerUserNumTestsPerEvictionRun(final String userName) {
-        Integer value = null;
-        if (perUserNumTestsPerEvictionRun != null) {
-            value = perUserNumTestsPerEvictionRun.get(userName);
-        }
-        if (value == null) {
-            return getDefaultNumTestsPerEvictionRun();
-        }
-        return value.intValue();
+        return get(perUserNumTestsPerEvictionRun, userName, this::getDefaultNumTestsPerEvictionRun);
     }
 
     /**
@@ -474,14 +490,7 @@ public class PerUserPoolDataSource extends InstanceKeyDataSource {
      * @since 2.10.0
      */
     public Duration getPerUserSoftMinEvictableIdleDuration(final String userName) {
-        Duration value = null;
-        if (perUserSoftMinEvictableIdleDuration != null) {
-            value = perUserSoftMinEvictableIdleDuration.get(userName);
-        }
-        if (value == null) {
-            return getDefaultSoftMinEvictableIdleDuration();
-        }
-        return value;
+        return get(perUserSoftMinEvictableIdleDuration, userName, this::getDefaultSoftMinEvictableIdleDuration);
     }
 
     /**
@@ -507,14 +516,7 @@ public class PerUserPoolDataSource extends InstanceKeyDataSource {
      * @return The user specific value.
      */
     public boolean getPerUserTestOnBorrow(final String userName) {
-        Boolean value = null;
-        if (perUserTestOnBorrow != null) {
-            value = perUserTestOnBorrow.get(userName);
-        }
-        if (value == null) {
-            return getDefaultTestOnBorrow();
-        }
-        return value.booleanValue();
+        return get(perUserTestOnBorrow, userName, this::getDefaultTestOnBorrow);
     }
 
     /**
@@ -526,14 +528,7 @@ public class PerUserPoolDataSource extends InstanceKeyDataSource {
      * @return The user specific value.
      */
     public boolean getPerUserTestOnCreate(final String userName) {
-        Boolean value = null;
-        if (perUserTestOnCreate != null) {
-            value = perUserTestOnCreate.get(userName);
-        }
-        if (value == null) {
-            return getDefaultTestOnCreate();
-        }
-        return value.booleanValue();
+        return get(perUserTestOnCreate, userName, this::getDefaultTestOnCreate);
     }
 
     /**
@@ -545,14 +540,7 @@ public class PerUserPoolDataSource extends InstanceKeyDataSource {
      * @return The user specific value.
      */
     public boolean getPerUserTestOnReturn(final String userName) {
-        Boolean value = null;
-        if (perUserTestOnReturn != null) {
-            value = perUserTestOnReturn.get(userName);
-        }
-        if (value == null) {
-            return getDefaultTestOnReturn();
-        }
-        return value.booleanValue();
+        return get(perUserTestOnReturn, userName, this::getDefaultTestOnReturn);
     }
 
     /**
@@ -564,14 +552,7 @@ public class PerUserPoolDataSource extends InstanceKeyDataSource {
      * @return The user specific value.
      */
     public boolean getPerUserTestWhileIdle(final String userName) {
-        Boolean value = null;
-        if (perUserTestWhileIdle != null) {
-            value = perUserTestWhileIdle.get(userName);
-        }
-        if (value == null) {
-            return getDefaultTestWhileIdle();
-        }
-        return value.booleanValue();
+        return get(perUserTestWhileIdle, userName, this::getDefaultTestWhileIdle);
     }
 
     /**
@@ -600,10 +581,9 @@ public class PerUserPoolDataSource extends InstanceKeyDataSource {
         return mgr == null ? null : mgr.getPool();
     }
 
+    @SuppressWarnings("resource") // does not allocate a pool
     @Override
-    protected PooledConnectionAndInfo getPooledConnectionAndInfo(final String userName, final String password)
-            throws SQLException {
-
+    protected PooledConnectionAndInfo getPooledConnectionAndInfo(final String userName, final String password) throws SQLException {
         final PoolKey key = getPoolKey(userName);
         ObjectPool<PooledConnectionAndInfo> pool;
         PooledConnectionManager manager;
@@ -619,7 +599,6 @@ public class PerUserPoolDataSource extends InstanceKeyDataSource {
             }
             pool = getCPDSConnectionFactoryPool(manager);
         }
-
         PooledConnectionAndInfo info = null;
         try {
             info = pool.borrowObject();
@@ -682,39 +661,36 @@ public class PerUserPoolDataSource extends InstanceKeyDataSource {
     }
 
     /**
-     * Supports Serialization interface.
+     * Deserializes an instance from an ObjectInputStream.
      *
-     * @param in
-     *            a {@link java.io.ObjectInputStream} value
-     * @throws IOException
-     *             if an error occurs
-     * @throws ClassNotFoundException
-     *             if an error occurs
+     * @param in The source ObjectInputStream.
+     * @throws IOException            Any of the usual Input/Output related exceptions.
+     * @throws ClassNotFoundException A class of a serialized object cannot be found.
      */
+    @SuppressWarnings("resource")
     private void readObject(final ObjectInputStream in) throws IOException, ClassNotFoundException {
+        in.defaultReadObject();
+        this.managers = readObjectImpl().managers;
+    }
+
+    private PerUserPoolDataSource readObjectImpl() throws IOException, ClassNotFoundException {
         try {
-            in.defaultReadObject();
-            final PerUserPoolDataSource oldDS = (PerUserPoolDataSource) new PerUserPoolDataSourceFactory()
-                    .getObjectInstance(getReference(), null, null, null);
-            this.managers = oldDS.managers;
+            return (PerUserPoolDataSource) new PerUserPoolDataSourceFactory().getObjectInstance(getReference(), null, null, null);
         } catch (final NamingException e) {
             throw new IOException("NamingException: " + e);
         }
     }
 
-    private synchronized void registerPool(final String userName, final String password)
-        throws NamingException, SQLException {
-
+    private synchronized void registerPool(final String userName, final String password) throws NamingException, SQLException {
         final ConnectionPoolDataSource cpds = testCPDS(userName, password);
-
         // Set up the factory we will use (passing the pool associates
         // the factory with the pool, so we do not have to do so
         // explicitly)
         final CPDSConnectionFactory factory = new CPDSConnectionFactory(cpds, getValidationQuery(), getValidationQueryTimeoutDuration(),
-            isRollbackAfterValidation(), userName, password);
+                isRollbackAfterValidation(), userName, password);
         factory.setMaxConn(getMaxConnDuration());
-
         // Create an object pool to contain our PooledConnections
+        @SuppressWarnings("resource")
         final GenericObjectPool<PooledConnectionAndInfo> pool = new GenericObjectPool<>(factory);
         factory.setPool(pool);
         pool.setBlockWhenExhausted(getPerUserBlockWhenExhausted(userName));
@@ -732,13 +708,13 @@ public class PerUserPoolDataSource extends InstanceKeyDataSource {
         pool.setTestOnReturn(getPerUserTestOnReturn(userName));
         pool.setTestWhileIdle(getPerUserTestWhileIdle(userName));
         pool.setDurationBetweenEvictionRuns(getPerUserDurationBetweenEvictionRuns(userName));
-
         pool.setSwallowedExceptionListener(new SwallowedExceptionLogger(log));
-
-        final PooledConnectionManager old = managers.put(getPoolKey(userName), factory);
-        if (old != null) {
+        final PoolKey poolKey = getPoolKey(userName);
+        if (managers.containsKey(poolKey)) {
+            pool.close();
             throw new IllegalStateException("Pool already contains an entry for this user/password: " + userName);
         }
+        managers.put(poolKey, factory);
     }
 
     private <K, V> Map<K, V> replaceAll(final Map<K, V> currentMap, final Map<K, V> newMap) {
@@ -855,7 +831,9 @@ public class PerUserPoolDataSource extends InstanceKeyDataSource {
     /**
      * Sets a user specific value for {@link GenericObjectPool#getEvictionPolicyClassName()} for the specified user's
      * pool.
-     *
+     * <p>
+     * The class must implement {@link EvictionPolicy}.
+     * </p>
      * @param userName
      *            The user name key.
      * @param value
diff --git a/java/org/apache/tomcat/dbcp/dbcp2/datasources/PoolKey.java b/java/org/apache/tomcat/dbcp/dbcp2/datasources/PoolKey.java
index 9cc08ab23b..43deef603e 100644
--- a/java/org/apache/tomcat/dbcp/dbcp2/datasources/PoolKey.java
+++ b/java/org/apache/tomcat/dbcp/dbcp2/datasources/PoolKey.java
@@ -20,6 +20,8 @@ import java.io.Serializable;
 import java.util.Objects;
 
 /**
+ * The key type for entries in a {@link PerUserPoolDataSource}.
+ *
  * @since 2.0
  */
 final class PoolKey implements Serializable {
diff --git a/java/org/apache/tomcat/dbcp/dbcp2/datasources/PooledConnectionAndInfo.java b/java/org/apache/tomcat/dbcp/dbcp2/datasources/PooledConnectionAndInfo.java
index 18aa8c144e..ecbe1b451b 100644
--- a/java/org/apache/tomcat/dbcp/dbcp2/datasources/PooledConnectionAndInfo.java
+++ b/java/org/apache/tomcat/dbcp/dbcp2/datasources/PooledConnectionAndInfo.java
@@ -30,15 +30,6 @@ final class PooledConnectionAndInfo {
 
     private final UserPassKey userPassKey;
 
-    /**
-     * Constructs a new instance.
-     *
-     * @since 2.4.0
-     */
-    PooledConnectionAndInfo(final PooledConnection pooledConnection, final char[] userName, final char[] userPassword) {
-        this(pooledConnection, new UserPassKey(userName, userPassword));
-    }
-
     PooledConnectionAndInfo(final PooledConnection pooledConnection, final UserPassKey userPassKey) {
         this.pooledConnection = pooledConnection;
         this.userPassKey = userPassKey;
diff --git a/java/org/apache/tomcat/dbcp/dbcp2/datasources/SharedPoolDataSource.java b/java/org/apache/tomcat/dbcp/dbcp2/datasources/SharedPoolDataSource.java
index e3907066d5..63e8423403 100644
--- a/java/org/apache/tomcat/dbcp/dbcp2/datasources/SharedPoolDataSource.java
+++ b/java/org/apache/tomcat/dbcp/dbcp2/datasources/SharedPoolDataSource.java
@@ -51,10 +51,19 @@ public class SharedPoolDataSource extends InstanceKeyDataSource {
 
     private static final long serialVersionUID = -1458539734480586454L;
 
-    // Pool properties
+    /**
+     * Max total defaults to {@link GenericKeyedObjectPoolConfig#DEFAULT_MAX_TOTAL}.
+     */
     private int maxTotal = GenericKeyedObjectPoolConfig.DEFAULT_MAX_TOTAL;
 
+    /**
+     * Maps user credentials to pooled connection with credentials.
+     */
     private transient KeyedObjectPool<UserPassKey, PooledConnectionAndInfo> pool;
+
+    /**
+     * A {@link KeyedPooledObjectFactory} that creates {@link PoolableConnection}s.
+     */
     private transient KeyedCPDSConnectionFactory factory;
 
     /**
@@ -89,9 +98,6 @@ public class SharedPoolDataSource extends InstanceKeyDataSource {
         return this.maxTotal;
     }
 
-    // ----------------------------------------------------------------------
-    // Instrumentation Methods
-
     /**
      * Gets the number of active connections in the pool.
      *
@@ -142,20 +148,20 @@ public class SharedPoolDataSource extends InstanceKeyDataSource {
     }
 
     /**
-     * Supports Serialization interface.
+     * Deserializes an instance from an ObjectInputStream.
      *
-     * @param in
-     *            a {@link java.io.ObjectInputStream} value
-     * @throws IOException
-     *             if an error occurs
-     * @throws ClassNotFoundException
-     *             if an error occurs
+     * @param in The source ObjectInputStream.
+     * @throws IOException            Any of the usual Input/Output related exceptions.
+     * @throws ClassNotFoundException A class of a serialized object cannot be found.
      */
     private void readObject(final ObjectInputStream in) throws IOException, ClassNotFoundException {
+        in.defaultReadObject();
+        this.pool = readObjectImpl();
+    }
+
+    private KeyedObjectPool<UserPassKey, PooledConnectionAndInfo> readObjectImpl() throws IOException, ClassNotFoundException {
         try {
-            in.defaultReadObject();
-            final SharedPoolDataSource oldDS = (SharedPoolDataSource) new SharedPoolDataSourceFactory().getObjectInstance(getReference(), null, null, null);
-            this.pool = oldDS.pool;
+            return ((SharedPoolDataSource) new SharedPoolDataSourceFactory().getObjectInstance(getReference(), null, null, null)).pool;
         } catch (final NamingException e) {
             throw new IOException("NamingException: " + e);
         }
diff --git a/java/org/apache/tomcat/dbcp/dbcp2/datasources/SharedPoolDataSourceFactory.java b/java/org/apache/tomcat/dbcp/dbcp2/datasources/SharedPoolDataSourceFactory.java
index b3feafe0e7..ca405f8405 100644
--- a/java/org/apache/tomcat/dbcp/dbcp2/datasources/SharedPoolDataSourceFactory.java
+++ b/java/org/apache/tomcat/dbcp/dbcp2/datasources/SharedPoolDataSourceFactory.java
@@ -25,8 +25,16 @@ import javax.naming.Reference;
  * @since 2.0
  */
 public class SharedPoolDataSourceFactory extends InstanceKeyDataSourceFactory {
+
     private static final String SHARED_POOL_CLASSNAME = SharedPoolDataSource.class.getName();
 
+    /**
+     * Constructs a new instance.
+     */
+    public SharedPoolDataSourceFactory() {
+        // empty
+    }
+
     @Override
     protected InstanceKeyDataSource getNewInstance(final Reference ref) {
         final SharedPoolDataSource spds = new SharedPoolDataSource();
diff --git a/java/org/apache/tomcat/dbcp/dbcp2/datasources/UserPassKey.java b/java/org/apache/tomcat/dbcp/dbcp2/datasources/UserPassKey.java
index 02ed24bbcd..4c4a2afd97 100644
--- a/java/org/apache/tomcat/dbcp/dbcp2/datasources/UserPassKey.java
+++ b/java/org/apache/tomcat/dbcp/dbcp2/datasources/UserPassKey.java
@@ -42,10 +42,6 @@ final class UserPassKey implements Serializable {
     private final CharArray name;
     private final CharArray password;
 
-    UserPassKey(final char[] userName, final char[] password) {
-        this(new CharArray(userName), new CharArray(password));
-    }
-
     UserPassKey(final CharArray userName, final CharArray userPassword) {
         this.name = userName;
         this.password = userPassword;
diff --git a/java/org/apache/tomcat/dbcp/dbcp2/managed/BasicManagedDataSource.java b/java/org/apache/tomcat/dbcp/dbcp2/managed/BasicManagedDataSource.java
index 436a073f06..e456811bca 100644
--- a/java/org/apache/tomcat/dbcp/dbcp2/managed/BasicManagedDataSource.java
+++ b/java/org/apache/tomcat/dbcp/dbcp2/managed/BasicManagedDataSource.java
@@ -128,8 +128,7 @@ public class BasicManagedDataSource extends BasicDataSource {
             throws SQLException {
         PoolableConnectionFactory connectionFactory = null;
         try {
-            connectionFactory = new PoolableManagedConnectionFactory((XAConnectionFactory) driverConnectionFactory,
-                    getRegisteredJmxName());
+            connectionFactory = new PoolableManagedConnectionFactory((XAConnectionFactory) driverConnectionFactory, getRegisteredJmxName());
             connectionFactory.setValidationQuery(getValidationQuery());
             connectionFactory.setValidationQueryTimeout(getValidationQueryTimeoutDuration());
             connectionFactory.setConnectionInitSql(getConnectionInitSqls());
@@ -148,6 +147,7 @@ public class BasicManagedDataSource extends BasicDataSource {
             connectionFactory.setDefaultQueryTimeout(getDefaultQueryTimeoutDuration());
             connectionFactory.setFastFailValidation(getFastFailValidation());
             connectionFactory.setDisconnectionSqlCodes(getDisconnectionSqlCodes());
+            connectionFactory.setDisconnectionIgnoreSqlCodes(getDisconnectionIgnoreSqlCodes());
             validateConnectionFactory(connectionFactory);
         } catch (final RuntimeException e) {
             throw e;
@@ -236,17 +236,14 @@ public class BasicManagedDataSource extends BasicDataSource {
     }
 
     /**
-     * <p>
      * Sets the XADataSource instance used by the XAConnectionFactory.
-     * </p>
      * <p>
-     * Note: this method currently has no effect once the pool has been initialized. The pool is initialized the first
-     * time one of the following methods is invoked: <code>getConnection, setLogwriter,
-     * setLoginTimeout, getLoginTimeout, getLogWriter.</code>
+     * 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: {@link #getConnection()}, {@link #setLogWriter(java.io.PrintWriter)}, {@link #setLoginTimeout(int)}, {@link #getLoginTimeout()},
+     * {@link #getLogWriter()}.
      * </p>
      *
-     * @param xaDataSourceInstance
-     *            XADataSource instance
+     * @param xaDataSourceInstance XADataSource instance
      */
     public synchronized void setXaDataSourceInstance(final XADataSource xaDataSourceInstance) {
         this.xaDataSourceInstance = xaDataSourceInstance;
diff --git a/java/org/apache/tomcat/dbcp/dbcp2/managed/LocalXAConnectionFactory.java b/java/org/apache/tomcat/dbcp/dbcp2/managed/LocalXAConnectionFactory.java
index d32b701567..8c70d24478 100644
--- a/java/org/apache/tomcat/dbcp/dbcp2/managed/LocalXAConnectionFactory.java
+++ b/java/org/apache/tomcat/dbcp/dbcp2/managed/LocalXAConnectionFactory.java
@@ -201,18 +201,18 @@ public class LocalXAConnectionFactory implements XAConnectionFactory {
                     connection.setAutoCommit(originalAutoCommit);
 
                     // tell the transaction manager we are read only
-                    return XAResource.XA_RDONLY;
+                    return XA_RDONLY;
                 }
             } catch (final SQLException ignored) {
                 // no big deal
             }
 
             // this is a local (one phase) only connection, so we can't prepare
-            return XAResource.XA_OK;
+            return XA_OK;
         }
 
         /**
-         * Always returns a zero length Xid array. The LocalXAConnectionFactory can not support recovery, so no xids
+         * Always returns a zero length Xid array. The LocalXAConnectionFactory cannot support recovery, so no xids
          * will ever be found.
          *
          * @param flag
@@ -280,7 +280,7 @@ public class LocalXAConnectionFactory implements XAConnectionFactory {
          */
         @Override
         public synchronized void start(final Xid xid, final int flag) throws XAException {
-            if (flag == XAResource.TMNOFLAGS) {
+            if (flag == TMNOFLAGS) {
                 // first time in this transaction
 
                 // make sure we aren't already in another tx
@@ -305,7 +305,7 @@ public class LocalXAConnectionFactory implements XAConnectionFactory {
                 }
 
                 this.currentXid = xid;
-            } else if (flag == XAResource.TMRESUME) {
+            } else if (flag == TMRESUME) {
                 if (!xid.equals(this.currentXid)) {
                     throw new XAException("Attempting to resume in different transaction: expected " + this.currentXid
                             + ", but was " + xid);
@@ -369,6 +369,8 @@ public class LocalXAConnectionFactory implements XAConnectionFactory {
     }
 
     /**
+     * Gets the connection factory.
+     *
      * @return The connection factory.
      * @since 2.6.0
      */
@@ -376,6 +378,11 @@ public class LocalXAConnectionFactory implements XAConnectionFactory {
         return connectionFactory;
     }
 
+    /**
+     * Gets the transaction registry.
+     *
+     * @return The transaction registry.
+     */
     @Override
     public TransactionRegistry getTransactionRegistry() {
         return transactionRegistry;
diff --git a/java/org/apache/tomcat/dbcp/dbcp2/managed/ManagedConnection.java b/java/org/apache/tomcat/dbcp/dbcp2/managed/ManagedConnection.java
index 579231d362..1f958e1e46 100644
--- a/java/org/apache/tomcat/dbcp/dbcp2/managed/ManagedConnection.java
+++ b/java/org/apache/tomcat/dbcp/dbcp2/managed/ManagedConnection.java
@@ -122,7 +122,7 @@ public class ManagedConnection<C extends Connection> extends DelegatingConnectio
     @Override
     public void commit() throws SQLException {
         if (transactionContext != null) {
-            throw new SQLException("Commit can not be set while enrolled in a transaction");
+            throw new SQLException("Commit cannot be set while enrolled in a transaction");
         }
         super.commit();
     }
@@ -148,6 +148,8 @@ public class ManagedConnection<C extends Connection> extends DelegatingConnectio
     }
 
     /**
+     * Gets the transaction context.
+     *
      * @return The transaction context.
      * @since 2.6.0
      */
@@ -156,6 +158,8 @@ public class ManagedConnection<C extends Connection> extends DelegatingConnectio
     }
 
     /**
+     * Gets the transaction registry.
+     *
      * @return The transaction registry.
      * @since 2.6.0
      */
@@ -175,7 +179,7 @@ public class ManagedConnection<C extends Connection> extends DelegatingConnectio
     @Override
     public void rollback() throws SQLException {
         if (transactionContext != null) {
-            throw new SQLException("Commit can not be set while enrolled in a transaction");
+            throw new SQLException("Commit cannot be set while enrolled in a transaction");
         }
         super.rollback();
     }
@@ -183,7 +187,7 @@ public class ManagedConnection<C extends Connection> extends DelegatingConnectio
     @Override
     public void setAutoCommit(final boolean autoCommit) throws SQLException {
         if (transactionContext != null) {
-            throw new SQLException("Auto-commit can not be set while enrolled in a transaction");
+            throw new SQLException("Auto-commit cannot be set while enrolled in a transaction");
         }
         super.setAutoCommit(autoCommit);
     }
@@ -191,7 +195,7 @@ public class ManagedConnection<C extends Connection> extends DelegatingConnectio
     @Override
     public void setReadOnly(final boolean readOnly) throws SQLException {
         if (transactionContext != null) {
-            throw new SQLException("Read-only can not be set while enrolled in a transaction");
+            throw new SQLException("Read-only cannot be set while enrolled in a transaction");
         }
         super.setReadOnly(readOnly);
     }
@@ -240,7 +244,7 @@ public class ManagedConnection<C extends Connection> extends DelegatingConnectio
         if (transactionContext != null && !transactionContext.isTransactionComplete()) {
             if (transactionContext.isActive()) {
                 if (transactionContext != transactionRegistry.getActiveTransactionContext()) {
-                    throw new SQLException("Connection can not be used while enlisted in another transaction");
+                    throw new SQLException("Connection cannot be used while enlisted in another transaction");
                 }
                 return;
             }
diff --git a/java/org/apache/tomcat/dbcp/dbcp2/managed/PoolableManagedConnection.java b/java/org/apache/tomcat/dbcp/dbcp2/managed/PoolableManagedConnection.java
index f9958d7a81..05d5a132a3 100644
--- a/java/org/apache/tomcat/dbcp/dbcp2/managed/PoolableManagedConnection.java
+++ b/java/org/apache/tomcat/dbcp/dbcp2/managed/PoolableManagedConnection.java
@@ -33,7 +33,7 @@ public class PoolableManagedConnection extends PoolableConnection {
     private final TransactionRegistry transactionRegistry;
 
     /**
-     * Create a PoolableManagedConnection.
+     * Creates a PoolableManagedConnection.
      *
      * @param transactionRegistry
      *            transaction registry
@@ -42,13 +42,12 @@ public class PoolableManagedConnection extends PoolableConnection {
      * @param pool
      *            connection pool
      */
-    public PoolableManagedConnection(final TransactionRegistry transactionRegistry, final Connection conn,
-            final ObjectPool<PoolableConnection> pool) {
+    public PoolableManagedConnection(final TransactionRegistry transactionRegistry, final Connection conn, final ObjectPool<PoolableConnection> pool) {
         this(transactionRegistry, conn, pool, null, true);
     }
 
     /**
-     * Create a PoolableManagedConnection.
+     * Creates a PoolableManagedConnection.
      *
      * @param transactionRegistry
      *            transaction registry
@@ -57,19 +56,43 @@ public class PoolableManagedConnection extends PoolableConnection {
      * @param pool
      *            connection pool
      * @param disconnectSqlCodes
-     *            SQL_STATE codes considered fatal disconnection errors
+     *            SQL State codes considered fatal disconnection errors
      * @param fastFailValidation
      *            true means fatal disconnection errors cause subsequent validations to fail immediately (no attempt to
      *            run query or isValid)
      */
-    public PoolableManagedConnection(final TransactionRegistry transactionRegistry, final Connection conn,
-            final ObjectPool<PoolableConnection> pool, final Collection<String> disconnectSqlCodes,
-            final boolean fastFailValidation) {
-        super(conn, pool, null, disconnectSqlCodes, fastFailValidation);
+    public PoolableManagedConnection(final TransactionRegistry transactionRegistry, final Connection conn, final ObjectPool<PoolableConnection> pool,
+            final Collection<String> disconnectSqlCodes, final boolean fastFailValidation) {
+        this(transactionRegistry, conn, pool, disconnectSqlCodes, null, fastFailValidation);
+    }
+
+    /**
+     * Creates a PoolableManagedConnection.
+     *
+     * @param transactionRegistry
+     *            transaction registry
+     * @param conn
+     *            underlying connection
+     * @param pool
+     *            connection pool
+     * @param disconnectSqlCodes
+     *            SQL State codes considered fatal disconnection errors
+     * @param disconnectionIgnoreSqlCodes
+     *            SQL State codes considered fatal disconnection errors
+     * @param fastFailValidation
+     *            true means fatal disconnection errors cause subsequent validations to fail immediately (no attempt to
+     *            run query or isValid)
+     * @since 2.13.0
+     */
+    public PoolableManagedConnection(final TransactionRegistry transactionRegistry, final Connection conn, final ObjectPool<PoolableConnection> pool,
+            final Collection<String> disconnectSqlCodes, final Collection<String> disconnectionIgnoreSqlCodes, final boolean fastFailValidation) {
+        super(conn, pool, null, disconnectSqlCodes, disconnectionIgnoreSqlCodes, fastFailValidation);
         this.transactionRegistry = transactionRegistry;
     }
 
     /**
+     * Gets the transaction registry.
+     *
      * @return The transaction registry.
      * @since 2.6.0
      */
diff --git a/java/org/apache/tomcat/dbcp/dbcp2/managed/PoolableManagedConnectionFactory.java b/java/org/apache/tomcat/dbcp/dbcp2/managed/PoolableManagedConnectionFactory.java
index 28c2046cd1..bbd5a4e2c6 100644
--- a/java/org/apache/tomcat/dbcp/dbcp2/managed/PoolableManagedConnectionFactory.java
+++ b/java/org/apache/tomcat/dbcp/dbcp2/managed/PoolableManagedConnectionFactory.java
@@ -58,6 +58,8 @@ public class PoolableManagedConnectionFactory extends PoolableConnectionFactory
     }
 
     /**
+     * Gets the transaction registry.
+     *
      * @return The transaction registry.
      * @since 2.6.0
      */
@@ -103,7 +105,7 @@ public class PoolableManagedConnectionFactory extends PoolableConnectionFactory
             ((PoolingConnection) conn).setCacheState(getCacheState());
         }
         final PoolableManagedConnection pmc = new PoolableManagedConnection(transactionRegistry, conn, getPool(),
-                getDisconnectionSqlCodes(), isFastFailValidation());
+                getDisconnectionSqlCodes(), getDisconnectionIgnoreSqlCodes(), isFastFailValidation());
         pmc.setCacheState(getCacheState());
         return new DefaultPooledObject<>(pmc);
     }
diff --git a/java/org/apache/tomcat/dbcp/dbcp2/managed/TransactionContext.java b/java/org/apache/tomcat/dbcp/dbcp2/managed/TransactionContext.java
index 1a8823483f..a1d84745dc 100644
--- a/java/org/apache/tomcat/dbcp/dbcp2/managed/TransactionContext.java
+++ b/java/org/apache/tomcat/dbcp/dbcp2/managed/TransactionContext.java
@@ -162,7 +162,6 @@ public class TransactionContext {
      * Gets the transaction complete flag to true.
      *
      * @return The transaction complete flag.
-     *
      * @since 2.4.0
      */
     public boolean isTransactionComplete() {
diff --git a/java/org/apache/tomcat/dbcp/dbcp2/managed/TransactionContextListener.java b/java/org/apache/tomcat/dbcp/dbcp2/managed/TransactionContextListener.java
index 10dd34a02b..c447ecb01d 100644
--- a/java/org/apache/tomcat/dbcp/dbcp2/managed/TransactionContextListener.java
+++ b/java/org/apache/tomcat/dbcp/dbcp2/managed/TransactionContextListener.java
@@ -22,13 +22,14 @@ package org.apache.tomcat.dbcp.dbcp2.managed;
  * @since 2.0
  */
 public interface TransactionContextListener {
+
     /**
-     * Occurs after the transaction commits or rolls back.
+     * Called after a transaction commits or rolls back.
      *
      * @param transactionContext
-     *            the transaction context that completed
+     *            the transaction context that completed.
      * @param committed
-     *            true if the transaction committed; false otherwise
+     *            true if the transaction committed; false otherwise.
      */
     void afterCompletion(TransactionContext transactionContext, boolean committed);
 }
diff --git a/java/org/apache/tomcat/dbcp/dbcp2/managed/XAConnectionFactory.java b/java/org/apache/tomcat/dbcp/dbcp2/managed/XAConnectionFactory.java
index b9913fbb42..07b6dec7d8 100644
--- a/java/org/apache/tomcat/dbcp/dbcp2/managed/XAConnectionFactory.java
+++ b/java/org/apache/tomcat/dbcp/dbcp2/managed/XAConnectionFactory.java
@@ -30,16 +30,16 @@ import org.apache.tomcat.dbcp.dbcp2.ConnectionFactory;
  * @since 2.0
  */
 public interface XAConnectionFactory extends ConnectionFactory {
+
     /**
-     * Create a new {@link java.sql.Connection} in an implementation specific fashion.
+     * Creates a new {@link Connection} in an implementation specific fashion.
      * <p>
      * An implementation can assume that the caller of this will wrap the connection in a proxy that protects access to
      * the setAutoCommit, commit and rollback when enrolled in a XA transaction.
      * </p>
      *
-     * @return a new {@link java.sql.Connection}
-     * @throws java.sql.SQLException
-     *             if a database error occurs creating the connection
+     * @return a new {@link Connection}
+     * @throws SQLException if a database error occurs creating the connection
      */
     @Override
     Connection createConnection() throws SQLException;
diff --git a/java/org/apache/tomcat/dbcp/dbcp2/managed/package-info.java b/java/org/apache/tomcat/dbcp/dbcp2/managed/package-info.java
index a439660a21..ca00170f9c 100644
--- a/java/org/apache/tomcat/dbcp/dbcp2/managed/package-info.java
+++ b/java/org/apache/tomcat/dbcp/dbcp2/managed/package-info.java
@@ -19,7 +19,7 @@
  * <p>
  * This package provides support for pooling of ManagedConnections. A managed
  * connection is responsible for managing a database connection in a
- * transactional environment (typically called <i>Container Managed</i>).
+ * transactional environment (typically called <em>Container Managed</em>).
  * A managed connection operates like any other connection when no global
  * transaction (a.k.a. XA transaction or JTA Transaction) is in progress.
  * When a global transaction is active a single physical connection to the
diff --git a/java/org/apache/tomcat/dbcp/dbcp2/package-info.java b/java/org/apache/tomcat/dbcp/dbcp2/package-info.java
index 58a517589d..cfccb0df96 100644
--- a/java/org/apache/tomcat/dbcp/dbcp2/package-info.java
+++ b/java/org/apache/tomcat/dbcp/dbcp2/package-info.java
@@ -19,7 +19,7 @@
  * <p>
  * Database Connection Pool API.
  * </p>
- * <b>Overview in Dialog Form</b>
+ * <strong>Overview in Dialog Form</strong>
  * <p>
  * Q: How do I use the DBCP package?
  * </p>
diff --git a/webapps/docs/changelog.xml b/webapps/docs/changelog.xml
index 6a1d6f7824..d341f87b3f 100644
--- a/webapps/docs/changelog.xml
+++ b/webapps/docs/changelog.xml
@@ -301,6 +301,9 @@
         <bug>69465</bug>: Fix warnings during native image compilation using the
         Tomcat embedded JARs. (markt)
       </fix>
+      <update>
+        Update Tomcat's fork of Commons DBCP to 2.13.0. (markt)
+      </update>
     </changelog>
   </subsection>
 </section>


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