diff --git a/pom.xml b/pom.xml
index d98662ab4de57e7b776e92e78917c1be7332fbad..8919c18d5491644b90c93cb2f0543a4f511aed4d 100644
--- a/pom.xml
+++ b/pom.xml
@@ -10,7 +10,7 @@
     <artifactId>postgresql</artifactId>
     <packaging>jar</packaging>
     <name>PostgreSQL JDBC Driver - JDBC 4.2</name>
-    <version>42.4.0</version>
+    <version>42.4.1</version>
     <description>Java JDBC 4.2 (JRE 8+) driver for PostgreSQL database</description>
     <url>https://github.com/pgjdbc/pgjdbc</url>
 
@@ -60,30 +60,30 @@
         <dependency>
             <groupId>org.junit.jupiter</groupId>
             <artifactId>junit-jupiter-api</artifactId>
-            <version>5.6.0</version>
+            <version>5.8.2</version>
             <scope>test</scope>
         </dependency>
         <dependency>
             <groupId>uk.org.webcompere</groupId>
             <artifactId>system-stubs-jupiter</artifactId>
-            <version>1.2.0</version>
+            <version>2.0.1</version>
         </dependency>
         <dependency>
             <groupId>org.junit.jupiter</groupId>
             <artifactId>junit-jupiter-params</artifactId>
-            <version>5.6.0</version>
+            <version>5.8.2</version>
             <scope>test</scope>
         </dependency>
         <dependency>
             <groupId>org.junit.jupiter</groupId>
             <artifactId>junit-jupiter-engine</artifactId>
-            <version>5.6.0</version>
+            <version>5.8.2</version>
             <scope>test</scope>
         </dependency>
         <dependency>
             <groupId>org.junit.vintage</groupId>
             <artifactId>junit-vintage-engine</artifactId>
-            <version>5.6.0</version>
+            <version>5.8.2</version>
             <scope>test</scope>
         </dependency>
     </dependencies>
diff --git a/src/main/java/org/postgresql/Driver.java b/src/main/java/org/postgresql/Driver.java
index 569a56a3597b18bb1ad46da1e3905067c38425fc..7e893b82745f33b679adaec2332b6772e1efc481 100644
--- a/src/main/java/org/postgresql/Driver.java
+++ b/src/main/java/org/postgresql/Driver.java
@@ -23,8 +23,9 @@ import org.postgresql.util.URLCoder;
 
 import java.io.IOException;
 import java.io.InputStream;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
 import java.net.URL;
-import java.security.AccessController;
 import java.security.PrivilegedActionException;
 import java.security.PrivilegedExceptionAction;
 import java.sql.Connection;
@@ -88,18 +89,47 @@ public class Driver implements java.sql.Driver {
     // Make sure we load properties with the maximum possible privileges.
     try {
       defaultProperties =
-          AccessController.doPrivileged(new PrivilegedExceptionAction<Properties>() {
+          doPrivileged(new PrivilegedExceptionAction<Properties>() {
             public Properties run() throws IOException {
               return loadDefaultProperties();
             }
           });
     } catch (PrivilegedActionException e) {
-      throw (IOException) e.getException();
+      Exception ex = e.getException();
+      if (ex instanceof IOException) {
+        throw (IOException) ex;
+      }
+      throw new RuntimeException(e);
+    } catch (Throwable e) {
+      if (e instanceof IOException) {
+        throw (IOException) e;
+      }
+      if (e instanceof RuntimeException) {
+        throw (RuntimeException) e;
+      }
+      if (e instanceof Error) {
+        throw (Error) e;
+      }
+      throw new RuntimeException(e);
     }
 
     return defaultProperties;
   }
 
+  private static <T> T doPrivileged(PrivilegedExceptionAction<T> action) throws Throwable {
+    try {
+      Class<?> accessControllerClass = Class.forName("java.security.AccessController");
+      Method doPrivileged = accessControllerClass.getMethod("doPrivileged",
+          PrivilegedExceptionAction.class);
+      //noinspection unchecked
+      return (T) doPrivileged.invoke(null, action);
+    } catch (ClassNotFoundException e) {
+      return action.run();
+    } catch (InvocationTargetException e) {
+      throw castNonNull(e.getCause());
+    }
+  }
+
   private Properties loadDefaultProperties() throws IOException {
     Properties merged = new Properties();
 
@@ -241,7 +271,7 @@ public class Driver implements java.sql.Driver {
     // parse URL and add more properties
     if ((props = parseURL(url, props)) == null) {
       throw new PSQLException(
-          GT.tr("Unable to parse URL "),
+          GT.tr("Unable to parse URL {0}", url),
           PSQLState.UNEXPECTED_ERROR);
     }
     try {
@@ -271,12 +301,14 @@ public class Driver implements java.sql.Driver {
       // re-throw the exception, otherwise it will be caught next, and a
       // org.postgresql.unusual error will be returned instead.
       throw ex1;
-    } catch (java.security.AccessControlException ace) {
-      throw new PSQLException(
-          GT.tr(
-              "Your security policy has prevented the connection from being attempted.  You probably need to grant the connect java.net.SocketPermission to the database server host and port that you wish to connect to."),
-          PSQLState.UNEXPECTED_ERROR, ace);
     } catch (Exception ex2) {
+      if ("java.security.AccessControlException".equals(ex2.getClass().getName())) {
+        // java.security.AccessControlException has been deprecated for removal, so compare the class name
+        throw new PSQLException(
+            GT.tr(
+                "Your security policy has prevented the connection from being attempted.  You probably need to grant the connect java.net.SocketPermission to the database server host and port that you wish to connect to."),
+            PSQLState.UNEXPECTED_ERROR, ex2);
+      }
       LOGGER.log(Level.FINE, "Unexpected connection error: ", ex2);
       throw new PSQLException(
           GT.tr(
diff --git a/src/main/java/org/postgresql/gss/GssAction.java b/src/main/java/org/postgresql/gss/GssAction.java
index 39c320dc3a514b4f89e92ce903e3c1cf922ab0c0..5cf5de3752d84d1ea80d32bc5d3f24f50c9d2b6f 100644
--- a/src/main/java/org/postgresql/gss/GssAction.java
+++ b/src/main/java/org/postgresql/gss/GssAction.java
@@ -37,10 +37,10 @@ class GssAction implements PrivilegedAction</* @Nullable */ Exception> {
   private final String kerberosServerName;
   private final String user;
   private final boolean useSpnego;
-  private final Subject subject;
+  private final /* @Nullable */ Subject subject;
   private final boolean logServerErrorDetail;
 
-  GssAction(PGStream pgStream, Subject subject, String host, String user,
+  GssAction(PGStream pgStream, /* @Nullable */ Subject subject, String host, String user,
       String kerberosServerName, boolean useSpnego, boolean logServerErrorDetail) {
     this.pgStream = pgStream;
     this.subject = subject;
diff --git a/src/main/java/org/postgresql/gss/GssEncAction.java b/src/main/java/org/postgresql/gss/GssEncAction.java
index d1bccea4fc20de1d90644a42b7ec17a23118d16b..b1d080e43e6161f2a3b3994a13b577b5fe201563 100644
--- a/src/main/java/org/postgresql/gss/GssEncAction.java
+++ b/src/main/java/org/postgresql/gss/GssEncAction.java
@@ -23,22 +23,23 @@ import java.security.Principal;
 import java.security.PrivilegedAction;
 import java.util.Iterator;
 import java.util.Set;
+import java.util.concurrent.Callable;
 import java.util.logging.Level;
 import java.util.logging.Logger;
 
 import javax.security.auth.Subject;
 
-public class GssEncAction implements PrivilegedAction</* @Nullable */ Exception> {
+public class GssEncAction implements PrivilegedAction</* @Nullable */ Exception>, Callable</* @Nullable */ Exception> {
   private static final Logger LOGGER = Logger.getLogger(GssAction.class.getName());
   private final PGStream pgStream;
   private final String host;
   private final String user;
   private final String kerberosServerName;
   private final boolean useSpnego;
-  private final Subject subject;
+  private final /* @Nullable */ Subject subject;
   private final boolean logServerErrorDetail;
 
-  public GssEncAction(PGStream pgStream,  Subject subject,
+  public GssEncAction(PGStream pgStream, /* @Nullable */ Subject subject,
       String host, String user,
       String kerberosServerName, boolean useSpnego, boolean logServerErrorDetail) {
     this.pgStream = pgStream;
@@ -150,4 +151,8 @@ public class GssEncAction implements PrivilegedAction</* @Nullable */ Exception>
     return null;
   }
 
+  @Override
+  public /* @Nullable */ Exception call() throws Exception {
+    return run();
+  }
 }
diff --git a/src/main/java/org/postgresql/gss/MakeGSS.java b/src/main/java/org/postgresql/gss/MakeGSS.java
index 7c29fae5648d5894479ccd69acba1ca0c9dd4306..f407decad3ec3b1061851f99e87624d944a0b4c5 100644
--- a/src/main/java/org/postgresql/gss/MakeGSS.java
+++ b/src/main/java/org/postgresql/gss/MakeGSS.java
@@ -13,13 +13,18 @@ import org.postgresql.util.GT;
 import org.postgresql.util.PSQLException;
 import org.postgresql.util.PSQLState;
 
+// import org.checkerframework.checker.nullness.qual.NonNull;
 // import org.checkerframework.checker.nullness.qual.Nullable;
 import org.ietf.jgss.GSSCredential;
 
 import java.io.IOException;
-import java.security.AccessController;
+import java.lang.invoke.MethodHandle;
+import java.lang.invoke.MethodHandles;
+import java.lang.invoke.MethodType;
 import java.security.PrivilegedAction;
+import java.security.PrivilegedExceptionAction;
 import java.util.Set;
+import java.util.concurrent.Callable;
 import java.util.logging.Level;
 import java.util.logging.Logger;
 
@@ -28,6 +33,87 @@ import javax.security.auth.login.LoginContext;
 
 public class MakeGSS {
   private static final Logger LOGGER = Logger.getLogger(MakeGSS.class.getName());
+  private static final /* @Nullable */ MethodHandle SUBJECT_CURRENT;
+  private static final /* @Nullable */ MethodHandle ACCESS_CONTROLLER_GET_CONTEXT;
+  private static final /* @Nullable */ MethodHandle SUBJECT_GET_SUBJECT;
+  // Java <18
+  private static final /* @Nullable */ MethodHandle SUBJECT_DO_AS;
+  // Java 18+, see https://bugs.openjdk.org/browse/JDK-8267108
+  private static final /* @Nullable */ MethodHandle SUBJECT_CALL_AS;
+
+  static {
+    MethodHandle subjectCurrent = null;
+    try {
+      subjectCurrent = MethodHandles.lookup()
+          .findStatic(Subject.class, "current", MethodType.methodType(Subject.class));
+    } catch (NoSuchMethodException | IllegalAccessException ignore) {
+      // E.g. pre Java 18
+    }
+    SUBJECT_CURRENT = subjectCurrent;
+
+    MethodHandle accessControllerGetContext = null;
+    MethodHandle subjectGetSubject = null;
+
+    try {
+      Class<?> accessControllerClass = Class.forName("java.security.AccessController");
+      Class<?> accessControlContextClass =
+          Class.forName("java.security.AccessControlContext");
+      accessControllerGetContext = MethodHandles.lookup()
+          .findStatic(accessControllerClass, "getContext",
+              MethodType.methodType(accessControlContextClass));
+      subjectGetSubject = MethodHandles.lookup()
+          .findStatic(Subject.class, "getSubject",
+              MethodType.methodType(Subject.class, accessControlContextClass));
+    } catch (NoSuchMethodException | IllegalAccessException | ClassNotFoundException ignore) {
+      // E.g. pre Java 18+
+    }
+
+    ACCESS_CONTROLLER_GET_CONTEXT = accessControllerGetContext;
+    SUBJECT_GET_SUBJECT = subjectGetSubject;
+
+    MethodHandle subjectDoAs = null;
+    try {
+      subjectDoAs = MethodHandles.lookup().findStatic(Subject.class, "doAs",
+          MethodType.methodType(Object.class, Subject.class, PrivilegedExceptionAction.class));
+    } catch (NoSuchMethodException | IllegalAccessException ignore) {
+    }
+    SUBJECT_DO_AS = subjectDoAs;
+
+    MethodHandle subjectCallAs = null;
+    try {
+      subjectDoAs = MethodHandles.lookup().findStatic(Subject.class, "callAs",
+          MethodType.methodType(Object.class, Subject.class, Callable.class));
+    } catch (NoSuchMethodException | IllegalAccessException ignore) {
+    }
+    SUBJECT_CALL_AS = subjectCallAs;
+  }
+
+  /**
+   * Use {@code Subject.current()} in Java 18+, and
+   * {@code Subject.getSubject(AccessController.getContext())} in Java before 18.
+   * @return current Subject or null
+   */
+  private static /* @Nullable */ Subject getCurrentSubject() {
+    try {
+      if (SUBJECT_CURRENT != null) {
+        return (Subject) SUBJECT_CURRENT.invokeExact();
+      }
+      if (SUBJECT_GET_SUBJECT == null || ACCESS_CONTROLLER_GET_CONTEXT == null) {
+        return null;
+      }
+      return (Subject) SUBJECT_GET_SUBJECT.invoke(
+          ACCESS_CONTROLLER_GET_CONTEXT.invokeExact()
+      );
+    } catch (Throwable e) {
+      if (e instanceof RuntimeException) {
+        throw (RuntimeException) e;
+      }
+      if (e instanceof Error) {
+        throw (Error) e;
+      }
+      throw new RuntimeException(e);
+    }
+  }
 
   public static void authenticate(boolean encrypted,
       PGStream pgStream, String host, String user, char /* @Nullable */ [] password,
@@ -49,7 +135,7 @@ public class MakeGSS {
       boolean performAuthentication = jaasLogin;
 
       //Check if we can get credential from subject to avoid login.
-      Subject sub = Subject.getSubject(AccessController.getContext());
+      Subject sub = getCurrentSubject();
       if (sub != null) {
         Set<GSSCredential> gssCreds = sub.getPrivateCredentials(GSSCredential.class);
         if (gssCreds != null && !gssCreds.isEmpty()) {
@@ -62,18 +148,28 @@ public class MakeGSS {
         sub = lc.getSubject();
       }
 
+      PrivilegedAction</* @Nullable */ Exception> action;
       if ( encrypted ) {
-        PrivilegedAction</* @Nullable */ Exception> action = new GssEncAction(pgStream, sub, host, user,
+        action = new GssEncAction(pgStream, sub, host, user,
             kerberosServerName, useSpnego, logServerErrorDetail);
-
-        result = Subject.doAs(sub, action);
       } else {
-        PrivilegedAction</* @Nullable */ Exception> action = new GssAction(pgStream, sub, host, user,
+        action = new GssAction(pgStream, sub, host, user,
             kerberosServerName, useSpnego, logServerErrorDetail);
-
-        result = Subject.doAs(sub, action);
       }
-    } catch (Exception e) {
+      //noinspection ConstantConditions
+      @SuppressWarnings({"cast.unsafe", "assignment.type.incompatible"})
+      /* @NonNull */ Subject subject = sub;
+      if (SUBJECT_DO_AS != null) {
+        result = (Exception) SUBJECT_DO_AS.invoke(subject, action);
+      } else if (SUBJECT_CALL_AS != null) {
+        //noinspection ConstantConditions,unchecked
+        result = (Exception) SUBJECT_CALL_AS.invoke(subject, (Callable<Exception>) action);
+      } else {
+        throw new PSQLException(
+            GT.tr("Neither Subject.doAs (Java before 18) nor Subject.callAs (Java 18+) method found"),
+            PSQLState.OBJECT_NOT_IN_STATE);
+      }
+    } catch (Throwable e) {
       throw new PSQLException(GT.tr("GSS Authentication failed"), PSQLState.CONNECTION_FAILURE, e);
     }
 
diff --git a/src/main/java/org/postgresql/jdbc/PgConnection.java b/src/main/java/org/postgresql/jdbc/PgConnection.java
index 37ed7f32d9a2050323e247cba2da3968698ff563..98fc8431bcf7566d01118c924ff8c2eedc2c51c8 100644
--- a/src/main/java/org/postgresql/jdbc/PgConnection.java
+++ b/src/main/java/org/postgresql/jdbc/PgConnection.java
@@ -47,6 +47,10 @@ import org.postgresql.xml.PGXmlFactoryFactory;
 // import org.checkerframework.dataflow.qual.Pure;
 
 import java.io.IOException;
+import java.lang.invoke.MethodHandle;
+import java.lang.invoke.MethodHandles;
+import java.lang.invoke.MethodType;
+import java.security.Permission;
 import java.sql.Array;
 import java.sql.Blob;
 import java.sql.CallableStatement;
@@ -89,6 +93,26 @@ public class PgConnection implements BaseConnection {
   private static final SQLPermission SQL_PERMISSION_ABORT = new SQLPermission("callAbort");
   private static final SQLPermission SQL_PERMISSION_NETWORK_TIMEOUT = new SQLPermission("setNetworkTimeout");
 
+  private static final /* @Nullable */ MethodHandle SYSTEM_GET_SECURITY_MANAGER;
+  private static final /* @Nullable */ MethodHandle SECURITY_MANAGER_CHECK_PERMISSION;
+
+  static {
+    MethodHandle systemGetSecurityManagerHandle = null;
+    MethodHandle securityManagerCheckPermission = null;
+    try {
+      Class<?> securityManagerClass = Class.forName("java.lang.SecurityManager");
+      systemGetSecurityManagerHandle =
+          MethodHandles.lookup().findStatic(System.class, "getSecurityManager",
+              MethodType.methodType(securityManagerClass));
+      securityManagerCheckPermission =
+          MethodHandles.lookup().findVirtual(securityManagerClass, "checkPermission",
+              MethodType.methodType(void.class, Permission.class));
+    } catch (NoSuchMethodException | IllegalAccessException | ClassNotFoundException ignore) {
+    }
+    SYSTEM_GET_SECURITY_MANAGER = systemGetSecurityManagerHandle;
+    SECURITY_MANAGER_CHECK_PERMISSION = securityManagerCheckPermission;
+  }
+
   private enum ReadOnlyBehavior {
     ignore,
     transaction,
@@ -1624,10 +1648,7 @@ public class PgConnection implements BaseConnection {
               PSQLState.INVALID_PARAMETER_VALUE);
     }
 
-    SecurityManager securityManager = System.getSecurityManager();
-    if (securityManager != null) {
-      securityManager.checkPermission(SQL_PERMISSION_NETWORK_TIMEOUT);
-    }
+    checkPermission(SQL_PERMISSION_NETWORK_TIMEOUT);
 
     try {
       queryExecutor.setNetworkTimeout(milliseconds);
@@ -1637,6 +1658,19 @@ public class PgConnection implements BaseConnection {
     }
   }
 
+  private void checkPermission(SQLPermission sqlPermissionNetworkTimeout) {
+    if (SYSTEM_GET_SECURITY_MANAGER != null && SECURITY_MANAGER_CHECK_PERMISSION != null) {
+      try {
+        Object securityManager = SYSTEM_GET_SECURITY_MANAGER.invoke();
+        if (securityManager != null) {
+          SECURITY_MANAGER_CHECK_PERMISSION.invoke(securityManager, sqlPermissionNetworkTimeout);
+        }
+      } catch (Throwable e) {
+        throw new RuntimeException(e);
+      }
+    }
+  }
+
   public int getNetworkTimeout() throws SQLException {
     checkClosed();
 
diff --git a/src/main/java/org/postgresql/jdbc/PgPreparedStatement.java b/src/main/java/org/postgresql/jdbc/PgPreparedStatement.java
index 49954c2407704f8ca7d4ef756f8b31b4ef472fb5..2cb7c76ae9cb9b39e52d166121b34108a3ac34c5 100644
--- a/src/main/java/org/postgresql/jdbc/PgPreparedStatement.java
+++ b/src/main/java/org/postgresql/jdbc/PgPreparedStatement.java
@@ -99,7 +99,7 @@ class PgPreparedStatement extends PgStatement implements PreparedStatement {
     int maxSupportedParameters = maximumNumberOfParameters();
     if (parameterCount > maxSupportedParameters) {
       throw new PSQLException(
-          GT.tr("PreparedStatement can have at most {0} parameters. Please consider using arrays, or splitting the query in several ones, or using COPY. Given query has {0} parameters",
+          GT.tr("PreparedStatement can have at most {0} parameters. Please consider using arrays, or splitting the query in several ones, or using COPY. Given query has {1} parameters",
               maxSupportedParameters,
               parameterCount),
           PSQLState.INVALID_PARAMETER_VALUE);
diff --git a/src/main/java/org/postgresql/jdbc/PgResultSet.java b/src/main/java/org/postgresql/jdbc/PgResultSet.java
index 1252c3bd0e46c4822ee33ac9ebfb981a3f7d9c31..9873a358a3882bc6f7bc1dba70e0dc8495fc9b9f 100644
--- a/src/main/java/org/postgresql/jdbc/PgResultSet.java
+++ b/src/main/java/org/postgresql/jdbc/PgResultSet.java
@@ -1418,7 +1418,7 @@ public class PgResultSet implements ResultSet, org.postgresql.PGRefCursorResultS
       if (i > 1) {
         selectSQL.append(", ");
       }
-      selectSQL.append(pgmd.getBaseColumnName(i));
+      Utils.escapeIdentifier(selectSQL, pgmd.getBaseColumnName(i));
     }
     selectSQL.append(" from ").append(onlyTable).append(tableName).append(" where ");
 
@@ -1428,7 +1428,8 @@ public class PgResultSet implements ResultSet, org.postgresql.PGRefCursorResultS
     for (int i = 0; i < numKeys; i++) {
 
       PrimaryKey primaryKey = primaryKeys.get(i);
-      selectSQL.append(primaryKey.name).append(" = ?");
+      Utils.escapeIdentifier(selectSQL, primaryKey.name);
+      selectSQL.append(" = ?");
 
       if (i < numKeys - 1) {
         selectSQL.append(" and ");
diff --git a/src/main/java/org/postgresql/util/DriverInfo.java b/src/main/java/org/postgresql/util/DriverInfo.java
index b85d5ba814ebe62625f83736c2e2565f1e6f38fd..d759f11da7a045c6bff805c13f7b904e676cce6a 100644
--- a/src/main/java/org/postgresql/util/DriverInfo.java
+++ b/src/main/java/org/postgresql/util/DriverInfo.java
@@ -16,13 +16,13 @@ public final class DriverInfo {
   // Driver name
   public static final String DRIVER_NAME = "PostgreSQL JDBC Driver";
   public static final String DRIVER_SHORT_NAME = "PgJDBC";
-  public static final String DRIVER_VERSION = "42.4.0";
+  public static final String DRIVER_VERSION = "42.4.1";
   public static final String DRIVER_FULL_NAME = DRIVER_NAME + " " + DRIVER_VERSION;
 
   // Driver version
   public static final int MAJOR_VERSION = 42;
   public static final int MINOR_VERSION = 4;
-  public static final int PATCH_VERSION = 0;
+  public static final int PATCH_VERSION = 1;
 
   // JDBC specification
   public static final String JDBC_VERSION = "4.2";
diff --git a/src/main/java/org/postgresql/util/StreamWrapper.java b/src/main/java/org/postgresql/util/StreamWrapper.java
index f449c5f2030833a48c59ca2aadaa1a1adc42aeb8..c4027ceb0c54f4412aebaaaad7eeedfd21bffc1d 100644
--- a/src/main/java/org/postgresql/util/StreamWrapper.java
+++ b/src/main/java/org/postgresql/util/StreamWrapper.java
@@ -117,6 +117,7 @@ public class StreamWrapper {
             }
           }
 
+          @SuppressWarnings({"deprecation", "removal"})
           protected void finalize() throws IOException {
             // forcibly close it because super.finalize() may keep the FD open, which may prevent
             // file deletion
diff --git a/src/main/resources/META-INF/MANIFEST.MF b/src/main/resources/META-INF/MANIFEST.MF
index 5d090da75f2b6a64f83c7026d028917e5382467a..2da7f8f45e2bb873b9c0fb0acc29835093d37dbe 100644
--- a/src/main/resources/META-INF/MANIFEST.MF
+++ b/src/main/resources/META-INF/MANIFEST.MF
@@ -2,7 +2,7 @@ Manifest-Version: 1.0
 Implementation-Title: PostgreSQL JDBC Driver
 Bundle-License: BSD-2-Clause
 Automatic-Module-Name: org.postgresql.jdbc
-Implementation-Version: 42.4.0
+Implementation-Version: 42.4.1
 Specification-Vendor: Oracle Corporation
 Specification-Title: JDBC
 Implementation-Vendor-Id: org.postgresql
diff --git a/src/test/java/org/postgresql/test/XaTests.java b/src/test/java/org/postgresql/test/XaTests.java
new file mode 100644
index 0000000000000000000000000000000000000000..00df0f3616a465f884cab6107289b9347f23d328
--- /dev/null
+++ b/src/test/java/org/postgresql/test/XaTests.java
@@ -0,0 +1,12 @@
+/*
+ * Copyright (c) 2022, PostgreSQL Global Development Group
+ * See the LICENSE file in the project root for more information.
+ */
+
+package org.postgresql.test;
+
+/**
+ * Declares interface to specify XA tests.
+ */
+public interface XaTests {
+}
diff --git a/src/test/java/org/postgresql/test/jdbc2/BaseTest4.java b/src/test/java/org/postgresql/test/jdbc2/BaseTest4.java
index fdd2e20806da12e87434a5d562344b87a58e03c0..8a7a3d723d9fb11aed3a35cda735691e1b979071 100644
--- a/src/test/java/org/postgresql/test/jdbc2/BaseTest4.java
+++ b/src/test/java/org/postgresql/test/jdbc2/BaseTest4.java
@@ -93,12 +93,18 @@ public class BaseTest4 {
 
   public void assumeByteaSupported() {
     Assume.assumeTrue("bytea is not supported in simple protocol execution mode",
-        preferQueryMode.compareTo(PreferQueryMode.EXTENDED) >= 0);
+        preferQueryMode != PreferQueryMode.SIMPLE);
+  }
+
+  public static void assumeCallableStatementsSupported(Connection con) throws SQLException {
+    PreferQueryMode preferQueryMode = con.unwrap(PGConnection.class).getPreferQueryMode();
+    Assume.assumeTrue("callable statements are not fully supported in simple protocol execution mode",
+        preferQueryMode != PreferQueryMode.SIMPLE);
   }
 
   public void assumeCallableStatementsSupported() {
     Assume.assumeTrue("callable statements are not fully supported in simple protocol execution mode",
-        preferQueryMode.compareTo(PreferQueryMode.EXTENDED) >= 0);
+        preferQueryMode != PreferQueryMode.SIMPLE);
   }
 
   public void assumeBinaryModeRegular() {
diff --git a/src/test/java/org/postgresql/test/jdbc2/CallableStmtTest.java b/src/test/java/org/postgresql/test/jdbc2/CallableStmtTest.java
index 21139091156dde64bcb9eeca5f69b7effaea97fc..331393bcef11e5aae21b9e1ec43e632f0eb3381f 100644
--- a/src/test/java/org/postgresql/test/jdbc2/CallableStmtTest.java
+++ b/src/test/java/org/postgresql/test/jdbc2/CallableStmtTest.java
@@ -13,10 +13,12 @@ import static org.junit.Assert.fail;
 
 import org.postgresql.test.TestUtil;
 
+import org.junit.BeforeClass;
 import org.junit.Test;
 
 import java.sql.Array;
 import java.sql.CallableStatement;
+import java.sql.Connection;
 import java.sql.ResultSet;
 import java.sql.SQLException;
 import java.sql.SQLWarning;
@@ -29,6 +31,12 @@ import java.sql.Types;
  * @author Paul Bethe
  */
 public class CallableStmtTest extends BaseTest4 {
+  @BeforeClass
+  public static void beforeClass() throws Exception {
+    try (Connection con = TestUtil.openDB();) {
+      assumeCallableStatementsSupported(con);
+    }
+  }
 
   @Override
   public void setUp() throws Exception {
@@ -99,7 +107,6 @@ public class CallableStmtTest extends BaseTest4 {
 
   @Test
   public void testGetUpdateCount() throws SQLException {
-    assumeCallableStatementsSupported();
     CallableStatement call = con.prepareCall(func + pkgName + "getDouble (?) }");
     call.setDouble(2, 3.04);
     call.registerOutParameter(1, Types.DOUBLE);
@@ -128,7 +135,6 @@ public class CallableStmtTest extends BaseTest4 {
 
   @Test
   public void testGetDouble() throws Throwable {
-    assumeCallableStatementsSupported();
     CallableStatement call = con.prepareCall(func + pkgName + "getDouble (?) }");
     call.setDouble(2, 3.04);
     call.registerOutParameter(1, Types.DOUBLE);
@@ -147,7 +153,6 @@ public class CallableStmtTest extends BaseTest4 {
 
   @Test
   public void testGetInt() throws Throwable {
-    assumeCallableStatementsSupported();
     CallableStatement call = con.prepareCall(func + pkgName + "getInt (?) }");
     call.setInt(2, 4);
     call.registerOutParameter(1, Types.INTEGER);
@@ -157,7 +162,6 @@ public class CallableStmtTest extends BaseTest4 {
 
   @Test
   public void testGetShort() throws Throwable {
-    assumeCallableStatementsSupported();
     CallableStatement call = con.prepareCall(func + pkgName + "getShort (?) }");
     call.setShort(2, (short) 4);
     call.registerOutParameter(1, Types.SMALLINT);
@@ -167,7 +171,6 @@ public class CallableStmtTest extends BaseTest4 {
 
   @Test
   public void testGetNumeric() throws Throwable {
-    assumeCallableStatementsSupported();
     CallableStatement call = con.prepareCall(func + pkgName + "getNumeric (?) }");
     call.setBigDecimal(2, new java.math.BigDecimal(4));
     call.registerOutParameter(1, Types.NUMERIC);
@@ -177,7 +180,6 @@ public class CallableStmtTest extends BaseTest4 {
 
   @Test
   public void testGetNumericWithoutArg() throws Throwable {
-    assumeCallableStatementsSupported();
     CallableStatement call = con.prepareCall(func + pkgName + "getNumericWithoutArg () }");
     call.registerOutParameter(1, Types.NUMERIC);
     call.execute();
@@ -186,7 +188,6 @@ public class CallableStmtTest extends BaseTest4 {
 
   @Test
   public void testGetString() throws Throwable {
-    assumeCallableStatementsSupported();
     CallableStatement call = con.prepareCall(func + pkgName + "getString (?) }");
     call.setString(2, "foo");
     call.registerOutParameter(1, Types.VARCHAR);
@@ -197,7 +198,6 @@ public class CallableStmtTest extends BaseTest4 {
 
   @Test
   public void testGetArray() throws SQLException {
-    assumeCallableStatementsSupported();
     CallableStatement call = con.prepareCall(func + pkgName + "getarray()}");
     call.registerOutParameter(1, Types.ARRAY);
     call.execute();
@@ -212,7 +212,6 @@ public class CallableStmtTest extends BaseTest4 {
 
   @Test
   public void testRaiseNotice() throws SQLException {
-    assumeCallableStatementsSupported();
     Statement statement = con.createStatement();
     statement.execute("SET SESSION client_min_messages = 'NOTICE'");
     CallableStatement call = con.prepareCall(func + pkgName + "raisenotice()}");
diff --git a/src/test/java/org/postgresql/test/jdbc2/ConnectTimeoutTest.java b/src/test/java/org/postgresql/test/jdbc2/ConnectTimeoutTest.java
index 5b9673918bf32c80a6d7e75eacde6a12eef67309..2d2ca2c8067efcfabbb678ac92ebcde86fa0c973 100644
--- a/src/test/java/org/postgresql/test/jdbc2/ConnectTimeoutTest.java
+++ b/src/test/java/org/postgresql/test/jdbc2/ConnectTimeoutTest.java
@@ -56,7 +56,7 @@ public class ConnectTimeoutTest {
        * We treat this as a skipped test, as the test didn't really "succeed"
        * in testing the original behaviour, but it didn't fail either.
        */
-      Assume.assumeTrue("Host fast-failed connection to unreachable address "
+      Assume.assumeFalse("Host fast-failed connection to unreachable address "
                         + UNREACHABLE_HOST + " after " + interval + " ms, "
                         + " before timeout should have triggered.",
                         e.getCause() instanceof NoRouteToHostException
diff --git a/src/test/java/org/postgresql/test/jdbc2/DriverTest.java b/src/test/java/org/postgresql/test/jdbc2/DriverTest.java
index 2573a75e57aea8f35f6a33f0dd616280151b4ae7..dc0fd5379411c110f98c31def06fa4bc0bc085b9 100644
--- a/src/test/java/org/postgresql/test/jdbc2/DriverTest.java
+++ b/src/test/java/org/postgresql/test/jdbc2/DriverTest.java
@@ -5,23 +5,23 @@
 
 package org.postgresql.test.jdbc2;
 
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertNull;
-import static org.junit.Assert.assertTrue;
-import static org.junit.Assert.fail;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertNull;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.junit.jupiter.api.Assertions.fail;
 
 import org.postgresql.Driver;
 import org.postgresql.PGEnvironment;
 import org.postgresql.PGProperty;
 import org.postgresql.test.TestUtil;
+import org.postgresql.util.StubEnvironmentAndProperties;
 import org.postgresql.util.URLCoder;
 
-import org.junit.Test;
-import org.junit.jupiter.api.extension.ExtendWith;
+import org.junit.jupiter.api.Test;
 import uk.org.webcompere.systemstubs.environment.EnvironmentVariables;
-import uk.org.webcompere.systemstubs.jupiter.SystemStubsExtension;
 import uk.org.webcompere.systemstubs.properties.SystemProperties;
 import uk.org.webcompere.systemstubs.resource.Resources;
 
@@ -42,7 +42,7 @@ import java.util.Properties;
  * Tests the dynamically created class org.postgresql.Driver
  *
  */
-@ExtendWith(SystemStubsExtension.class)
+@StubEnvironmentAndProperties
 public class DriverTest {
 
   @Test
@@ -56,11 +56,11 @@ public class DriverTest {
    * According to the javadoc of java.sql.Driver.connect(...), calling abort when the {@code executor} is {@code null}
    * results in SQLException
    */
-  @Test(expected = SQLException.class)
+  @Test
   public void urlIsNull() throws SQLException {
     Driver driver = new Driver();
 
-    driver.connect(null, new Properties());
+    assertThrows(SQLException.class, () -> driver.connect(null, new Properties()));
   }
 
   /*
@@ -136,14 +136,14 @@ public class DriverTest {
 
   private void verifyUrl(Driver drv, String url, String hosts, String ports, String dbName)
       throws Exception {
-    assertTrue(url, drv.acceptsURL(url));
+    assertTrue(drv.acceptsURL(url), url);
     Method parseMethod =
         drv.getClass().getDeclaredMethod("parseURL", String.class, Properties.class);
     parseMethod.setAccessible(true);
     Properties p = (Properties) parseMethod.invoke(drv, url, null);
-    assertEquals(url, dbName, p.getProperty(PGProperty.PG_DBNAME.getName()));
-    assertEquals(url, hosts, p.getProperty(PGProperty.PG_HOST.getName()));
-    assertEquals(url, ports, p.getProperty(PGProperty.PG_PORT.getName()));
+    assertEquals(dbName, p.getProperty(PGProperty.PG_DBNAME.getName()), url);
+    assertEquals(hosts, p.getProperty(PGProperty.PG_HOST.getName()), url);
+    assertEquals(ports, p.getProperty(PGProperty.PG_PORT.getName()), url);
   }
 
   /**
@@ -515,7 +515,7 @@ public class DriverTest {
       try {
         assertNotNull(con);
         System.err.println();
-        assertFalse("The System.err should not be closed.", System.err.checkError());
+        assertFalse(System.err.checkError(), "The System.err should not be closed.");
       } finally {
         con.close();
       }
diff --git a/src/test/java/org/postgresql/test/jdbc2/RefCursorFetchTest.java b/src/test/java/org/postgresql/test/jdbc2/RefCursorFetchTest.java
index 136be60be22be008e0204cb26c8f3374b32b4397..996fa7617fbd9c3f234e432ea71aafeb85e1c613 100644
--- a/src/test/java/org/postgresql/test/jdbc2/RefCursorFetchTest.java
+++ b/src/test/java/org/postgresql/test/jdbc2/RefCursorFetchTest.java
@@ -78,6 +78,7 @@ public class RefCursorFetchTest extends BaseTest4 {
   public static void beforeClass() throws Exception {
     TestUtil.assumeHaveMinimumServerVersion(ServerVersion.v9_0);
     try (Connection con = TestUtil.openDB();) {
+      assumeCallableStatementsSupported(con);
       TestUtil.createTable(con, "test_blob", "content bytea");
       TestUtil.execute(con, "");
       TestUtil.execute(con, "--create function to read data\n"
@@ -106,7 +107,6 @@ public class RefCursorFetchTest extends BaseTest4 {
   @Override
   public void setUp() throws Exception {
     super.setUp();
-    assumeCallableStatementsSupported();
     con.setAutoCommit(autoCommit == AutoCommit.YES);
   }
 
diff --git a/src/test/java/org/postgresql/test/jdbc2/RefCursorTest.java b/src/test/java/org/postgresql/test/jdbc2/RefCursorTest.java
index e920b9552a04883ce405668bc76dac7e153b3822..498ea91189450d52429d45d57e6ea8c4e81a2057 100644
--- a/src/test/java/org/postgresql/test/jdbc2/RefCursorTest.java
+++ b/src/test/java/org/postgresql/test/jdbc2/RefCursorTest.java
@@ -12,11 +12,13 @@ import static org.junit.Assert.assertTrue;
 
 import org.postgresql.test.TestUtil;
 
+import org.junit.BeforeClass;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
 
 import java.sql.CallableStatement;
+import java.sql.Connection;
 import java.sql.ResultSet;
 import java.sql.ResultSetMetaData;
 import java.sql.SQLException;
@@ -49,6 +51,13 @@ public class RefCursorTest extends BaseTest4 {
     });
   }
 
+  @BeforeClass
+  public static void beforeClass() throws Exception {
+    try (Connection con = TestUtil.openDB();) {
+      assumeCallableStatementsSupported(con);
+    }
+  }
+
   @Override
   public void setUp() throws Exception {
     // this is the same as the ResultSet setup.
@@ -87,7 +96,6 @@ public class RefCursorTest extends BaseTest4 {
 
   @Test
   public void testResult() throws SQLException {
-    assumeCallableStatementsSupported();
     CallableStatement call = con.prepareCall("{ ? = call testspg__getRefcursor () }");
     call.registerOutParameter(1, cursorType);
     call.execute();
@@ -119,7 +127,6 @@ public class RefCursorTest extends BaseTest4 {
 
   @Test
   public void testEmptyResult() throws SQLException {
-    assumeCallableStatementsSupported();
     CallableStatement call = con.prepareCall("{ ? = call testspg__getEmptyRefcursor () }");
     call.registerOutParameter(1, cursorType);
     call.execute();
@@ -133,8 +140,6 @@ public class RefCursorTest extends BaseTest4 {
 
   @Test
   public void testMetaData() throws SQLException {
-    assumeCallableStatementsSupported();
-
     CallableStatement call = con.prepareCall("{ ? = call testspg__getRefcursor () }");
     call.registerOutParameter(1, cursorType);
     call.execute();
@@ -152,7 +157,6 @@ public class RefCursorTest extends BaseTest4 {
 
   @Test
   public void testResultType() throws SQLException {
-    assumeCallableStatementsSupported();
     CallableStatement call = con.prepareCall("{ ? = call testspg__getRefcursor () }",
         ResultSet.TYPE_SCROLL_INSENSITIVE, ResultSet.CONCUR_READ_ONLY);
     call.registerOutParameter(1, cursorType);
diff --git a/src/test/java/org/postgresql/test/jdbc2/ResultSetRefreshTest.java b/src/test/java/org/postgresql/test/jdbc2/ResultSetRefreshTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..023f2710b1327c229a3fb59f184dc2ef5b3152ab
--- /dev/null
+++ b/src/test/java/org/postgresql/test/jdbc2/ResultSetRefreshTest.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright (c) 2022, PostgreSQL Global Development Group
+ * See the LICENSE file in the project root for more information.
+ */
+
+package org.postgresql.test.jdbc2;
+
+import static org.junit.Assert.assertTrue;
+
+import org.postgresql.test.TestUtil;
+
+import org.junit.Test;
+
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.sql.Statement;
+
+public class ResultSetRefreshTest extends BaseTest4 {
+  @Test
+  public void testWithDataColumnThatRequiresEscaping() throws Exception {
+    TestUtil.dropTable(con, "refresh_row_bad_ident");
+    TestUtil.execute(con, "CREATE TABLE refresh_row_bad_ident (id int PRIMARY KEY, \"1 FROM refresh_row_bad_ident; SELECT 2; SELECT *\" int)");
+    TestUtil.execute(con, "INSERT INTO refresh_row_bad_ident (id) VALUES (1), (2), (3)");
+
+    Statement stmt = con.createStatement(ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_UPDATABLE);
+    ResultSet rs = stmt.executeQuery("SELECT * FROM refresh_row_bad_ident");
+    assertTrue(rs.next());
+    try {
+      rs.refreshRow();
+    } catch (SQLException ex) {
+      throw new RuntimeException("ResultSet.refreshRow() did not handle escaping data column identifiers", ex);
+    }
+    rs.close();
+    stmt.close();
+  }
+
+  @Test
+  public void testWithKeyColumnThatRequiresEscaping() throws Exception {
+    TestUtil.dropTable(con, "refresh_row_bad_ident");
+    TestUtil.execute(con, "CREATE TABLE refresh_row_bad_ident (\"my key\" int PRIMARY KEY)");
+    TestUtil.execute(con, "INSERT INTO refresh_row_bad_ident VALUES (1), (2), (3)");
+
+    Statement stmt = con.createStatement(ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_UPDATABLE);
+    ResultSet rs = stmt.executeQuery("SELECT * FROM refresh_row_bad_ident");
+    assertTrue(rs.next());
+    try {
+      rs.refreshRow();
+    } catch (SQLException ex) {
+      throw new RuntimeException("ResultSet.refreshRow() did not handle escaping key column identifiers", ex);
+    }
+    rs.close();
+    stmt.close();
+  }
+}
diff --git a/src/test/java/org/postgresql/test/jdbc3/Jdbc3CallableStatementTest.java b/src/test/java/org/postgresql/test/jdbc3/Jdbc3CallableStatementTest.java
index 17cf45003b68635b575497a603a10b3cf13bfcd9..e09a8ac700789c7204422bafe2af8016c58a72ef 100644
--- a/src/test/java/org/postgresql/test/jdbc3/Jdbc3CallableStatementTest.java
+++ b/src/test/java/org/postgresql/test/jdbc3/Jdbc3CallableStatementTest.java
@@ -16,10 +16,12 @@ import org.postgresql.test.TestUtil;
 import org.postgresql.test.jdbc2.BaseTest4;
 import org.postgresql.util.PSQLState;
 
+import org.junit.BeforeClass;
 import org.junit.Test;
 
 import java.math.BigDecimal;
 import java.sql.CallableStatement;
+import java.sql.Connection;
 import java.sql.PreparedStatement;
 import java.sql.ResultSet;
 import java.sql.SQLException;
@@ -30,6 +32,12 @@ import java.sql.Types;
  * @author davec
  */
 public class Jdbc3CallableStatementTest extends BaseTest4 {
+  @BeforeClass
+  public static void beforeClass() throws Exception {
+    try (Connection con = TestUtil.openDB();) {
+      assumeCallableStatementsSupported(con);
+    }
+  }
 
   @Override
   public void setUp() throws Exception {
@@ -129,7 +137,6 @@ public class Jdbc3CallableStatementTest extends BaseTest4 {
 
   @Test
   public void testSomeInOut() throws Throwable {
-    assumeCallableStatementsSupported();
     CallableStatement call = con.prepareCall("{ call test_somein_someout(?,?,?) }");
 
     call.registerOutParameter(2, Types.VARCHAR);
@@ -141,7 +148,6 @@ public class Jdbc3CallableStatementTest extends BaseTest4 {
 
   @Test
   public void testNotEnoughParameters() throws Throwable {
-    assumeCallableStatementsSupported();
     CallableStatement cs = con.prepareCall("{call myiofunc(?,?)}");
     cs.setInt(1, 2);
     cs.registerOutParameter(2, Types.INTEGER);
@@ -189,8 +195,6 @@ public class Jdbc3CallableStatementTest extends BaseTest4 {
 
   @Test
   public void testNumeric() throws Throwable {
-    assumeCallableStatementsSupported();
-
     CallableStatement call = con.prepareCall("{ call Numeric_Proc(?,?,?) }");
 
     call.registerOutParameter(1, Types.NUMERIC, 15);
@@ -216,7 +220,6 @@ public class Jdbc3CallableStatementTest extends BaseTest4 {
 
   @Test
   public void testGetObjectDecimal() throws Throwable {
-    assumeCallableStatementsSupported();
     try {
       Statement stmt = con.createStatement();
       stmt.execute(
@@ -420,7 +423,6 @@ public class Jdbc3CallableStatementTest extends BaseTest4 {
 
   @Test
   public void testGetObjectLongVarchar() throws Throwable {
-    assumeCallableStatementsSupported();
     try {
       Statement stmt = con.createStatement();
       stmt.execute("create temp table longvarchar_tab ( t text, null_val text )");
@@ -699,7 +701,6 @@ public class Jdbc3CallableStatementTest extends BaseTest4 {
 
   @Test
   public void testGetObjectFloat() throws Throwable {
-    assumeCallableStatementsSupported();
     try {
       Statement stmt = con.createStatement();
       stmt.execute(createDecimalTab);
@@ -736,7 +737,6 @@ public class Jdbc3CallableStatementTest extends BaseTest4 {
 
   @Test
   public void testGetDouble01() throws Throwable {
-    assumeCallableStatementsSupported();
     try {
       Statement stmt = con.createStatement();
       stmt.execute("create temp table d_tab ( max_val float, min_val float, null_val float )");
@@ -777,7 +777,6 @@ public class Jdbc3CallableStatementTest extends BaseTest4 {
 
   @Test
   public void testGetDoubleAsReal() throws Throwable {
-    assumeCallableStatementsSupported();
     try {
       Statement stmt = con.createStatement();
       stmt.execute("create temp table d_tab ( max_val float, min_val float, null_val float )");
@@ -818,7 +817,6 @@ public class Jdbc3CallableStatementTest extends BaseTest4 {
 
   @Test
   public void testGetShort01() throws Throwable {
-    assumeCallableStatementsSupported();
     try {
       Statement stmt = con.createStatement();
       stmt.execute("create temp table short_tab ( max_val int2, min_val int2, null_val int2 )");
@@ -859,7 +857,6 @@ public class Jdbc3CallableStatementTest extends BaseTest4 {
 
   @Test
   public void testGetInt01() throws Throwable {
-    assumeCallableStatementsSupported();
     try {
       Statement stmt = con.createStatement();
       stmt.execute("create temp table i_tab ( max_val int, min_val int, null_val int )");
@@ -900,7 +897,6 @@ public class Jdbc3CallableStatementTest extends BaseTest4 {
 
   @Test
   public void testGetLong01() throws Throwable {
-    assumeCallableStatementsSupported();
     try {
       Statement stmt = con.createStatement();
       stmt.execute("create temp table l_tab ( max_val int8, min_val int8, null_val int8 )");
@@ -941,7 +937,6 @@ public class Jdbc3CallableStatementTest extends BaseTest4 {
 
   @Test
   public void testGetBoolean01() throws Throwable {
-    assumeCallableStatementsSupported();
     try {
       Statement stmt = con.createStatement();
       stmt.execute(createBitTab);
@@ -992,7 +987,6 @@ public class Jdbc3CallableStatementTest extends BaseTest4 {
 
   @Test
   public void testGetByte01() throws Throwable {
-    assumeCallableStatementsSupported();
     try {
       Statement stmt = con.createStatement();
       stmt.execute("create temp table byte_tab ( max_val int2, min_val int2, null_val int2 )");
@@ -1033,7 +1027,6 @@ public class Jdbc3CallableStatementTest extends BaseTest4 {
 
   @Test
   public void testMultipleOutExecutions() throws SQLException {
-    assumeCallableStatementsSupported();
     CallableStatement cs = con.prepareCall("{call myiofunc(?, ?)}");
     for (int i = 0; i < 10; i++) {
       cs.registerOutParameter(1, Types.INTEGER);
@@ -1048,7 +1041,6 @@ public class Jdbc3CallableStatementTest extends BaseTest4 {
 
   @Test
   public void testSum() throws SQLException {
-    assumeCallableStatementsSupported();
     CallableStatement cs = con.prepareCall("{?= call mysum(?, ?)}");
     cs.registerOutParameter(1, Types.INTEGER);
     cs.setInt(2, 2);
@@ -1059,7 +1051,6 @@ public class Jdbc3CallableStatementTest extends BaseTest4 {
 
   @Test
   public void testFunctionNoParametersWithParentheses() throws SQLException {
-    assumeCallableStatementsSupported();
     CallableStatement cs = con.prepareCall("{?= call mynoparams()}");
     cs.registerOutParameter(1, Types.INTEGER);
     cs.execute();
@@ -1069,7 +1060,6 @@ public class Jdbc3CallableStatementTest extends BaseTest4 {
 
   @Test
   public void testFunctionNoParametersWithoutParentheses() throws SQLException {
-    assumeCallableStatementsSupported();
     CallableStatement cs = con.prepareCall("{?= call mynoparams}");
     cs.registerOutParameter(1, Types.INTEGER);
     cs.execute();
@@ -1079,7 +1069,6 @@ public class Jdbc3CallableStatementTest extends BaseTest4 {
 
   @Test
   public void testProcedureNoParametersWithParentheses() throws SQLException {
-    assumeCallableStatementsSupported();
     CallableStatement cs = con.prepareCall("{ call mynoparamsproc()}");
     cs.execute();
     TestUtil.closeQuietly(cs);
@@ -1087,7 +1076,6 @@ public class Jdbc3CallableStatementTest extends BaseTest4 {
 
   @Test
   public void testProcedureNoParametersWithoutParentheses() throws SQLException {
-    assumeCallableStatementsSupported();
     CallableStatement cs = con.prepareCall("{ call mynoparamsproc}");
     cs.execute();
     TestUtil.closeQuietly(cs);
@@ -1096,7 +1084,6 @@ public class Jdbc3CallableStatementTest extends BaseTest4 {
   @Test
   public void testProcedureInOnlyNativeCall() throws SQLException {
     assumeMinimumServerVersion(ServerVersion.v11);
-    assumeCallableStatementsSupported();
     CallableStatement cs = con.prepareCall("call inonlyprocedure(?)");
     cs.setInt(1, 5);
     cs.execute();
@@ -1106,7 +1093,6 @@ public class Jdbc3CallableStatementTest extends BaseTest4 {
   @Test
   public void testProcedureInOutNativeCall() throws SQLException {
     assumeMinimumServerVersion(ServerVersion.v11);
-    assumeCallableStatementsSupported();
     // inoutprocedure(a INOUT int) returns a*2 via the INOUT parameter
     CallableStatement cs = con.prepareCall("call inoutprocedure(?)");
     cs.setInt(1, 5);
diff --git a/src/test/java/org/postgresql/test/jdbc3/ProcedureTransactionTest.java b/src/test/java/org/postgresql/test/jdbc3/ProcedureTransactionTest.java
index e1262df91e19e0acdb057ea23c4667901610a7f4..faab402f446918783f5dbbc618b22bdfa7a2f9c8 100644
--- a/src/test/java/org/postgresql/test/jdbc3/ProcedureTransactionTest.java
+++ b/src/test/java/org/postgresql/test/jdbc3/ProcedureTransactionTest.java
@@ -16,15 +16,23 @@ import org.postgresql.test.TestUtil;
 import org.postgresql.test.jdbc2.BaseTest4;
 import org.postgresql.util.PSQLState;
 
+import org.junit.BeforeClass;
 import org.junit.Test;
 
 import java.sql.CallableStatement;
+import java.sql.Connection;
 import java.sql.ResultSet;
 import java.sql.SQLException;
 import java.sql.Statement;
 import java.util.Properties;
 
 public class ProcedureTransactionTest extends BaseTest4 {
+  @BeforeClass
+  public static void beforeClass() throws Exception {
+    try (Connection con = TestUtil.openDB();) {
+      assumeCallableStatementsSupported(con);
+    }
+  }
 
   @Override
   protected void updateProperties(Properties props) {
@@ -63,7 +71,6 @@ public class ProcedureTransactionTest extends BaseTest4 {
   @Test
   public void testProcWithNoTxnControl() throws SQLException {
     assumeMinimumServerVersion(ServerVersion.v11);
-    assumeCallableStatementsSupported();
     CallableStatement cs = con.prepareCall("call mynotxnproc(?)");
     int val = 1;
     cs.setInt(1, val);
@@ -84,7 +91,6 @@ public class ProcedureTransactionTest extends BaseTest4 {
   @Test
   public void testProcWithCommitInside() throws SQLException {
     assumeMinimumServerVersion(ServerVersion.v11);
-    assumeCallableStatementsSupported();
     CallableStatement cs = con.prepareCall("call mycommitproc(?)");
     int val = 2;
     cs.setInt(1, val);
@@ -105,7 +111,6 @@ public class ProcedureTransactionTest extends BaseTest4 {
   @Test
   public void testProcWithRollbackInside() throws SQLException {
     assumeMinimumServerVersion(ServerVersion.v11);
-    assumeCallableStatementsSupported();
     CallableStatement cs = con.prepareCall("call myrollbackproc(?)");
     int val = 3;
     cs.setInt(1, val);
@@ -148,7 +153,6 @@ public class ProcedureTransactionTest extends BaseTest4 {
 
   private void testProcAutoCommit() throws SQLException {
     assumeMinimumServerVersion(ServerVersion.v11);
-    assumeCallableStatementsSupported();
     CallableStatement cs = con.prepareCall("call mycommitproc(?)");
     int val = 4;
     cs.setInt(1, val);
diff --git a/src/test/java/org/postgresql/test/xa/XADataSourceTest.java b/src/test/java/org/postgresql/test/xa/XADataSourceTest.java
index 8a28c3b81d45ca1e7ccedc3a75601572c3758534..303d568594979d424fe3b1d1487ed684fd8b5492 100644
--- a/src/test/java/org/postgresql/test/xa/XADataSourceTest.java
+++ b/src/test/java/org/postgresql/test/xa/XADataSourceTest.java
@@ -12,13 +12,16 @@ import static org.junit.Assert.fail;
 import static org.junit.Assume.assumeTrue;
 
 import org.postgresql.test.TestUtil;
+import org.postgresql.test.XaTests;
 import org.postgresql.test.jdbc2.optional.BaseDataSourceTest;
 import org.postgresql.xa.PGXADataSource;
 
 // import org.checkerframework.checker.nullness.qual.Nullable;
 import org.junit.After;
 import org.junit.Before;
+import org.junit.BeforeClass;
 import org.junit.Test;
+import org.junit.experimental.categories.Category;
 
 import java.sql.Connection;
 import java.sql.ResultSet;
@@ -33,6 +36,7 @@ import javax.transaction.xa.XAException;
 import javax.transaction.xa.XAResource;
 import javax.transaction.xa.Xid;
 
+@Category(XaTests.class)
 public class XADataSourceTest {
 
   private XADataSource xaDs;
@@ -49,10 +53,16 @@ public class XADataSourceTest {
     BaseDataSourceTest.setupDataSource((PGXADataSource) xaDs);
   }
 
+  @BeforeClass
+  public static void beforeClass() throws Exception {
+    try (Connection con = TestUtil.openDB();) {
+      assumeTrue("max_prepared_transactions should be non-zero for XA tests", isPreparedTransactionEnabled(con));
+    }
+  }
+
   @Before
   public void setUp() throws Exception {
     dbConn = TestUtil.openDB();
-    assumeTrue(isPreparedTransactionEnabled(dbConn));
 
     // Check if we're operating as a superuser; some tests require it.
     Statement st = dbConn.createStatement();
diff --git a/src/test/java/org/postgresql/util/OSUtilTest.java b/src/test/java/org/postgresql/util/OSUtilTest.java
index d59ba297a9ab61095f504f7da6be642336e171ae..6c020e813e4ebb26784b1f39fdc19d8aeee8e355 100644
--- a/src/test/java/org/postgresql/util/OSUtilTest.java
+++ b/src/test/java/org/postgresql/util/OSUtilTest.java
@@ -8,15 +8,13 @@ package org.postgresql.util;
 import static org.junit.jupiter.api.Assertions.assertEquals;
 
 import org.junit.jupiter.api.Test;
-import org.junit.jupiter.api.extension.ExtendWith;
 import uk.org.webcompere.systemstubs.environment.EnvironmentVariables;
-import uk.org.webcompere.systemstubs.jupiter.SystemStubsExtension;
 import uk.org.webcompere.systemstubs.properties.SystemProperties;
 import uk.org.webcompere.systemstubs.resource.Resources;
 
 import java.io.File;
 
-@ExtendWith(SystemStubsExtension.class)
+@StubEnvironmentAndProperties
 class OSUtilTest {
 
   @Test
diff --git a/src/test/java/org/postgresql/util/PGPropertyPasswordParserTest.java b/src/test/java/org/postgresql/util/PGPropertyPasswordParserTest.java
index e1199c1be4ac4a33866f4bfcbb7102279c348448..805888b1c45b37a5d3a74822e20dd8e7afabb850 100644
--- a/src/test/java/org/postgresql/util/PGPropertyPasswordParserTest.java
+++ b/src/test/java/org/postgresql/util/PGPropertyPasswordParserTest.java
@@ -26,6 +26,7 @@ import java.net.URL;
  *
  * @author Marek Läll
  */
+@StubEnvironmentAndProperties
 class PGPropertyPasswordParserTest {
 
   // "org.postgresql.pgpassfile" : missing
diff --git a/src/test/java/org/postgresql/util/PGPropertyServiceParserTest.java b/src/test/java/org/postgresql/util/PGPropertyServiceParserTest.java
index 9becaccd1b3bc2667bef962eb5256cf6912cd807..bc4cb93e846c2c10f3711c601d72f4b1eaae7951 100644
--- a/src/test/java/org/postgresql/util/PGPropertyServiceParserTest.java
+++ b/src/test/java/org/postgresql/util/PGPropertyServiceParserTest.java
@@ -13,9 +13,7 @@ import static org.junit.jupiter.api.Assertions.assertTrue;
 import org.postgresql.PGEnvironment;
 
 import org.junit.jupiter.api.Test;
-import org.junit.jupiter.api.extension.ExtendWith;
 import uk.org.webcompere.systemstubs.environment.EnvironmentVariables;
-import uk.org.webcompere.systemstubs.jupiter.SystemStubsExtension;
 import uk.org.webcompere.systemstubs.properties.SystemProperties;
 import uk.org.webcompere.systemstubs.resource.Resources;
 
@@ -30,7 +28,7 @@ import java.util.Properties;
  *
  * @author Marek Läll
  */
-@ExtendWith(SystemStubsExtension.class)
+@StubEnvironmentAndProperties
 class PGPropertyServiceParserTest {
 
   // "org.postgresql.pgservicefile" : missing
diff --git a/src/test/java/org/postgresql/util/StubEnvironmentAndProperties.java b/src/test/java/org/postgresql/util/StubEnvironmentAndProperties.java
new file mode 100644
index 0000000000000000000000000000000000000000..23f780d50ddba012ebbb59245fab847921d701c8
--- /dev/null
+++ b/src/test/java/org/postgresql/util/StubEnvironmentAndProperties.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright (c) 2022, PostgreSQL Global Development Group
+ * See the LICENSE file in the project root for more information.
+ */
+
+package org.postgresql.util;
+
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.junit.jupiter.api.parallel.Isolated;
+import uk.org.webcompere.systemstubs.jupiter.SystemStubsExtension;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * This annotation is used to mark a test method as a test that should be run with stubbing system
+ * calls like {@code System#getProperty} and {@code System#getenv}.
+ * <p>The tests should be run in isolation to prevent concurrent modification of properties and
+ * the environment.</p>
+ * <p>Note: environment mocking works from a single thread only until
+ * <a href="https://github.com/webcompere/system-stubs/pull/46">Fix multi-threaded
+ * environment variable mocking</a>, and <a href="https://github.com/mockito/mockito/issues/2142">Mocked
+ * static methods are not available in other threads</a> are resolved</p>
+ */
+@Isolated
+@ExtendWith(SystemStubsExtension.class)
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.METHOD, ElementType.TYPE})
+public @interface StubEnvironmentAndProperties {
+}