Repository: microsoft/mssql-jdbc
Pull request #2194 (2023-08-15 20:29:15)

Performance optimizations when bulk loading large amounts of timestamps
While writing software to sync a couple of hundred million rows from another rdbms to sql server via the BulkCopy API, I discovered a disproportioned amount of time was spent serializing Timestamp objects to then deserialize them into Timestamp objects once again.
My fix for this is to be able to accept Timestamp objects directly (which should cover the majority of objects passed) and only serialize them once when passing the date values to the TDSWriter.

I have attached an example to show the difference while importing about 600k rows with 16 columns with dates only.
![image](https://github.com/microsoft/mssql-jdbc/assets/22618923/c595aca4-ca99-4417-9a71-82d31f320dd0)


microsoft-github-policy-service[bot]: 
@hannes92 please read the following Contributor License Agreement(CLA). If you agree with the CLA, please reply with the following information.

```
@microsoft-github-policy-service agree [company="{your company}"]
```

> Options:
> - (default - no company specified) I have sole ownership of intellectual property rights to my Submissions and I am not making Submissions in the course of work for my employer.
>```
>@microsoft-github-policy-service agree
>```
> - (when company given) I am making Submissions in the course of work for my employer (or my employer has intellectual property rights in my Submissions by contract or applicable law). I have permission from my employer to make Submissions and enter into this Agreement on behalf of my employer. By signing below, the defined term “You” includes me and my employer.
>```
>@microsoft-github-policy-service agree company="Microsoft"
>```

<details>
    <summary>Contributor License Agreement</summary>
    
### Contribution License Agreement
This Contribution License Agreement (**“Agreement”**) is agreed to by the party signing below (**“You”**),
and conveys certain license rights to Microsoft Corporation and its affiliates (“Microsoft”) for Your
contributions to Microsoft open source projects. This Agreement is effective as of the latest signature
date below.
1. **Definitions**.
**“Code”** means the computer software code, whether in human-readable or machine-executable form,
that is delivered by You to Microsoft under this Agreement.
**“Project”** means any of the projects owned or managed by Microsoft and offered under a license
approved by the Open Source Initiative (www.opensource.org).
**“Submit”** is the act of uploading, submitting, transmitting, or distributing code or other content to any
Project, including but not limited to communication on electronic mailing lists, source code control
systems, and issue tracking systems that are managed by, or on behalf of, the Project for the purpose of
discussing and improving that Project, but excluding communication that is conspicuously marked or
otherwise designated in writing by You as “Not a Submission.”
**“Submission”** means the Code and any other copyrightable material Submitted by You, including any
associated comments and documentation.
2. **Your Submission**. You must agree to the terms of this Agreement before making a Submission to any
Project. This Agreement covers any and all Submissions that You, now or in the future (except as
described in Section 4 below), Submit to any Project.
3. **Originality of Work**. You represent that each of Your Submissions is entirely Your original work.
Should You wish to Submit materials that are not Your original work, You may Submit them separately
to the Project if You (a) retain all copyright and license information that was in the materials as You
received them, (b) in the description accompanying Your Submission, include the phrase “Submission
containing materials of a third party:” followed by the names of the third party and any licenses or other
restrictions of which You are aware, and (c) follow any other instructions in the Project’s written
guidelines concerning Submissions.
4. **Your Employer**. References to “employer” in this Agreement include Your employer or anyone else
for whom You are acting in making Your Submission, e.g. as a contractor, vendor, or agent. If Your
Submission is made in the course of Your work for an employer or Your employer has intellectual
property rights in Your Submission by contract or applicable law, You must secure permission from Your
employer to make the Submission before signing this Agreement. In that case, the term “You” in this
Agreement will refer to You and the employer collectively. If You change employers in the future and
desire to Submit additional Submissions for the new employer, then You agree to sign a new Agreement
and secure permission from the new employer before Submitting those Submissions.
5. **Licenses**.
  * **Copyright License**. You grant Microsoft, and those who receive the Submission directly or
indirectly from Microsoft, a perpetual, worldwide, non-exclusive, royalty-free, irrevocable license in the
Submission to reproduce, prepare derivative works of, publicly display, publicly perform, and distribute
the Submission and such derivative works, and to sublicense any or all of the foregoing rights to third
parties.
  * **Patent License**. You grant Microsoft, and those who receive the Submission directly or
indirectly from Microsoft, a perpetual, worldwide, non-exclusive, royalty-free, irrevocable license under
Your patent claims that are necessarily infringed by the Submission or the combination of the
Submission with the Project to which it was Submitted to make, have made, use, offer to sell, sell and
import or otherwise dispose of the Submission alone or with the Project.
  * **Other Rights Reserved**. Each party reserves all rights not expressly granted in this Agreement.
No additional licenses or rights whatsoever (including, without limitation, any implied licenses) are
granted by implication, exhaustion, estoppel or otherwise.
6. **Representations and Warranties**. You represent that You are legally entitled to grant the above
licenses. You represent that each of Your Submissions is entirely Your original work (except as You may
have disclosed under Section 3). You represent that You have secured permission from Your employer to
make the Submission in cases where Your Submission is made in the course of Your work for Your
employer or Your employer has intellectual property rights in Your Submission by contract or applicable
law. If You are signing this Agreement on behalf of Your employer, You represent and warrant that You
have the necessary authority to bind the listed employer to the obligations contained in this Agreement.
You are not expected to provide support for Your Submission, unless You choose to do so. UNLESS
REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING, AND EXCEPT FOR THE WARRANTIES
EXPRESSLY STATED IN SECTIONS 3, 4, AND 6, THE SUBMISSION PROVIDED UNDER THIS AGREEMENT IS
PROVIDED WITHOUT WARRANTY OF ANY KIND, INCLUDING, BUT NOT LIMITED TO, ANY WARRANTY OF
NONINFRINGEMENT, MERCHANTABILITY, OR FITNESS FOR A PARTICULAR PURPOSE.
7. **Notice to Microsoft**. You agree to notify Microsoft in writing of any facts or circumstances of which
You later become aware that would make Your representations in this Agreement inaccurate in any
respect.
8. **Information about Submissions**. You agree that contributions to Projects and information about
contributions may be maintained indefinitely and disclosed publicly, including Your name and other
information that You submit with Your Submission.
9. **Governing Law/Jurisdiction**. This Agreement is governed by the laws of the State of Washington, and
the parties consent to exclusive jurisdiction and venue in the federal courts sitting in King County,
Washington, unless no federal subject matter jurisdiction exists, in which case the parties consent to
exclusive jurisdiction and venue in the Superior Court of King County, Washington. The parties waive all
defenses of lack of personal jurisdiction and forum non-conveniens.
10. **Entire Agreement/Assignment**. This Agreement is the entire agreement between the parties, and
supersedes any and all prior agreements, understandings or communications, written or oral, between
the parties relating to the subject matter hereof. This Agreement may be assigned by Microsoft.

</details>
<!-- Policy app identification https://img.shields.io/static/v1?label=ContributorLicenseAgreement. -->
hannes92: @microsoft-github-policy-service agree
Skickat från min iPhone

> 15 aug. 2023 kl. 22:33 skrev microsoft-github-policy-service[bot] ***@***.***>:
> 
> @microsoft-github-policy-service agree

Jeffery-Wasty: Thank you for the PR @hannes92. We'll review it when we're able to and get back to you with our next steps.

Patch:
diff --git a/README.md b/README.md
index e542079c..ffbc0435 100644
--- a/README.md
+++ b/README.md
@@ -11,13 +11,13 @@ The Microsoft JDBC Driver for SQL Server is a Type 4 JDBC driver that provides d
 
 We hope you enjoy using the Microsoft JDBC Driver for SQL Server.
 
-SQL Server Team
+Microsoft JDBC driver for SQL Server Team
 
 ## Take our survey
 
 Let us know how you think we're doing.
 
-<a href="https://aka.ms/mssqljdbcsurvey"><img style="float: right;"  height="67" width="156" src="https://sqlchoice.blob.core.windows.net/sqlchoice/static/images/survey.png"></a>
+<a href="https://aka.ms/mssqljdbcsurvey"><img style="float: right;"  height="50" width="125" src="https://img.shields.io/badge/Survey-70B244"></a>
 
 ## Status of Most Recent Builds
 | Azure Pipelines (Windows) | Azure Pipelines (Linux) | Azure Pipelines (MacOS) |
@@ -28,10 +28,7 @@ Let us know how you think we're doing.
 What's coming next?  We will look into adding a more comprehensive set of tests, improving our javadocs, and start developing the next set of features.
 
 ## Get Started 
-* [**Ubuntu + SQL Server + Java**](https://www.microsoft.com/en-us/sql-server/developer-get-started/java/ubuntu) 
-* [**Red Hat + SQL Server + Java**](https://www.microsoft.com/en-us/sql-server/developer-get-started/java/rhel)
-* [**Mac + SQL Server + Java**](https://www.microsoft.com/en-us/sql-server/developer-get-started/java/mac)
-* [**Windows + SQL Server + Java**](https://www.microsoft.com/en-us/sql-server/developer-get-started/java/windows)
+[**Getting started with SQL Server and Java**](https://github.com/AzureSQLDB/sql-driver-examples/blob/main/examples/sql/drivers/java-driver-example.md)
 
 ## Build
 ### Prerequisites
@@ -47,7 +44,7 @@ To build the jar files, you must use minimum version of Java 11 with Maven. You
 * Maven:
 	1. If you have not already done so, add the environment variable `mssql_jdbc_test_connection_properties` in your system with the connection properties for your SQL Server or SQL DB instance.
 	2. Run one of the commands below to build a JRE 11 and newer versions compatible jar or JRE 8 compatible jar in the `\target` directory. 
-        * Run `mvn install -Pjre20`. This creates JRE 20 compatible jar in `\target` directory which is JDBC 4.3 compliant (Build with JDK 20).
+        * Run `mvn install -Pjre21`. This creates JRE 21 compatible jar in `\target` directory which is JDBC 4.3 compliant (Build with JDK 21).
         * Run `mvn install -Pjre17`. This creates JRE 17 compatible jar in `\target` directory which is JDBC 4.3 compliant (Build with JDK 17+).
         * Run `mvn install -Pjre11`. This creates JRE 11 compatible jar in `\target` directory which is JDBC 4.3 compliant (Build with JDK 11+).
         * Run `mvn install -Pjre8`. This creates JRE 8 compatible jar in `\target` directory which is JDBC 4.2 compliant (Build with JDK 11+).
@@ -55,7 +52,7 @@ To build the jar files, you must use minimum version of Java 11 with Maven. You
 * Gradle:
 	1. If you have not already done so, add the environment variable `mssql_jdbc_test_connection_properties` in your system with the connection properties for your SQL Server or SQL DB instance.
 	2. Run one of the commands below to build a JRE 11 and newer versions compatible jar or JRE 8 compatible jar in the `\build\libs` directory. 
-        * Run `gradle build -PbuildProfile=jre20`. This creates JRE 20 compatible jar in `\build\libs` directory which is JDBC 4.3 compliant (Build with JDK 20).
+        * Run `gradle build -PbuildProfile=jre21`. This creates JRE 21 compatible jar in `\build\libs` directory which is JDBC 4.3 compliant (Build with JDK 21).
         * Run `gradle build -PbuildProfile=jre17`. This creates JRE 17 compatible jar in `\build\libs` directory which is JDBC 4.3 compliant (Build with JDK 17+).
         * Run `gradle build -PbuildProfile=jre11`. This creates JRE 11 compatible jar in `\build\libs` directory which is JDBC 4.3 compliant (Build with JDK 11+).
         * Run `gradle build -PbuildProfile=jre8`. This creates JRE 8 compatible jar in `\build\libs` directory which is JDBC 4.2 compliant (Build with JDK 11+).
@@ -82,7 +79,7 @@ We're now on the Maven Central Repository. Add the following to your POM file to
 <dependency>
 	<groupId>com.microsoft.sqlserver</groupId>
 	<artifactId>mssql-jdbc</artifactId>
-	<version>12.4.0.jre11</version>
+	<version>12.4.1.jre11</version>
 </dependency>
 ```
 The driver can be downloaded from [Microsoft](https://aka.ms/downloadmssqljdbc).
@@ -93,7 +90,7 @@ To get the latest version of the driver, add the following to your POM file:
 <dependency>
 	<groupId>com.microsoft.sqlserver</groupId>
 	<artifactId>mssql-jdbc</artifactId>
-	<version>12.4.0.jre11</version>
+	<version>12.4.1.jre11</version>
 </dependency>
 ```
 
@@ -128,7 +125,7 @@ Projects that require either of the two features need to explicitly declare the
 <dependency>
 	<groupId>com.microsoft.sqlserver</groupId>
 	<artifactId>mssql-jdbc</artifactId>
-	<version>12.4.0.jre11</version>
+	<version>12.4.1.jre11</version>
 	<scope>compile</scope>
 </dependency>
 
@@ -146,7 +143,7 @@ Projects that require either of the two features need to explicitly declare the
 <dependency>
 	<groupId>com.microsoft.sqlserver</groupId>
 	<artifactId>mssql-jdbc</artifactId>
-	<version>12.4.0.jre11</version>
+	<version>12.4.1.jre11</version>
 	<scope>compile</scope>
 </dependency>
 
@@ -173,7 +170,7 @@ When setting 'useFmtOnly' property to 'true' for establishing a connection or cr
 <dependency>
 	<groupId>com.microsoft.sqlserver</groupId>
 	<artifactId>mssql-jdbc</artifactId>
-	<version>12.4.0.jre11</version>
+	<version>12.4.1.jre11</version>
 </dependency>
 
 <dependency>
diff --git a/build.gradle b/build.gradle
index 4ce995b8..18cbe02a 100644
--- a/build.gradle
+++ b/build.gradle
@@ -29,21 +29,21 @@ allprojects {
 
 test {
     useJUnitPlatform {
-        excludeTags (hasProperty('excludedGroups') ? excludedGroups : 'xSQLv15','xGradle','reqExternalSetup','NTLM','MSI','clientCertAuth','fedAuth')
+        excludeTags (hasProperty('excludedGroups') ? excludedGroups : 'xSQLv15','xGradle','reqExternalSetup','NTLM','MSI','clientCertAuth','fedAuth','kerberos')
     }
 }
 
-if (!hasProperty('buildProfile') || (hasProperty('buildProfile') && buildProfile == "jre20")) {
+if (!hasProperty('buildProfile') || (hasProperty('buildProfile') && buildProfile == "jre21")) {
 
-	jreVersion = "jre20"
+	jreVersion = "jre21"
 	excludedFile = 'com/microsoft/sqlserver/jdbc/SQLServerJdbc42.java'
 	jar {
 		manifest {
 			attributes 'Automatic-Module-Name': 'com.microsoft.sqlserver.jdbc'
 		}
 	}
-	sourceCompatibility = 20
-	targetCompatibility = 20
+	sourceCompatibility = 21
+	targetCompatibility = 21
 }
 
 if (hasProperty('buildProfile') && buildProfile == "jre17") {
diff --git a/pom.xml b/pom.xml
index c7fb25e1..eb0d5167 100644
--- a/pom.xml
+++ b/pom.xml
@@ -42,13 +42,14 @@
 			xAzureSQLDB - - - - For tests not compatible with Azure SQL Database - - 
 			xAzureSQLDW - - - - For tests not compatible with Azure Data Warehouse - 
 			xAzureSQLMI - - - - For tests not compatible with Azure SQL Managed Instance 
-			NTLM - - - - - - For tests using NTLM Authentication mode (excluded by default) 
-			reqExternalSetup - For tests requiring external setup (excluded by default) 
+			NTLM  - - - - - - - For tests using NTLM Authentication mode (excluded by default)
+			kerberos -  - - - - For tests using Kerberos authentication (excluded by default)
+			reqExternalSetup - For tests requiring external setup (excluded by default)
 			clientCertAuth - - For tests requiring client certificate authentication 
 			setup (excluded by default) - - - - - - - - - - - - - - - - - - - - - - - 
 			- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 
 			Default testing enabled with SQL Server 2019 (SQLv15) -->
-		<excludedGroups>xSQLv12,xSQLv15,NTLM,MSI,reqExternalSetup,clientCertAuth,fedAuth</excludedGroups>
+		<excludedGroups>xSQLv12,xSQLv15,NTLM,MSI,reqExternalSetup,clientCertAuth,fedAuth,kerberos</excludedGroups>
 		<!-- Use -preview for preview release, leave empty for official release. -->
 		<releaseExt>-preview</releaseExt>
 		<!-- Driver Dependencies -->
@@ -345,12 +346,12 @@
 			</build>
 		</profile>
 		<profile>
-			<id>jre20</id>
+			<id>jre21</id>
 			<activation>
 				<activeByDefault>true</activeByDefault>
 			</activation>
 			<build>
-				<finalName>${project.artifactId}-${project.version}.jre20${releaseExt}</finalName>
+				<finalName>${project.artifactId}-${project.version}.jre21${releaseExt}</finalName>
 				<plugins>
 					<plugin>
 						<groupId>org.apache.maven.plugins</groupId>
@@ -360,8 +361,8 @@
 							<excludes>
 								<exclude>**/com/microsoft/sqlserver/jdbc/SQLServerJdbc42.java</exclude>
 							</excludes>
-							<source>20</source>
-							<target>20</target>
+							<source>21</source>
+							<target>21</target>
 						</configuration>
 					</plugin>
 					<plugin>
diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/AuthenticationJNI.java b/src/main/java/com/microsoft/sqlserver/jdbc/AuthenticationJNI.java
index 9d525186..c21c1863 100644
--- a/src/main/java/com/microsoft/sqlserver/jdbc/AuthenticationJNI.java
+++ b/src/main/java/com/microsoft/sqlserver/jdbc/AuthenticationJNI.java
@@ -7,6 +7,8 @@ package com.microsoft.sqlserver.jdbc;
 
 import java.text.MessageFormat;
 import java.util.logging.Level;
+import java.util.concurrent.locks.Lock;
+import java.util.concurrent.locks.ReentrantLock;
 
 
 class FedAuthDllInfo {
@@ -37,6 +39,8 @@ final class AuthenticationJNI extends SSPIAuthentication {
 
     private static final UnsatisfiedLinkError linkError;
 
+    private static final Lock lock = new ReentrantLock();
+
     static int getMaxSSPIBlobSize() {
         return sspiBlobMaxlen;
     }
@@ -82,8 +86,14 @@ final class AuthenticationJNI extends SSPIAuthentication {
 
     static FedAuthDllInfo getAccessTokenForWindowsIntegrated(String stsURL, String servicePrincipalName,
             String clientConnectionId, String clientId, long expirationFileTime) throws DLLException {
-        return ADALGetAccessTokenForWindowsIntegrated(stsURL, servicePrincipalName, clientConnectionId, clientId,
-                expirationFileTime, authLogger);
+        try {
+            lock.lock();
+
+            return ADALGetAccessTokenForWindowsIntegrated(stsURL, servicePrincipalName, clientConnectionId, clientId,
+                    expirationFileTime, authLogger);
+        } finally {
+            lock.unlock();
+        }
     }
 
     // InitDNSName should be called to initialize the DNSName before calling this function
@@ -154,13 +164,13 @@ final class AuthenticationJNI extends SSPIAuthentication {
 
     private static native int GetDNSName(String address, String[] DNSName, java.util.logging.Logger log);
 
-    private static synchronized native FedAuthDllInfo ADALGetAccessTokenForWindowsIntegrated(String stsURL,
+    private static native FedAuthDllInfo ADALGetAccessTokenForWindowsIntegrated(String stsURL,
             String servicePrincipalName, String clientConnectionId, String clientId, long expirationFileTime,
             java.util.logging.Logger log);
 
-    static synchronized native byte[] DecryptColumnEncryptionKey(String masterKeyPath, String encryptionAlgorithm,
+    static native byte[] DecryptColumnEncryptionKey(String masterKeyPath, String encryptionAlgorithm,
             byte[] encryptedColumnEncryptionKey) throws DLLException;
 
-    static synchronized native boolean VerifyColumnMasterKeyMetadata(String keyPath, boolean allowEnclaveComputations,
+    static native boolean VerifyColumnMasterKeyMetadata(String keyPath, boolean allowEnclaveComputations,
             byte[] signature) throws DLLException;
 }
diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/DDC.java b/src/main/java/com/microsoft/sqlserver/jdbc/DDC.java
index 267034bf..c9bc8871 100644
--- a/src/main/java/com/microsoft/sqlserver/jdbc/DDC.java
+++ b/src/main/java/com/microsoft/sqlserver/jdbc/DDC.java
@@ -861,6 +861,9 @@ final class DDC {
      *
      * java.sql.Date java.sql.Time java.sql.Timestamp java.lang.String
      *
+     * @param connection
+     *        the JDBC connection from which value was read
+     *
      * @param jdbcType
      *        the JDBC type indicating the desired conversion
      *
@@ -890,7 +893,8 @@ final class DDC {
      *
      * @return a Java object of the desired type.
      */
-    static final Object convertTemporalToObject(JDBCType jdbcType, SSType ssType, Calendar timeZoneCalendar,
+    static final Object convertTemporalToObject(
+            SQLServerConnection connection, JDBCType jdbcType, SSType ssType, Calendar timeZoneCalendar,
             int daysSinceBaseDate, long ticksSinceMidnight, int fractionalSecondsScale) throws SQLServerException {
 
         // In cases where a Calendar object (and therefore Timezone) is not passed to the method,
@@ -1127,7 +1131,13 @@ final class DDC {
                 java.sql.Timestamp ts2 = new java.sql.Timestamp(cal.getTimeInMillis());
                 ts2.setNanos(subSecondNanos);
                 if (jdbcType == JDBCType.LOCALDATETIME) {
-                    return ts2.toLocalDateTime();
+                    if (connection.getIgnoreOffsetOnDateTimeOffsetConversion()) {
+                        return LocalDateTime.of(
+                                cal.get(Calendar.YEAR), cal.get(Calendar.MONTH) + 1, cal.get(Calendar.DAY_OF_MONTH),
+                                cal.get(Calendar.HOUR_OF_DAY), cal.get(Calendar.MINUTE), cal.get(Calendar.SECOND), subSecondNanos);
+                    } else {
+                        return ts2.toLocalDateTime();
+                    }
                 }
                 return ts2;
 
diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/DataTypes.java b/src/main/java/com/microsoft/sqlserver/jdbc/DataTypes.java
index 7b710bc0..9358f835 100644
--- a/src/main/java/com/microsoft/sqlserver/jdbc/DataTypes.java
+++ b/src/main/java/com/microsoft/sqlserver/jdbc/DataTypes.java
@@ -1115,7 +1115,7 @@ final class DataTypes {
     static final void throwConversionError(String fromType, String toType) throws SQLServerException {
         MessageFormat form = new MessageFormat(SQLServerException.getErrString("R_unsupportedConversionFromTo"));
         Object[] msgArgs = {fromType, toType};
-        SQLServerException.makeFromDriverError(null, null, form.format(msgArgs), null, true);
+        SQLServerException.makeFromDriverError(null, null, form.format(msgArgs), SQLState.ERROR_IN_ASSIGNMENT.getSQLStateCode(), true);
     }
 
     /**
diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/IOBuffer.java b/src/main/java/com/microsoft/sqlserver/jdbc/IOBuffer.java
index bb709307..89fab7c7 100644
--- a/src/main/java/com/microsoft/sqlserver/jdbc/IOBuffer.java
+++ b/src/main/java/com/microsoft/sqlserver/jdbc/IOBuffer.java
@@ -40,6 +40,7 @@ import java.security.Security;
 import java.sql.Timestamp;
 import java.text.MessageFormat;
 import java.time.LocalDate;
+import java.time.LocalDateTime;
 import java.time.OffsetDateTime;
 import java.time.OffsetTime;
 import java.util.ArrayList;
@@ -3755,29 +3756,24 @@ final class TDSWriter {
         writeShort((short) minutesSinceMidnight);
     }
 
-    void writeDatetime(String value) throws SQLServerException {
-        GregorianCalendar calendar = initializeCalender(TimeZone.getDefault());
-        long utcMillis; // Value to which the calendar is to be set (in milliseconds 1/1/1970 00:00:00 GMT)
+    void writeDatetime(java.sql.Timestamp dateValue) throws SQLServerException {
+        LocalDateTime ldt = dateValue.toLocalDateTime();
         int subSecondNanos;
-        java.sql.Timestamp timestampValue = java.sql.Timestamp.valueOf(value);
-        utcMillis = timestampValue.getTime();
-        subSecondNanos = timestampValue.getNanos();
+        subSecondNanos = ldt.getNano();
 
-        // Load the calendar with the desired value
-        calendar.setTimeInMillis(utcMillis);
 
         // Number of days there have been since the SQL Base Date.
         // These are based on SQL Server algorithms
-        int daysSinceSQLBaseDate = DDC.daysSinceBaseDate(calendar.get(Calendar.YEAR),
-                calendar.get(Calendar.DAY_OF_YEAR), TDS.BASE_YEAR_1900);
+        int daysSinceSQLBaseDate = DDC.daysSinceBaseDate(ldt.getYear(),
+                ldt.getDayOfYear(), TDS.BASE_YEAR_1900);
 
         // Number of milliseconds since midnight of the current day.
         int millisSinceMidnight = (subSecondNanos + Nanos.PER_MILLISECOND / 2) / Nanos.PER_MILLISECOND + // Millis into
-                                                                                                         // the current
-                                                                                                         // second
-                1000 * calendar.get(Calendar.SECOND) + // Seconds into the current minute
-                60 * 1000 * calendar.get(Calendar.MINUTE) + // Minutes into the current hour
-                60 * 60 * 1000 * calendar.get(Calendar.HOUR_OF_DAY); // Hours into the current day
+                // the current
+                // second
+                1000 * ldt.getSecond() + // Seconds into the current minute
+                60 * 1000 * ldt.getMinute() + // Minutes into the current hour
+                60 * 60 * 1000 * ldt.getHour(); // Hours into the current day
 
         // The last millisecond of the current day is always rounded to the first millisecond
         // of the next day because DATETIME is only accurate to 1/300th of a second.
@@ -7229,7 +7225,7 @@ final class TDSReader implements Serializable {
         }
 
         // Convert the DATETIME/SMALLDATETIME value to the desired Java type.
-        return DDC.convertTemporalToObject(jdbcType, SSType.DATETIME, appTimeZoneCalendar, daysSinceSQLBaseDate,
+        return DDC.convertTemporalToObject(con, jdbcType, SSType.DATETIME, appTimeZoneCalendar, daysSinceSQLBaseDate,
                 msecSinceMidnight, 0); // scale
                                        // (ignored
                                        // for
@@ -7246,7 +7242,7 @@ final class TDSReader implements Serializable {
         int localDaysIntoCE = readDaysIntoCE();
 
         // Convert the DATE value to the desired Java type.
-        return DDC.convertTemporalToObject(jdbcType, SSType.DATE, appTimeZoneCalendar, localDaysIntoCE, 0, // midnight
+        return DDC.convertTemporalToObject(con, jdbcType, SSType.DATE, appTimeZoneCalendar, localDaysIntoCE, 0, // midnight
                                                                                                            // local to
                                                                                                            // app time
                                                                                                            // zone
@@ -7262,7 +7258,7 @@ final class TDSReader implements Serializable {
         long localNanosSinceMidnight = readNanosSinceMidnight(typeInfo.getScale());
 
         // Convert the TIME value to the desired Java type.
-        return DDC.convertTemporalToObject(jdbcType, SSType.TIME, appTimeZoneCalendar, 0, localNanosSinceMidnight,
+        return DDC.convertTemporalToObject(con, jdbcType, SSType.TIME, appTimeZoneCalendar, 0, localNanosSinceMidnight,
                 typeInfo.getScale());
     }
 
@@ -7276,7 +7272,7 @@ final class TDSReader implements Serializable {
         int localDaysIntoCE = readDaysIntoCE();
 
         // Convert the DATETIME2 value to the desired Java type.
-        return DDC.convertTemporalToObject(jdbcType, SSType.DATETIME2, appTimeZoneCalendar, localDaysIntoCE,
+        return DDC.convertTemporalToObject(con, jdbcType, SSType.DATETIME2, appTimeZoneCalendar, localDaysIntoCE,
                 localNanosSinceMidnight, typeInfo.getScale());
     }
 
@@ -7291,7 +7287,7 @@ final class TDSReader implements Serializable {
         int localMinutesOffset = readShort();
 
         // Convert the DATETIMEOFFSET value to the desired Java type.
-        return DDC.convertTemporalToObject(jdbcType, SSType.DATETIMEOFFSET,
+        return DDC.convertTemporalToObject(con, jdbcType, SSType.DATETIMEOFFSET,
                 new GregorianCalendar(new SimpleTimeZone(localMinutesOffset * 60 * 1000, ""), Locale.US), utcDaysIntoCE,
                 utcNanosSinceMidnight, typeInfo.getScale());
     }
diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/ISQLServerConnection.java b/src/main/java/com/microsoft/sqlserver/jdbc/ISQLServerConnection.java
index 88fa73a3..8025410c 100644
--- a/src/main/java/com/microsoft/sqlserver/jdbc/ISQLServerConnection.java
+++ b/src/main/java/com/microsoft/sqlserver/jdbc/ISQLServerConnection.java
@@ -416,6 +416,21 @@ public interface ISQLServerConnection extends java.sql.Connection {
      */
     void setDelayLoadingLobs(boolean delayLoadingLobs);
 
+    /**
+     * Returns the current flag value for ignoreOffsetOnDateTimeOffsetConversion.
+     *
+     * @return 'ignoreOffsetOnDateTimeOffsetConversion' property value.
+     */
+    boolean getIgnoreOffsetOnDateTimeOffsetConversion();
+
+    /**
+     * Specifies the flag to ignore offset when converting DATETIMEOFFSET to LocalDateTime.
+     *
+     * @param ignoreOffsetOnDateTimeOffsetConversion
+     *        boolean value for 'ignoreOffsetOnDateTimeOffsetConversion'.
+     */
+    void setIgnoreOffsetOnDateTimeOffsetConversion(boolean ignoreOffsetOnDateTimeOffsetConversion);
+
     /**
      * Sets the name of the preferred type of IP Address.
      * 
diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/ISQLServerDataSource.java b/src/main/java/com/microsoft/sqlserver/jdbc/ISQLServerDataSource.java
index a435666e..3bfc279a 100644
--- a/src/main/java/com/microsoft/sqlserver/jdbc/ISQLServerDataSource.java
+++ b/src/main/java/com/microsoft/sqlserver/jdbc/ISQLServerDataSource.java
@@ -594,9 +594,24 @@ public interface ISQLServerDataSource extends javax.sql.CommonDataSource {
      */
     String getServerSpn();
 
+    /**
+     * Sets the value to indicate whether useDefaultGSSCredential is enabled.
+     *
+     * @param enable
+     *        true if useDefaultGSSCredential is enabled. Otherwise, false.
+     */
+    void setUseDefaultGSSCredential(boolean enable);
+
+    /**
+     * Returns the useDefaultGSSCredential.
+     *
+     * @return if enabled, return true. Otherwise, false.
+     */
+    boolean getUseDefaultGSSCredential();
+
     /**
      * Sets the GSSCredential.
-     * 
+     *
      * @param userCredential
      *        the credential
      */
@@ -604,7 +619,7 @@ public interface ISQLServerDataSource extends javax.sql.CommonDataSource {
 
     /**
      * Returns the GSSCredential.
-     * 
+     *
      * @return GSSCredential
      */
     GSSCredential getGSSCredentials();
@@ -802,7 +817,7 @@ public interface ISQLServerDataSource extends javax.sql.CommonDataSource {
     int getSocketTimeout();
 
     /**
-     * Sets the login configuration file for Kerberos authentication. This overrides the default configuration <i>
+     * Sets the login configuration name for Kerberos authentication. This overrides the default configuration <i>
      * SQLJDBCDriver </i>
      * 
      * @param configurationName
@@ -814,7 +829,7 @@ public interface ISQLServerDataSource extends javax.sql.CommonDataSource {
     void setJASSConfigurationName(String configurationName);
 
     /**
-     * Returns the login configuration file for Kerberos authentication.
+     * Returns the login configuration name for Kerberos authentication.
      *
      * 
      * @return login configuration file name
@@ -825,7 +840,7 @@ public interface ISQLServerDataSource extends javax.sql.CommonDataSource {
     String getJASSConfigurationName();
 
     /**
-     * Sets the login configuration file for Kerberos authentication. This overrides the default configuration <i>
+     * Sets the login configuration name for Kerberos authentication. This overrides the default configuration <i>
      * SQLJDBCDriver </i>
      * 
      * 
@@ -835,12 +850,27 @@ public interface ISQLServerDataSource extends javax.sql.CommonDataSource {
     void setJAASConfigurationName(String configurationName);
 
     /**
-     * Returns the login configuration file for Kerberos authentication.
+     * Returns the login configuration name for Kerberos authentication.
      * 
-     * @return login configuration file name
+     * @return login configuration name
      */
     String getJAASConfigurationName();
 
+    /**
+     * Returns whether the default JAAS Configuration should be used
+     *
+     * @return useDefaultJaasConfig boolean value
+     */
+    boolean getUseDefaultJaasConfig();
+
+    /**
+     * Sets whether the default JAAS Configuration will be used.  This means the system-wide JAAS configuration
+     * is ignored to avoid conflicts with libraries that override the JAAS configuration.
+     *
+     * @param useDefaultJaasConfig
+     *         boolean property to use the default JAAS configuration
+     */
+    void setUseDefaultJaasConfig(boolean useDefaultJaasConfig);
     /**
      * Sets whether Fips Mode should be enabled/disabled on the connection. For FIPS enabled JVM this property should be
      * true.
diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/KerbAuthentication.java b/src/main/java/com/microsoft/sqlserver/jdbc/KerbAuthentication.java
index ffc01644..4c3c9109 100644
--- a/src/main/java/com/microsoft/sqlserver/jdbc/KerbAuthentication.java
+++ b/src/main/java/com/microsoft/sqlserver/jdbc/KerbAuthentication.java
@@ -39,6 +39,7 @@ final class KerbAuthentication extends SSPIAuthentication {
     private LoginContext lc = null;
     private boolean isUserCreatedCredential = false;
     private GSSCredential peerCredentials = null;
+    private boolean useDefaultNativeGSSCredential = false;
     private GSSContext peerContext = null;
 
     static {
@@ -63,6 +64,10 @@ final class KerbAuthentication extends SSPIAuthentication {
             // as it is.
             GSSName remotePeerName = manager.createName(spn, null);
 
+            if (useDefaultNativeGSSCredential) {
+                peerCredentials = manager.createCredential(null, GSSCredential.DEFAULT_LIFETIME, kerberos, GSSCredential.INITIATE_ONLY);
+            }
+
             if (null != peerCredentials) {
                 peerContext = manager.createContext(remotePeerName, kerberos, peerCredentials,
                         GSSContext.DEFAULT_LIFETIME);
@@ -73,13 +78,31 @@ final class KerbAuthentication extends SSPIAuthentication {
                 String configName = con.activeConnectionProperties.getProperty(
                         SQLServerDriverStringProperty.JAAS_CONFIG_NAME.toString(),
                         SQLServerDriverStringProperty.JAAS_CONFIG_NAME.getDefaultValue());
+                boolean useDefaultJaas = Boolean.parseBoolean(con.activeConnectionProperties.getProperty(
+                        SQLServerDriverBooleanProperty.USE_DEFAULT_JAAS_CONFIG.toString(),
+                        Boolean.toString(SQLServerDriverBooleanProperty.USE_DEFAULT_JAAS_CONFIG.getDefaultValue())));
+
+                if (!configName.equals(
+                        SQLServerDriverStringProperty.JAAS_CONFIG_NAME.getDefaultValue()) && useDefaultJaas) {
+                    // Reset configName to default -- useDefaultJaas setting takes priority over jaasConfigName
+                    if (authLogger.isLoggable(Level.WARNING)) {
+                        authLogger.warning(toString() + String.format(
+                                "Using default JAAS configuration, configured %s=%s will not be used.",
+                                SQLServerDriverStringProperty.JAAS_CONFIG_NAME, configName));
+                    }
+                    configName = SQLServerDriverStringProperty.JAAS_CONFIG_NAME.getDefaultValue();
+                }
                 Subject currentSubject;
                 KerbCallback callback = new KerbCallback(con);
                 try {
                     AccessControlContext context = AccessController.getContext();
                     currentSubject = Subject.getSubject(context);
                     if (null == currentSubject) {
-                        lc = new LoginContext(configName, callback);
+                        if (useDefaultJaas) {
+                            lc = new LoginContext(configName, null, callback, new JaasConfiguration(null));
+                        } else {
+                            lc = new LoginContext(configName, callback);
+                        }
                         lc.login();
                         // per documentation LoginContext will instantiate a new subject.
                         currentSubject = lc.getSubject();
@@ -202,10 +225,11 @@ final class KerbAuthentication extends SSPIAuthentication {
      * @param impersonatedUserCred
      */
     KerbAuthentication(SQLServerConnection con, String address, int port, GSSCredential impersonatedUserCred,
-            boolean isUserCreated) {
+            boolean isUserCreated, boolean useDefaultNativeGSSCredential) {
         this(con, address, port);
         this.peerCredentials = impersonatedUserCred;
         this.isUserCreatedCredential = isUserCreated;
+        this.useDefaultNativeGSSCredential = useDefaultNativeGSSCredential;
     }
 
     byte[] generateClientContext(byte[] pin, boolean[] done) throws SQLServerException {
@@ -217,9 +241,9 @@ final class KerbAuthentication extends SSPIAuthentication {
 
     void releaseClientContext() {
         try {
-            if (null != peerCredentials && !isUserCreatedCredential) {
+            if (null != peerCredentials && !isUserCreatedCredential && !useDefaultNativeGSSCredential) {
                 peerCredentials.dispose();
-            } else if (null != peerCredentials && isUserCreatedCredential) {
+            } else if (null != peerCredentials && (isUserCreatedCredential || useDefaultNativeGSSCredential)) {
                 peerCredentials = null;
             }
             if (null != peerContext) {
diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerBulkCopy.java b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerBulkCopy.java
index 48160ee7..427fe916 100644
--- a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerBulkCopy.java
+++ b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerBulkCopy.java
@@ -1724,53 +1724,53 @@ public class SQLServerBulkCopy implements java.lang.AutoCloseable, java.io.Seria
         SQLServerStatement stmt = null;
         String metaDataQuery = null;
 
-        try {
-            if (null != destinationTableMetadata) {
-                rs = (SQLServerResultSet) destinationTableMetadata;
-            } else {
-                stmt = (SQLServerStatement) connection.createStatement(ResultSet.TYPE_FORWARD_ONLY,
-                        ResultSet.CONCUR_READ_ONLY, connection.getHoldability(), stmtColumnEncriptionSetting);
+        if (null == destColumnMetadata || destColumnMetadata.isEmpty()) {
+            try {
+                if (null != destinationTableMetadata) {
+                    rs = (SQLServerResultSet) destinationTableMetadata;
+                } else {
+                    stmt = (SQLServerStatement) connection.createStatement(ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_READ_ONLY, connection.getHoldability(), stmtColumnEncriptionSetting);
 
-                // Get destination metadata
-                rs = stmt.executeQueryInternal(
-                        "sp_executesql N'SET FMTONLY ON SELECT * FROM " + escapedDestinationTableName + " '");
-            }
+                    // Get destination metadata
+                    rs = stmt.executeQueryInternal("sp_executesql N'SET FMTONLY ON SELECT * FROM " + escapedDestinationTableName + " '");
+                }
 
-            destColumnCount = rs.getMetaData().getColumnCount();
-            destColumnMetadata = new HashMap<>();
-            destCekTable = rs.getCekTable();
+                int destColumnMetadataCount = rs.getMetaData().getColumnCount();
+                destColumnMetadata = new HashMap<>();
+                destCekTable = rs.getCekTable();
 
-            if (!connection.getServerSupportsColumnEncryption()) {
-                metaDataQuery = "select collation_name from sys.columns where " + "object_id=OBJECT_ID('"
-                        + escapedDestinationTableName + "') " + "order by column_id ASC";
-            } else {
-                metaDataQuery = "select collation_name, encryption_type from sys.columns where "
-                        + "object_id=OBJECT_ID('" + escapedDestinationTableName + "') " + "order by column_id ASC";
-            }
+                metaDataQuery = "select * from sys.columns where " + "object_id=OBJECT_ID('" + escapedDestinationTableName + "') " + "order by column_id ASC";
 
-            try (SQLServerStatement statementMoreMetadata = (SQLServerStatement) connection.createStatement();
-                    SQLServerResultSet rsMoreMetaData = statementMoreMetadata.executeQueryInternal(metaDataQuery)) {
-                for (int i = 1; i <= destColumnCount; ++i) {
-                    if (rsMoreMetaData.next()) {
-                        String bulkCopyEncryptionType = null;
-                        if (connection.getServerSupportsColumnEncryption()) {
-                            bulkCopyEncryptionType = rsMoreMetaData.getString("encryption_type");
+                try (SQLServerStatement statementMoreMetadata = (SQLServerStatement) connection.createStatement();
+                        SQLServerResultSet rsMoreMetaData = statementMoreMetadata.executeQueryInternal(metaDataQuery)) {
+                    for (int i = 1; i <= destColumnMetadataCount; ++i) {
+                        if (rsMoreMetaData.next()) {
+                            String bulkCopyEncryptionType = null;
+                            if (connection.getServerSupportsColumnEncryption()) {
+                                bulkCopyEncryptionType = rsMoreMetaData.getString("encryption_type");
+                            }
+                            // Skip computed columns
+                            if (!rsMoreMetaData.getBoolean("is_computed")) {
+                                destColumnMetadata.put(i, new BulkColumnMetaData(rs.getColumn(i), rsMoreMetaData.getString("collation_name"),
+                                        bulkCopyEncryptionType));
+                            }
+                        } else {
+                            destColumnMetadata.put(i, new BulkColumnMetaData(rs.getColumn(i)));
                         }
-                        destColumnMetadata.put(i, new BulkColumnMetaData(rs.getColumn(i),
-                                rsMoreMetaData.getString("collation_name"), bulkCopyEncryptionType));
-                    } else {
-                        destColumnMetadata.put(i, new BulkColumnMetaData(rs.getColumn(i)));
                     }
+                    destColumnCount = destColumnMetadata.size();
+                }
+            } catch (SQLException e) {
+                // Unable to retrieve metadata for destination
+                throw new SQLServerException(SQLServerException.getErrString("R_unableRetrieveColMeta"), e);
+            } finally {
+                if (null != rs) {
+                    rs.close();
+                }
+                if (null != stmt) {
+                    stmt.close();
                 }
             }
-        } catch (SQLException e) {
-            // Unable to retrieve metadata for destination
-            throw new SQLServerException(SQLServerException.getErrString("R_unableRetrieveColMeta"), e);
-        } finally {
-            if (null != rs)
-                rs.close();
-            if (null != stmt)
-                stmt.close();
         }
     }
 
@@ -1779,41 +1779,43 @@ public class SQLServerBulkCopy implements java.lang.AutoCloseable, java.io.Seria
      * BulkColumnMetaData object helps to access source metadata from the same place for both ResultSet and File.
      */
     private void getSourceMetadata() throws SQLServerException {
-        srcColumnMetadata = new HashMap<>();
-        int currentColumn;
-        if (null != sourceResultSet) {
-            try {
-                srcColumnCount = sourceResultSetMetaData.getColumnCount();
-                for (int i = 1; i <= srcColumnCount; ++i) {
-                    srcColumnMetadata.put(i,
-                            new BulkColumnMetaData(sourceResultSetMetaData.getColumnName(i),
-                                    (ResultSetMetaData.columnNoNulls != sourceResultSetMetaData.isNullable(i)),
-                                    sourceResultSetMetaData.getPrecision(i), sourceResultSetMetaData.getScale(i),
-                                    sourceResultSetMetaData.getColumnType(i), null));
+        if (null == srcColumnMetadata || srcColumnMetadata.isEmpty()) {
+            srcColumnMetadata = new HashMap<>();
+            int currentColumn;
+            if (null != sourceResultSet) {
+                try {
+                    srcColumnCount = sourceResultSetMetaData.getColumnCount();
+                    for (int i = 1; i <= srcColumnCount; ++i) {
+                        srcColumnMetadata.put(i,
+                                new BulkColumnMetaData(sourceResultSetMetaData.getColumnName(i),
+                                        (ResultSetMetaData.columnNoNulls != sourceResultSetMetaData.isNullable(i)),
+                                        sourceResultSetMetaData.getPrecision(i), sourceResultSetMetaData.getScale(i),
+                                        sourceResultSetMetaData.getColumnType(i), null));
+                    }
+                } catch (SQLException e) {
+                    // Unable to retrieve meta data for destination
+                    throw new SQLServerException(SQLServerException.getErrString("R_unableRetrieveColMeta"), e);
                 }
-            } catch (SQLException e) {
-                // Unable to retrieve meta data for destination
-                throw new SQLServerException(SQLServerException.getErrString("R_unableRetrieveColMeta"), e);
-            }
-        } else if (null != serverBulkData) {
-            Set<Integer> columnOrdinals = serverBulkData.getColumnOrdinals();
-            if (null == columnOrdinals || columnOrdinals.isEmpty()) {
-                throw new SQLServerException(SQLServerException.getErrString("R_unableRetrieveColMeta"), null);
-            } else {
-                srcColumnCount = columnOrdinals.size();
-                for (Integer columnOrdinal : columnOrdinals) {
-                    currentColumn = columnOrdinal;
-                    srcColumnMetadata.put(currentColumn, new BulkColumnMetaData(
-                            serverBulkData.getColumnName(currentColumn), true,
-                            serverBulkData.getPrecision(currentColumn), serverBulkData.getScale(currentColumn),
-                            serverBulkData.getColumnType(currentColumn),
-                            ((serverBulkData instanceof SQLServerBulkCSVFileRecord) ? ((SQLServerBulkCSVFileRecord) serverBulkData)
-                                    .getColumnDateTimeFormatter(currentColumn) : null)));
+            } else if (null != serverBulkData) {
+                Set<Integer> columnOrdinals = serverBulkData.getColumnOrdinals();
+                if (null == columnOrdinals || columnOrdinals.isEmpty()) {
+                    throw new SQLServerException(SQLServerException.getErrString("R_unableRetrieveColMeta"), null);
+                } else {
+                    srcColumnCount = columnOrdinals.size();
+                    for (Integer columnOrdinal : columnOrdinals) {
+                        currentColumn = columnOrdinal;
+                        srcColumnMetadata.put(currentColumn, new BulkColumnMetaData(
+                                serverBulkData.getColumnName(currentColumn), true,
+                                serverBulkData.getPrecision(currentColumn), serverBulkData.getScale(currentColumn),
+                                serverBulkData.getColumnType(currentColumn),
+                                ((serverBulkData instanceof SQLServerBulkCSVFileRecord) ? ((SQLServerBulkCSVFileRecord) serverBulkData)
+                                        .getColumnDateTimeFormatter(currentColumn) : null)));
+                    }
                 }
+            } else {
+                // Unable to retrieve meta data for source
+                throw new SQLServerException(SQLServerException.getErrString("R_unableRetrieveColMeta"), null);
             }
-        } else {
-            // Unable to retrieve meta data for source
-            throw new SQLServerException(SQLServerException.getErrString("R_unableRetrieveColMeta"), null);
         }
     }
 
@@ -2461,7 +2463,12 @@ public class SQLServerBulkCopy implements java.lang.AutoCloseable, java.io.Seria
                             case DATETIME:
                                 if (bulkNullable)
                                     tdsWriter.writeByte((byte) 0x08);
-                                tdsWriter.writeDatetime(colValue.toString());
+
+                                if (colValue instanceof java.sql.Timestamp) {
+                                    tdsWriter.writeDatetime((java.sql.Timestamp) colValue);
+                                } else {
+                                    tdsWriter.writeDatetime(java.sql.Timestamp.valueOf(colValue.toString()));
+                                }
                                 break;
                             default: // DATETIME2
                                 if (2 >= bulkScale)
@@ -2695,15 +2702,15 @@ public class SQLServerBulkCopy implements java.lang.AutoCloseable, java.io.Seria
                 tdsWriter.writeTime((java.sql.Timestamp) colValue, timeBulkScale);
                 break;
 
-            case DATETIME8:
-                writeBulkCopySqlVariantHeader(10, TDSType.DATETIME8.byteValue(), (byte) 0, tdsWriter);
-                tdsWriter.writeDatetime(colValue.toString());
-                break;
-
             case DATETIME4:
                 // when the type is ambiguous, we write to bigger type
+            case DATETIME8:
                 writeBulkCopySqlVariantHeader(10, TDSType.DATETIME8.byteValue(), (byte) 0, tdsWriter);
-                tdsWriter.writeDatetime(colValue.toString());
+                if (colValue instanceof java.sql.Timestamp) {
+                    tdsWriter.writeDatetime((java.sql.Timestamp) colValue);
+                } else {
+                    tdsWriter.writeDatetime(java.sql.Timestamp.valueOf(colValue.toString()));
+                }
                 break;
 
             case DATETIME2N:
diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerConnection.java b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerConnection.java
index ec3e86c3..acdf6723 100644
--- a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerConnection.java
+++ b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerConnection.java
@@ -973,6 +973,19 @@ public class SQLServerConnection implements ISQLServerConnection, java.io.Serial
         delayLoadingLobs = b;
     }
 
+    /** Boolean that indicates whether datetime types are converted to java.time objects using java.time rules */
+    private boolean ignoreOffsetOnDateTimeOffsetConversion = SQLServerDriverBooleanProperty.IGNORE_OFFSET_ON_DATE_TIME_OFFSET_CONVERSION.getDefaultValue();
+
+    @Override
+    public boolean getIgnoreOffsetOnDateTimeOffsetConversion() {
+        return ignoreOffsetOnDateTimeOffsetConversion;
+    }
+
+    @Override
+    public void setIgnoreOffsetOnDateTimeOffsetConversion(boolean ignoreOffsetOnDateTimeOffsetConversion) {
+        this.ignoreOffsetOnDateTimeOffsetConversion = ignoreOffsetOnDateTimeOffsetConversion;
+    }
+
     /** Session Recovery Object */
     private transient IdleConnectionResiliency sessionRecovery = new IdleConnectionResiliency(this);
 
@@ -983,8 +996,10 @@ public class SQLServerConnection implements ISQLServerConnection, java.io.Serial
     /** global system ColumnEncryptionKeyStoreProviders */
     static Map<String, SQLServerColumnEncryptionKeyStoreProvider> globalSystemColumnEncryptionKeyStoreProviders = new HashMap<>();
 
+    static boolean isWindows = System.getProperty("os.name").toLowerCase(Locale.ENGLISH).startsWith("windows");
+
     static {
-        if (System.getProperty("os.name").toLowerCase(Locale.ENGLISH).startsWith("windows")) {
+        if (isWindows) {
             SQLServerColumnEncryptionCertificateStoreProvider provider = new SQLServerColumnEncryptionCertificateStoreProvider();
             globalSystemColumnEncryptionKeyStoreProviders.put(provider.getName(), provider);
         }
@@ -1426,6 +1441,9 @@ public class SQLServerConnection implements ISQLServerConnection, java.io.Serial
     /** integrated authentication scheme */
     private AuthenticationScheme intAuthScheme = AuthenticationScheme.NATIVE_AUTHENTICATION;
 
+    /** use default native GSS-API Credential flag */
+    private boolean useDefaultGSSCredential = SQLServerDriverBooleanProperty.USE_DEFAULT_GSS_CREDENTIAL.getDefaultValue();
+
     /** impersonated user credential */
     private transient GSSCredential impersonatedUserCred;
 
@@ -2480,6 +2498,11 @@ public class SQLServerConnection implements ISQLServerConnection, java.io.Serial
                         impersonatedUserCred = (GSSCredential) activeConnectionProperties.get(sPropKey);
                         isUserCreatedCredential = true;
                     }
+                    sPropKey = SQLServerDriverBooleanProperty.USE_DEFAULT_GSS_CREDENTIAL.toString();
+                    sPropValue = activeConnectionProperties.getProperty(sPropKey);
+                    if(null != sPropValue && isWindows) {
+                        useDefaultGSSCredential = isBooleanPropertyOn(sPropKey, sPropValue);
+                    }
                 } else if (intAuthScheme == AuthenticationScheme.NTLM) {
                     String sPropKeyDomain = SQLServerDriverStringProperty.DOMAIN.toString();
                     String sPropValueDomain = activeConnectionProperties.getProperty(sPropKeyDomain);
@@ -2925,6 +2948,14 @@ public class SQLServerConnection implements ISQLServerConnection, java.io.Serial
                 }
                 delayLoadingLobs = isBooleanPropertyOn(sPropKey, sPropValue);
 
+                sPropKey = SQLServerDriverBooleanProperty.IGNORE_OFFSET_ON_DATE_TIME_OFFSET_CONVERSION.toString();
+                sPropValue = activeConnectionProperties.getProperty(sPropKey);
+                if (null == sPropValue) {
+                    sPropValue = Boolean.toString(SQLServerDriverBooleanProperty.IGNORE_OFFSET_ON_DATE_TIME_OFFSET_CONVERSION.getDefaultValue());
+                    activeConnectionProperties.setProperty(sPropKey, sPropValue);
+                }
+                ignoreOffsetOnDateTimeOffsetConversion = isBooleanPropertyOn(sPropKey, sPropValue);
+
                 FailoverInfo fo = null;
                 String databaseNameProperty = SQLServerDriverStringProperty.DATABASE_NAME.toString();
                 String serverNameProperty = SQLServerDriverStringProperty.SERVER_NAME.toString();
@@ -5097,9 +5128,9 @@ public class SQLServerConnection implements ISQLServerConnection, java.io.Serial
                 authentication = new AuthenticationJNI(this, currentConnectPlaceHolder.getServerName(),
                         currentConnectPlaceHolder.getPortNumber());
             } else if (AuthenticationScheme.JAVA_KERBEROS == intAuthScheme) {
-                if (null != impersonatedUserCred) {
+                if (null != impersonatedUserCred || useDefaultGSSCredential) {
                     authentication = new KerbAuthentication(this, currentConnectPlaceHolder.getServerName(),
-                            currentConnectPlaceHolder.getPortNumber(), impersonatedUserCred, isUserCreatedCredential);
+                            currentConnectPlaceHolder.getPortNumber(), impersonatedUserCred, isUserCreatedCredential, useDefaultGSSCredential);
                 } else {
                     authentication = new KerbAuthentication(this, currentConnectPlaceHolder.getServerName(),
                             currentConnectPlaceHolder.getPortNumber());
@@ -5800,8 +5831,7 @@ public class SQLServerConnection implements ISQLServerConnection, java.io.Serial
             } else if (authenticationString
                     .equalsIgnoreCase(SqlAuthentication.ACTIVE_DIRECTORY_INTEGRATED.toString())) {
                 // If operating system is windows and mssql-jdbc_auth is loaded then choose the DLL authentication.
-                if (System.getProperty("os.name").toLowerCase(Locale.ENGLISH).startsWith("windows")
-                        && AuthenticationJNI.isDllLoaded()) {
+                if (isWindows && AuthenticationJNI.isDllLoaded()) {
                     try {
                         FedAuthDllInfo dllInfo = AuthenticationJNI.getAccessTokenForWindowsIntegrated(
                                 fedAuthInfo.stsurl, fedAuthInfo.spn, clientConnectionId.toString(),
@@ -6328,9 +6358,10 @@ public class SQLServerConnection implements ISQLServerConnection, java.io.Serial
             serverName = currentConnectPlaceHolder.getFullServerName();
         } else {
             serverName = activeConnectionProperties.getProperty(SQLServerDriverStringProperty.SERVER_NAME.toString());
-            if (null != activeConnectionProperties.getProperty(SQLServerDriverStringProperty.INSTANCE_NAME.toString())) {
-                serverName += "\\" +
-                        activeConnectionProperties.getProperty(SQLServerDriverStringProperty.INSTANCE_NAME.toString());
+            if (null != activeConnectionProperties
+                    .getProperty(SQLServerDriverStringProperty.INSTANCE_NAME.toString())) {
+                serverName += "\\" + activeConnectionProperties
+                        .getProperty(SQLServerDriverStringProperty.INSTANCE_NAME.toString());
             }
         }
 
@@ -7278,6 +7309,9 @@ public class SQLServerConnection implements ISQLServerConnection, java.io.Serial
 
     /** original delayLoadingLobs */
     private boolean originalDelayLoadingLobs;
+    
+    /** original ignoreOffsetOnDateTimeOffsetConversion */
+    private boolean originalIgnoreOffsetOnDateTimeOffsetConversion;
 
     /** Always Encrypted version */
     private int aeVersion = TDS.COLUMNENCRYPTION_NOT_SUPPORTED;
@@ -7303,6 +7337,7 @@ public class SQLServerConnection implements ISQLServerConnection, java.io.Serial
                 openStatements = new LinkedList<>();
                 originalUseFmtOnly = useFmtOnly;
                 originalDelayLoadingLobs = delayLoadingLobs;
+                originalIgnoreOffsetOnDateTimeOffsetConversion = ignoreOffsetOnDateTimeOffsetConversion;
                 requestStarted = true;
             }
         } finally {
@@ -7361,6 +7396,9 @@ public class SQLServerConnection implements ISQLServerConnection, java.io.Serial
                 if (delayLoadingLobs != originalDelayLoadingLobs) {
                     setDelayLoadingLobs(originalDelayLoadingLobs);
                 }
+                if (ignoreOffsetOnDateTimeOffsetConversion != originalIgnoreOffsetOnDateTimeOffsetConversion) {
+                    setIgnoreOffsetOnDateTimeOffsetConversion(originalIgnoreOffsetOnDateTimeOffsetConversion);
+                }
                 sqlWarnings = originalSqlWarnings;
                 if (null != openStatements) {
                     while (!openStatements.isEmpty()) {
@@ -7389,7 +7427,8 @@ public class SQLServerConnection implements ISQLServerConnection, java.io.Serial
     String replaceParameterMarkers(String sqlSrc, int[] paramPositions, Parameter[] params,
             boolean isReturnValueSyntax) {
         final int MAX_PARAM_NAME_LEN = 6;
-        char[] sqlDst = new char[sqlSrc.length() + params.length * (MAX_PARAM_NAME_LEN + OUT.length)];
+        char[] sqlDst = new char[sqlSrc.length() + (params.length * (MAX_PARAM_NAME_LEN + OUT.length))
+                + (params.length * 2)];
         int dstBegin = 0;
         int srcBegin = 0;
         int nParam = 0;
@@ -7403,8 +7442,8 @@ public class SQLServerConnection implements ISQLServerConnection, java.io.Serial
             if (sqlSrc.length() == srcEnd)
                 break;
 
-            dstBegin += makeParamName(nParam++, sqlDst, dstBegin);
-            srcBegin = srcEnd + 1;
+            dstBegin += makeParamName(nParam++, sqlDst, dstBegin, true);
+            srcBegin = srcEnd + 1 <= sqlSrc.length() - 1 && sqlSrc.charAt(srcEnd + 1) == ' ' ? srcEnd + 2 : srcEnd + 1;
 
             if (params[paramIndex++].isOutput() && (!isReturnValueSyntax || paramIndex > 1)) {
                 System.arraycopy(OUT, 0, sqlDst, dstBegin, OUT.length);
@@ -7423,31 +7462,86 @@ public class SQLServerConnection implements ISQLServerConnection, java.io.Serial
      * @param name
      *        the parameter name
      * @param offset
+     *        the offset
+     * @param isPreparedSQL
+     *        if the param is for build preparedSQL
      * @return int
      */
-    static int makeParamName(int nParam, char[] name, int offset) {
-        name[offset + 0] = '@';
-        name[offset + 1] = 'P';
+    static int makeParamName(int nParam, char[] name, int offset, boolean isPreparedSQL) {
+        buildParamInitial(name, offset, isPreparedSQL);
         if (nParam < 10) {
-            name[offset + 2] = (char) ('0' + nParam);
-            return 3;
+            return buildParamLt10(nParam, name, offset, isPreparedSQL);
         } else {
             if (nParam < 100) {
-                int nBase = 2;
-                while (true) { // make a char[] representation of the param number 2.26
-                    if (nParam < nBase * 10) {
-                        name[offset + 2] = (char) ('0' + (nBase - 1));
-                        name[offset + 3] = (char) ('0' + (nParam - ((nBase - 1) * 10)));
-                        return 4;
-                    }
-                    nBase++;
-                }
+                return buildParamLt100(nParam, name, offset, isPreparedSQL);
             } else {
-                String sParam = "" + nParam;
-                sParam.getChars(0, sParam.length(), name, offset + 2);
-                return 2 + sParam.length();
+                return buildParamMt100(nParam, name, offset, isPreparedSQL);
+            }
+        }
+    }
+
+    private static void buildParamInitial(char[] name, int offset, boolean isPreparedSQL) {
+        int preparedSQLOffset = 0;
+        if (isPreparedSQL) {
+            name[offset + 0] = ' ';
+            preparedSQLOffset++;
+        }
+        name[offset + preparedSQLOffset + 0] = '@';
+        name[offset + preparedSQLOffset + 1] = 'P';
+    }
+
+    private static int buildParamLt10(int nParam, char[] name, int offset, boolean isPreparedSQL) {
+        int preparedSQLOffset = 0;
+
+        if (isPreparedSQL) {
+            preparedSQLOffset++;
+        }
+
+        name[offset + preparedSQLOffset + 2] = (char) ('0' + nParam);
+
+        if (isPreparedSQL) {
+            name[offset + 4] = ' ';
+            return 5;
+        }
+
+        return 3;
+    }
+
+    private static int buildParamLt100(int nParam, char[] name, int offset, boolean isPreparedSQL) {
+        int nBase = 2;
+        int preparedSQLOffset = 0;
+
+        if (isPreparedSQL) {
+            preparedSQLOffset = 1;
+        }
+
+        while (true) { // make a char[] representation of the param number 2.26
+            if (nParam < nBase * 10) {
+                name[offset + preparedSQLOffset + 2] = (char) ('0' + (nBase - 1));
+                name[offset + preparedSQLOffset + 3] = (char) ('0' + (nParam - ((nBase - 1) * 10)));
+
+                if (isPreparedSQL) {
+                    name[offset + 5] = ' ';
+                    preparedSQLOffset++;
+                }
+
+                return 4 + preparedSQLOffset;
             }
+            nBase++;
+        }
+    }
+
+    private static int buildParamMt100(int nParam, char[] name, int offset, boolean isPreparedSQL) {
+        int preparedSQLOffset = 0;
+        String sParam = Integer.toString(nParam);
+
+        if (isPreparedSQL) {
+            preparedSQLOffset++;
+            sParam = nParam + " ";
         }
+
+        sParam.getChars(0, sParam.length(), name, offset + preparedSQLOffset + 2);
+        return 2 + sParam.length() + preparedSQLOffset;
     }
 
     /**
@@ -8044,19 +8138,18 @@ public class SQLServerConnection implements ISQLServerConnection, java.io.Serial
             return supportsTransactions;
         }
 
-        supportsTransactions = true;
-
         try {
             this.connectionCommand("SELECT @@TRANCOUNT", "SQLServerConnection.supportsTransactions");
         } catch (SQLServerException e) {
-            if (e.getMessage().equals(SQLServerException.getErrString("R_transactionsNotSupported"))) {
+            if (e.getMessage().trim().equals(SQLServerException.getErrString("R_transactionsNotSupported"))) {
                 supportsTransactions = false;
                 return false;
             }
             throw e;
         }
 
-        return supportsTransactions;
+        supportsTransactions = true;
+        return true;
     }
 
     /**
diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerConnectionPoolProxy.java b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerConnectionPoolProxy.java
index 16864435..248957b3 100644
--- a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerConnectionPoolProxy.java
+++ b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerConnectionPoolProxy.java
@@ -632,6 +632,16 @@ class SQLServerConnectionPoolProxy implements ISQLServerConnection, java.io.Seri
         wrappedConnection.setDelayLoadingLobs(delayLoadingLobs);
     }
 
+    @Override
+    public boolean getIgnoreOffsetOnDateTimeOffsetConversion() {
+        return wrappedConnection.getIgnoreOffsetOnDateTimeOffsetConversion();
+    }
+
+    @Override
+    public void setIgnoreOffsetOnDateTimeOffsetConversion(boolean ignoreOffsetOnDateTimeOffsetConversion) {
+        wrappedConnection.setIgnoreOffsetOnDateTimeOffsetConversion(ignoreOffsetOnDateTimeOffsetConversion);
+    }
+
     @Override
     public void setIPAddressPreference(String iPAddressPreference) {
         wrappedConnection.setIPAddressPreference(iPAddressPreference);
diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerDataSource.java b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerDataSource.java
index b7e01811..52182bbf 100644
--- a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerDataSource.java
+++ b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerDataSource.java
@@ -239,6 +239,17 @@ public class SQLServerDataSource
                 SQLServerDriverObjectProperty.GSS_CREDENTIAL.getDefaultValue());
     }
 
+    @Override
+    public void setUseDefaultGSSCredential(boolean enable) {
+        setBooleanProperty(connectionProps, SQLServerDriverBooleanProperty.USE_DEFAULT_GSS_CREDENTIAL.toString(), enable);
+    }
+
+    @Override
+    public boolean getUseDefaultGSSCredential() {
+       return getBooleanProperty(connectionProps, SQLServerDriverBooleanProperty.USE_DEFAULT_GSS_CREDENTIAL.toString(),
+               SQLServerDriverBooleanProperty.USE_DEFAULT_GSS_CREDENTIAL.getDefaultValue());
+    }
+
     @Override
     public void setAccessToken(String accessToken) {
         setStringProperty(connectionProps, SQLServerDriverStringProperty.ACCESS_TOKEN.toString(), accessToken);
@@ -1030,6 +1041,18 @@ public class SQLServerDataSource
                 SQLServerDriverStringProperty.JAAS_CONFIG_NAME.getDefaultValue());
     }
 
+    @Override
+    public boolean getUseDefaultJaasConfig() {
+        return getBooleanProperty(connectionProps, SQLServerDriverBooleanProperty.USE_DEFAULT_JAAS_CONFIG.toString(),
+                SQLServerDriverBooleanProperty.USE_DEFAULT_JAAS_CONFIG.getDefaultValue());
+    }
+
+    @Override
+    public void setUseDefaultJaasConfig(boolean useDefaultJaasConfig) {
+        setBooleanProperty(connectionProps, SQLServerDriverBooleanProperty.USE_DEFAULT_JAAS_CONFIG.toString(),
+                useDefaultJaasConfig);
+    }
+
     /**
      * @deprecated This method is deprecated. Use {@link SQLServerDataSource#setUser(String user)} instead.
      *
diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerDatabaseMetaData.java b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerDatabaseMetaData.java
index af82279d..8141f538 100644
--- a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerDatabaseMetaData.java
+++ b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerDatabaseMetaData.java
@@ -1518,7 +1518,7 @@ public final class SQLServerDatabaseMetaData implements java.sql.DatabaseMetaDat
         if (loggerExternal.isLoggable(Level.FINER) && Util.isActivityTraceOn()) {
             loggerExternal.finer(toString() + ACTIVITY_ID + ActivityCorrelator.getCurrent().toString());
         }
-        return getSchemasInternal(catalog, schemaPattern);
+        return getSchemasInternal(catalog, escapeIDName(schemaPattern));
     }
 
     @Override
@@ -1709,12 +1709,30 @@ public final class SQLServerDatabaseMetaData implements java.sql.DatabaseMetaDat
         }
         checkClosed();
         String result = "";
-        try (SQLServerStatement s = (SQLServerStatement) connection.createStatement();
-                SQLServerResultSet rs = s.executeQueryInternal("select system_user")) {
-            // Select system_user will always return a row.
-            boolean next = rs.next();
-            assert next;
-            result = rs.getString(1);
+        try (SQLServerStatement s = (SQLServerStatement) connection.createStatement()) {
+            try (SQLServerResultSet rs = s.executeQueryInternal("SELECT SYSTEM_USER")) {
+                // Select system_user will always return a row.
+                boolean next = rs.next();
+                assert next;
+                result = rs.getString(1);
+            } catch (SQLServerException e) {
+                // execution using impersonated security context is disallowed for Azure SQL Server so return CURRENT_USER instead
+                if (e.getErrorCode() == SQLServerException.IMPERSONATION_CONTEXT_NOT_SUPPORTED) {
+                    if (loggerExternal.isLoggable(Level.FINEST)) {
+                        loggerExternal.finest(toString()
+                                + " Impersonation context is not supported in this version of SQL Server. Re-try getting CURRENT_USER");
+                    }
+              
+                    try (SQLServerResultSet rs = s.executeQueryInternal("SELECT CURRENT_USER")) {
+                        boolean next = rs.next();
+                        assert next;
+                        result = rs.getString(1);
+                    }
+                } else {
+                    // re-throw
+                    throw e;
+                }
+            }
         }
         return result;
     }
diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerDriver.java b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerDriver.java
index 1bd92024..d5f40111 100644
--- a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerDriver.java
+++ b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerDriver.java
@@ -693,7 +693,10 @@ enum SQLServerDriverBooleanProperty {
     USE_BULK_COPY_FOR_BATCH_INSERT("useBulkCopyForBatchInsert", false),
     USE_FMT_ONLY("useFmtOnly", false),
     SEND_TEMPORAL_DATATYPES_AS_STRING_FOR_BULK_COPY("sendTemporalDataTypesAsStringForBulkCopy", true),
-    DELAY_LOADING_LOBS("delayLoadingLobs", true);
+    DELAY_LOADING_LOBS("delayLoadingLobs", true),
+    IGNORE_OFFSET_ON_DATE_TIME_OFFSET_CONVERSION("ignoreOffsetOnDateTimeOffsetConversion", false),
+    USE_DEFAULT_JAAS_CONFIG("useDefaultJaasConfig", false),
+    USE_DEFAULT_GSS_CREDENTIAL("useDefaultGSSCredential", false);
 
     private final String name;
     private final boolean defaultValue;
@@ -747,7 +750,7 @@ public final class SQLServerDriver implements java.sql.Driver {
                     SQLServerDriverStringProperty.DATABASE_NAME.getDefaultValue(), false, null),
             new SQLServerDriverPropertyInfo(SQLServerDriverBooleanProperty.DISABLE_STATEMENT_POOLING.toString(),
                     Boolean.toString(SQLServerDriverBooleanProperty.DISABLE_STATEMENT_POOLING.getDefaultValue()), false,
-                    new String[] {"true", "false"}),
+                    TRUE_FALSE),
             new SQLServerDriverPropertyInfo(SQLServerDriverStringProperty.ENCRYPT.toString(),
                     SQLServerDriverStringProperty.ENCRYPT.getDefaultValue(), false,
                     new String[] {EncryptOption.FALSE.toString(), EncryptOption.NO.toString(),
@@ -767,6 +770,9 @@ public final class SQLServerDriver implements java.sql.Driver {
             new SQLServerDriverPropertyInfo(SQLServerDriverBooleanProperty.INTEGRATED_SECURITY.toString(),
                     Boolean.toString(SQLServerDriverBooleanProperty.INTEGRATED_SECURITY.getDefaultValue()), false,
                     TRUE_FALSE),
+            new SQLServerDriverPropertyInfo(SQLServerDriverBooleanProperty.USE_DEFAULT_GSS_CREDENTIAL.toString(),
+                    Boolean.toString(SQLServerDriverBooleanProperty.USE_DEFAULT_GSS_CREDENTIAL.getDefaultValue()), false,
+                    TRUE_FALSE),
             new SQLServerDriverPropertyInfo(SQLServerDriverStringProperty.KEY_STORE_AUTHENTICATION.toString(),
                     SQLServerDriverStringProperty.KEY_STORE_AUTHENTICATION.getDefaultValue(), false,
                     new String[] {KeyStoreAuthentication.JAVA_KEYSTORE_PASSWORD.toString()}),
@@ -892,6 +898,9 @@ public final class SQLServerDriver implements java.sql.Driver {
                     null),
             new SQLServerDriverPropertyInfo(SQLServerDriverStringProperty.JAAS_CONFIG_NAME.toString(),
                     SQLServerDriverStringProperty.JAAS_CONFIG_NAME.getDefaultValue(), false, null),
+            new SQLServerDriverPropertyInfo(SQLServerDriverBooleanProperty.USE_DEFAULT_JAAS_CONFIG.toString(),
+                    Boolean.toString(SQLServerDriverBooleanProperty.USE_DEFAULT_JAAS_CONFIG.getDefaultValue()), false,
+                    TRUE_FALSE),
             new SQLServerDriverPropertyInfo(SQLServerDriverStringProperty.SSL_PROTOCOL.toString(),
                     SQLServerDriverStringProperty.SSL_PROTOCOL.getDefaultValue(), false,
                     new String[] {SSLProtocol.TLS.toString(), SSLProtocol.TLS_V10.toString(),
diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerException.java b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerException.java
index 374d0c5c..6721c7b5 100644
--- a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerException.java
+++ b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerException.java
@@ -17,7 +17,8 @@ enum SQLState {
     DATA_EXCEPTION_DATETIME_FIELD_OVERFLOW("22008"),
     NUMERIC_DATA_OUT_OF_RANGE("22003"),
     DATA_EXCEPTION_LENGTH_MISMATCH("22026"),
-    COL_NOT_FOUND("42S22");
+    COL_NOT_FOUND("42S22"),
+    ERROR_IN_ASSIGNMENT("22005");
 
     private final String sqlStateCode;
 
@@ -59,6 +60,7 @@ public final class SQLServerException extends java.sql.SQLException {
     static final String EXCEPTION_XOPEN_CONNECTION_CANT_ESTABLISH = "08001";
     static final String EXCEPTION_XOPEN_CONNECTION_DOES_NOT_EXIST = "08003";
     static final String EXCEPTION_XOPEN_CONNECTION_FAILURE = "08006"; // After connection was connected OK
+    static final String EXCEPTION_XOPEN_ERROR_IN_ASSIGNMENT = "22005"; // Error code is the same in both SQL-99 and X/Open
 
     static final String LOG_CLIENT_CONNECTION_ID_PREFIX = " ClientConnectionId:";
 
@@ -66,8 +68,9 @@ public final class SQLServerException extends java.sql.SQLException {
     static final int LOGON_FAILED = 18456;
     static final int PASSWORD_EXPIRED = 18488;
     static final int USER_ACCOUNT_LOCKED = 18486;
-    static final java.util.logging.Logger exLogger = java.util.logging.Logger
-            .getLogger("com.microsoft.sqlserver.jdbc.internals.SQLServerException");
+
+    // Built-in function '%.*ls' in impersonation context is not supported in this version of SQL Server.
+    static final int IMPERSONATION_CONTEXT_NOT_SUPPORTED = 40529;
 
     // Facility for driver-specific error codes
     static final int DRIVER_ERROR_NONE = 0;
@@ -84,6 +87,9 @@ public final class SQLServerException extends java.sql.SQLException {
     static final int DATA_CLASSIFICATION_INVALID_LABEL_INDEX = 12;
     static final int DATA_CLASSIFICATION_INVALID_INFORMATION_TYPE_INDEX = 13;
 
+    static final java.util.logging.Logger exLogger = java.util.logging.Logger
+            .getLogger("com.microsoft.sqlserver.jdbc.internals.SQLServerException");
+
     /** driver error code */
     private int driverErrorCode = DRIVER_ERROR_NONE;
 
@@ -302,6 +308,8 @@ public final class SQLServerException extends java.sql.SQLException {
                     return "08S01";
                 case SQLServerException.EXCEPTION_XOPEN_CONNECTION_FAILURE:
                     return "08S01";
+                case SQLServerException.EXCEPTION_XOPEN_ERROR_IN_ASSIGNMENT:
+                    return "22005";
                 default:
                     return "";
             }
diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerMSAL4JUtils.java b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerMSAL4JUtils.java
index 5554fa3b..c8f2fb34 100644
--- a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerMSAL4JUtils.java
+++ b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerMSAL4JUtils.java
@@ -24,6 +24,8 @@ import java.util.concurrent.CompletableFuture;
 import java.util.concurrent.ExecutionException;
 import java.util.concurrent.ExecutorService;
 import java.util.concurrent.Executors;
+import java.util.concurrent.locks.Lock;
+import java.util.concurrent.locks.ReentrantLock;
 import java.util.logging.Level;
 
 import javax.security.auth.kerberos.KerberosPrincipal;
@@ -69,6 +71,8 @@ class SQLServerMSAL4JUtils {
         throw new UnsupportedOperationException(SQLServerException.getErrString("R_notSupported"));
     }
 
+    private static final Lock lock = new ReentrantLock();
+
     static SqlAuthenticationToken getSqlFedAuthToken(SqlFedAuthInfo fedAuthInfo, String user, String password,
             String authenticationString) throws SQLServerException {
         ExecutorService executorService = Executors.newSingleThreadExecutor();
@@ -77,6 +81,8 @@ class SQLServerMSAL4JUtils {
             logger.finest(LOGCONTEXT + authenticationString + ": get FedAuth token for user: " + user);
         }
 
+        lock.lock();
+
         try {
             final PublicClientApplication pca = PublicClientApplication
                     .builder(ActiveDirectoryAuthentication.JDBC_FEDAUTH_CLIENT_ID).executorService(executorService)
@@ -104,6 +110,7 @@ class SQLServerMSAL4JUtils {
         } catch (MalformedURLException | ExecutionException e) {
             throw getCorrectedException(e, user, authenticationString);
         } finally {
+            lock.unlock();
             executorService.shutdown();
         }
     }
@@ -122,6 +129,8 @@ class SQLServerMSAL4JUtils {
         Set<String> scopes = new HashSet<>();
         scopes.add(scope);
 
+        lock.lock();
+
         try {
             IClientCredential credential = ClientCredentialFactory.createFromSecret(aadPrincipalSecret);
             ConfidentialClientApplication clientApplication = ConfidentialClientApplication
@@ -148,6 +157,7 @@ class SQLServerMSAL4JUtils {
         } catch (MalformedURLException | ExecutionException e) {
             throw getCorrectedException(e, aadPrincipalID, authenticationString);
         } finally {
+            lock.unlock();
             executorService.shutdown();
         }
     }
@@ -168,6 +178,8 @@ class SQLServerMSAL4JUtils {
         Set<String> scopes = new HashSet<>();
         scopes.add(scope);
 
+        lock.lock();
+
         try {
             ConfidentialClientApplication clientApplication = null;
 
@@ -249,6 +261,7 @@ class SQLServerMSAL4JUtils {
             throw getCorrectedException(e, aadPrincipalID, authenticationString);
 
         } finally {
+            lock.unlock();
             executorService.shutdown();
         }
     }
@@ -269,6 +282,8 @@ class SQLServerMSAL4JUtils {
                     + "realm name:" + kerberosPrincipal.getRealm());
         }
 
+        lock.lock();
+
         try {
             final PublicClientApplication pca = PublicClientApplication
                     .builder(ActiveDirectoryAuthentication.JDBC_FEDAUTH_CLIENT_ID).executorService(executorService)
@@ -296,6 +311,7 @@ class SQLServerMSAL4JUtils {
         } catch (IOException | ExecutionException e) {
             throw getCorrectedException(e, user, authenticationString);
         } finally {
+            lock.unlock();
             executorService.shutdown();
         }
     }
@@ -308,6 +324,8 @@ class SQLServerMSAL4JUtils {
             logger.finest(LOGCONTEXT + authenticationString + ": get FedAuth token interactive for user: " + user);
         }
 
+        lock.lock();
+
         try {
             PublicClientApplication pca = PublicClientApplication
                     .builder(ActiveDirectoryAuthentication.JDBC_FEDAUTH_CLIENT_ID).executorService(executorService)
@@ -384,6 +402,7 @@ class SQLServerMSAL4JUtils {
         } catch (MalformedURLException | URISyntaxException | ExecutionException e) {
             throw getCorrectedException(e, user, authenticationString);
         } finally {
+            lock.unlock();
             executorService.shutdown();
         }
     }
diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerParameterMetaData.java b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerParameterMetaData.java
index 56fce55c..aeb7a99d 100644
--- a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerParameterMetaData.java
+++ b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerParameterMetaData.java
@@ -397,7 +397,8 @@ public final class SQLServerParameterMetaData implements ParameterMetaData {
 
     private boolean isValidParamProc(int n) {
         // Note row 1 is the 'return value' meta data
-        return ((stmtParent.bReturnValueSyntax && isTVP && procMetadata.size() >= n) || procMetadata.size() > n);
+        return (n >= 1 // JDBC index is 1 based
+                && ((stmtParent.bReturnValueSyntax && isTVP && procMetadata.size() >= n) || procMetadata.size() > n));
     }
 
     private boolean isValidParamQuery(int n) {
diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerPreparedStatement.java b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerPreparedStatement.java
index b0006456..cfe57812 100644
--- a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerPreparedStatement.java
+++ b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerPreparedStatement.java
@@ -461,7 +461,7 @@ public class SQLServerPreparedStatement extends SQLServerStatement implements IS
             if (i > 0)
                 sb.append(',');
 
-            int l = SQLServerConnection.makeParamName(i, cParamName, 0);
+            int l = SQLServerConnection.makeParamName(i, cParamName, 0, false);
             String parameterName = String.valueOf(cParamName, 0, l);
             sb.append(parameterName);
             sb.append(' ');
@@ -750,7 +750,7 @@ public class SQLServerPreparedStatement extends SQLServerStatement implements IS
         for (int index = 0; index < params.length; index++) {
             if (JDBCType.TVP == params[index].getJdbcType()) {
                 cParamName = new char[10];
-                int paramNameLen = SQLServerConnection.makeParamName(index, cParamName, 0);
+                int paramNameLen = SQLServerConnection.makeParamName(index, cParamName, 0, false);
                 tdsWriter.writeByte((byte) paramNameLen);
                 tdsWriter.writeString(new String(cParamName, 0, paramNameLen));
             }
@@ -2142,6 +2142,9 @@ public class SQLServerPreparedStatement extends SQLServerStatement implements IS
                             CryptoMetadata cryptoMetadata = c.getCryptoMetadata();
                             int jdbctype;
                             TypeInfo ti = c.getTypeInfo();
+                            if (ti.getUpdatability() == 0) { // Skip read only columns
+                                continue;
+                            }
                             checkValidColumns(ti);
                             if (null != cryptoMetadata) {
                                 jdbctype = cryptoMetadata.getBaseTypeInfo().getSSType().getJDBCType().getIntValue();
diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerResource.java b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerResource.java
index 44db5331..effb8ae6 100644
--- a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerResource.java
+++ b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerResource.java
@@ -197,6 +197,7 @@ public final class SQLServerResource extends ListResourceBundle {
         {"R_lastUpdateCountPropertyDescription", "Ensures that only the last update count is returned from an SQL statement passed to the server."},
         {"R_disableStatementPoolingPropertyDescription", "Disables the statement pooling feature."},
         {"R_integratedSecurityPropertyDescription", "Indicates whether Windows authentication will be used to connect to SQL Server."},
+        {"R_useDefaultGSSCredentialPropertyDescription", "Indicates whether GSSCredential will be created using native GSS-API."},
         {"R_authenticationSchemePropertyDescription", "The authentication scheme to be used for integrated authentication."},
         {"R_lockTimeoutPropertyDescription", "The number of milliseconds to wait before the database reports a lock time-out."},
         {"R_connectRetryCountPropertyDescription", "The number of reconnection attempts if there is a connection failure."},
@@ -451,7 +452,8 @@ public final class SQLServerResource extends ListResourceBundle {
         {"R_kerberosLoginFailedForUsername", "Cannot login with Kerberos principal {0}, check your credentials. {1}"},
         {"R_kerberosLoginFailed", "Kerberos Login failed: {0} due to {1} ({2})"},
         {"R_StoredProcedureNotFound", "Could not find stored procedure ''{0}''."},
-        {"R_jaasConfigurationNamePropertyDescription", "Login configuration file for Kerberos authentication."},
+        {"R_jaasConfigurationNamePropertyDescription", "Login configuration name for Kerberos authentication."},
+        {"R_useDefaultJaasConfigPropertyDescription", "Use the default JAAS configuration for Kerberos authentication."},
         {"R_AKVKeyNotFound", "Key not found: {0}"},
         {"R_SQLVariantSupport", "SQL_VARIANT is not supported in versions of SQL Server before 2008."},
         {"R_invalidProbbytes", "SQL_VARIANT: invalid probBytes for {0} type."},
diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerXAResource.java b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerXAResource.java
index 924ec0d3..7f9c44a2 100644
--- a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerXAResource.java
+++ b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerXAResource.java
@@ -946,7 +946,10 @@ public final class SQLServerXAResource implements javax.transaction.xa.XAResourc
     }
 
     private enum ResourceManagerFailure {
-        CONN_RESET("Connection reset");
+        CONN_RESET("Connection reset"),
+        CONN_RESET_BY_PEER("Connection reset by peer"),
+        CONN_TIMEOUT("Connection timed out"),
+        CONN_RESILIENCY_CLIENT_UNRECOVERABLE(SQLServerException.getErrString("R_crClientUnrecoverable"));
 
         private final String errString;
 
@@ -984,6 +987,10 @@ public final class SQLServerXAResource implements javax.transaction.xa.XAResourc
             return false;
         }
 
+        if (xaLogger.isLoggable(Level.FINE)) {
+            xaLogger.fine(toString() + " Resource manager failure root exception: " + root);
+        }
+
         ResourceManagerFailure err = ResourceManagerFailure.fromString(root.getMessage());
 
         if (null == err) {
@@ -991,16 +998,15 @@ public final class SQLServerXAResource implements javax.transaction.xa.XAResourc
         }
 
         // Add as needed here for future XAER_RMFAIL exceptions
-        if (root instanceof SocketException) {
-            switch (err) {
-                case CONN_RESET:
-                    return true;
-                default:
-                    return false;
-            }
+        switch (err) {
+            case CONN_RESET:
+            case CONN_RESET_BY_PEER:
+            case CONN_TIMEOUT:
+            case CONN_RESILIENCY_CLIENT_UNRECOVERABLE:
+                return true;
+            default:
+                return false;
         }
-
-        return false;
     }
 
 }
diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/Util.java b/src/main/java/com/microsoft/sqlserver/jdbc/Util.java
index fde8e94f..70b9c9aa 100644
--- a/src/main/java/com/microsoft/sqlserver/jdbc/Util.java
+++ b/src/main/java/com/microsoft/sqlserver/jdbc/Util.java
@@ -48,7 +48,8 @@ final class Util {
     private static final Lock LOCK = new ReentrantLock();
 
     static boolean isIBM() {
-        return SYSTEM_JRE.startsWith("IBM");
+        String vmName = System.getProperty("java.vm.name");
+        return SYSTEM_JRE.startsWith("IBM") && vmName.startsWith("IBM");
     }
 
     static String getJVMArchOnWindows() {
diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/dtv.java b/src/main/java/com/microsoft/sqlserver/jdbc/dtv.java
index 7fd3ba44..ab76347b 100644
--- a/src/main/java/com/microsoft/sqlserver/jdbc/dtv.java
+++ b/src/main/java/com/microsoft/sqlserver/jdbc/dtv.java
@@ -3543,13 +3543,13 @@ final class ServerDTVImpl extends DTVImpl {
                 // cannot reuse method
                 int daysIntoCE = getDaysIntoCE(decryptedValue, baseSSType);
 
-                return DDC.convertTemporalToObject(jdbcType, baseSSType, cal, daysIntoCE, 0, 0);
+                return DDC.convertTemporalToObject(con, jdbcType, baseSSType, cal, daysIntoCE, 0, 0);
 
             case TIME:
                 long localNanosSinceMidnight = readNanosSinceMidnightAE(decryptedValue, baseTypeInfo.getScale(),
                         baseSSType);
 
-                return DDC.convertTemporalToObject(jdbcType, SSType.TIME, cal, 0, localNanosSinceMidnight,
+                return DDC.convertTemporalToObject(con, jdbcType, SSType.TIME, cal, 0, localNanosSinceMidnight,
                         baseTypeInfo.getScale());
 
             case DATETIME2:
@@ -3570,7 +3570,7 @@ final class ServerDTVImpl extends DTVImpl {
                 int daysIntoCE2 = getDaysIntoCE(datePortion, baseSSType);
 
                 // Convert the DATETIME2 value to the desired Java type.
-                return DDC.convertTemporalToObject(jdbcType, SSType.DATETIME2, cal, daysIntoCE2,
+                return DDC.convertTemporalToObject(con, jdbcType, SSType.DATETIME2, cal, daysIntoCE2,
                         localNanosSinceMidnight2, baseTypeInfo.getScale());
 
             case SMALLDATETIME:
@@ -3582,7 +3582,7 @@ final class ServerDTVImpl extends DTVImpl {
                 // SQL smalldatetime has less precision. It stores 2 bytes
                 // for the days since SQL Base Date and 2 bytes for minutes
                 // after midnight.
-                return DDC.convertTemporalToObject(jdbcType, SSType.DATETIME, cal,
+                return DDC.convertTemporalToObject(con, jdbcType, SSType.DATETIME, cal,
                         Util.readUnsignedShort(decryptedValue, 0),
                         Util.readUnsignedShort(decryptedValue, 2) * 60L * 1000L, 0);
 
@@ -3597,7 +3597,7 @@ final class ServerDTVImpl extends DTVImpl {
                 // SQL datetime is 4 bytes for days since SQL Base Date
                 // (January 1, 1900 00:00:00 GMT) and 4 bytes for
                 // the number of three hundredths (1/300) of a second since midnight.
-                return DDC.convertTemporalToObject(jdbcType, SSType.DATETIME, cal, Util.readInt(decryptedValue, 0),
+                return DDC.convertTemporalToObject(con, jdbcType, SSType.DATETIME, cal, Util.readInt(decryptedValue, 0),
                         ticksSinceMidnight, 0);
 
             case DATETIMEOFFSET:
@@ -3616,7 +3616,7 @@ final class ServerDTVImpl extends DTVImpl {
 
                 int localMinutesOffset = ByteBuffer.wrap(offsetPortion2).order(ByteOrder.LITTLE_ENDIAN).getShort();
 
-                return DDC.convertTemporalToObject(jdbcType, SSType.DATETIMEOFFSET,
+                return DDC.convertTemporalToObject(con, jdbcType, SSType.DATETIMEOFFSET,
                         new GregorianCalendar(new SimpleTimeZone(localMinutesOffset * 60 * 1000, ""), Locale.US),
                         daysIntoCE3, localNanosSinceMidnight3, baseTypeInfo.getScale());
 


Test patch:
diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerColumnEncryptionCertificateStoreProvider.java b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerColumnEncryptionCertificateStoreProvider.java
index 46a3fa05..1765687b 100644
--- a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerColumnEncryptionCertificateStoreProvider.java
+++ b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerColumnEncryptionCertificateStoreProvider.java
@@ -6,6 +6,8 @@
 package com.microsoft.sqlserver.jdbc;
 
 import java.util.Locale;
+import java.util.concurrent.locks.Lock;
+import java.util.concurrent.locks.ReentrantLock;
 
 
 /**
@@ -17,21 +19,13 @@ public final class SQLServerColumnEncryptionCertificateStoreProvider extends SQL
     static final private java.util.logging.Logger windowsCertificateStoreLogger = java.util.logging.Logger
             .getLogger("com.microsoft.sqlserver.jdbc.SQLServerColumnEncryptionCertificateStoreProvider");
 
-    static boolean isWindows;
-
     String name = "MSSQL_CERTIFICATE_STORE";
 
     static final String LOCAL_MACHINE_DIRECTORY = "LocalMachine";
     static final String CURRENT_USER_DIRECTORY = "CurrentUser";
     static final String MY_CERTIFICATE_STORE = "My";
 
-    static {
-        if (System.getProperty("os.name").toLowerCase(Locale.ENGLISH).startsWith("windows")) {
-            isWindows = true;
-        } else {
-            isWindows = false;
-        }
-    }
+    private static final Lock lock = new ReentrantLock();
 
     /**
      * Constructs a SQLServerColumnEncryptionCertificateStoreProvider.
@@ -63,7 +57,7 @@ public final class SQLServerColumnEncryptionCertificateStoreProvider extends SQL
         windowsCertificateStoreLogger.entering(SQLServerColumnEncryptionCertificateStoreProvider.class.getName(),
                 "decryptColumnEncryptionKey", "Decrypting Column Encryption Key.");
         byte[] plainCek;
-        if (isWindows) {
+        if (SQLServerConnection.isWindows) {
             plainCek = decryptColumnEncryptionKeyWindows(masterKeyPath, encryptionAlgorithm,
                     encryptedColumnEncryptionKey);
         } else {
@@ -78,21 +72,29 @@ public final class SQLServerColumnEncryptionCertificateStoreProvider extends SQL
     public boolean verifyColumnMasterKeyMetadata(String masterKeyPath, boolean allowEnclaveComputations,
             byte[] signature) throws SQLServerException {
         try {
+            lock.lock();
+
             return AuthenticationJNI.VerifyColumnMasterKeyMetadata(masterKeyPath, allowEnclaveComputations, signature);
         } catch (DLLException e) {
             DLLException.buildException(e.getErrCode(), e.getParam1(), e.getParam2(), e.getParam3());
             return false;
+        } finally {
+            lock.unlock();
         }
     }
 
     private byte[] decryptColumnEncryptionKeyWindows(String masterKeyPath, String encryptionAlgorithm,
             byte[] encryptedColumnEncryptionKey) throws SQLServerException {
         try {
+            lock.lock();
+
             return AuthenticationJNI.DecryptColumnEncryptionKey(masterKeyPath, encryptionAlgorithm,
                     encryptedColumnEncryptionKey);
         } catch (DLLException e) {
             DLLException.buildException(e.getErrCode(), e.getParam1(), e.getParam2(), e.getParam3());
             return null;
+        } finally {
+            lock.unlock();
         }
     }
 
diff --git a/src/test/java/com/microsoft/sqlserver/jdbc/AlwaysEncrypted/AESetup.java b/src/test/java/com/microsoft/sqlserver/jdbc/AlwaysEncrypted/AESetup.java
index 53f3b18e..541f2da1 100644
--- a/src/test/java/com/microsoft/sqlserver/jdbc/AlwaysEncrypted/AESetup.java
+++ b/src/test/java/com/microsoft/sqlserver/jdbc/AlwaysEncrypted/AESetup.java
@@ -179,17 +179,19 @@ public class AESetup extends AbstractTest {
         if (!isSqlLinux() && null != serverName && null != url && null != protocol) {
             enclaveProperties = "serverName=" + serverName + ";" + Constants.ENCLAVE_ATTESTATIONURL + "=" + url + ";"
                     + Constants.ENCLAVE_ATTESTATIONPROTOCOL + "=" + protocol;
-            AETestConnectionString = connectionString + ";sendTimeAsDateTime=false" + ";columnEncryptionSetting=enabled"
-                    + ";" + enclaveProperties;
+            AETestConnectionString = connectionString + ";sendTimeAsDateTime=false;columnEncryptionSetting=enabled;"
+                    + enclaveProperties;
 
             // show progress if testing multiple servers
             if (enclaveServer.length > 1) {
                 System.out.println("Testing enclave: " + enclaveProperties);
             }
         } else {
-            AETestConnectionString = connectionString + ";sendTimeAsDateTime=false"
-                    + ";columnEncryptionSetting=enabled";
+            AETestConnectionString = connectionString + ";sendTimeAsDateTime=false;columnEncryptionSetting=enabled;";
         }
+
+        // TODO: update AE test servers to support
+        AETestConnectionString += ";encrypt=false;trustServerCertificate=true;";
     }
 
     @BeforeAll
diff --git a/src/test/java/com/microsoft/sqlserver/jdbc/AlwaysEncrypted/BulkCopySendTemporalDataTypesAsStringAETest.java b/src/test/java/com/microsoft/sqlserver/jdbc/AlwaysEncrypted/BulkCopySendTemporalDataTypesAsStringAETest.java
index d8552ad3..6dd177ab 100644
--- a/src/test/java/com/microsoft/sqlserver/jdbc/AlwaysEncrypted/BulkCopySendTemporalDataTypesAsStringAETest.java
+++ b/src/test/java/com/microsoft/sqlserver/jdbc/AlwaysEncrypted/BulkCopySendTemporalDataTypesAsStringAETest.java
@@ -57,6 +57,7 @@ import com.microsoft.sqlserver.testframework.PrepUtil;
 @Tag(Constants.xSQLv12)
 @Tag(Constants.xAzureSQLDB)
 @Tag(Constants.xAzureSQLDW)
+@Tag(Constants.reqExternalSetup)
 public class BulkCopySendTemporalDataTypesAsStringAETest extends AESetup {
     static String inputFile = "BulkCopyCSVSendTemporalDataTypesAsStringForBulkCopy.csv";
     static String encoding = "UTF-8";
diff --git a/src/test/java/com/microsoft/sqlserver/jdbc/AlwaysEncrypted/CallableStatementTest.java b/src/test/java/com/microsoft/sqlserver/jdbc/AlwaysEncrypted/CallableStatementTest.java
index 58ed2e63..95531d69 100644
--- a/src/test/java/com/microsoft/sqlserver/jdbc/AlwaysEncrypted/CallableStatementTest.java
+++ b/src/test/java/com/microsoft/sqlserver/jdbc/AlwaysEncrypted/CallableStatementTest.java
@@ -53,6 +53,7 @@ import microsoft.sql.DateTimeOffset;
 @Tag(Constants.xSQLv12)
 @Tag(Constants.xAzureSQLDW)
 @Tag(Constants.xAzureSQLDB)
+@Tag(Constants.reqExternalSetup)
 public class CallableStatementTest extends AESetup {
 
     private static String multiStatementsProcedure = AbstractSQLGenerator
diff --git a/src/test/java/com/microsoft/sqlserver/jdbc/AlwaysEncrypted/JDBCEncryptionDecryptionTest.java b/src/test/java/com/microsoft/sqlserver/jdbc/AlwaysEncrypted/JDBCEncryptionDecryptionTest.java
index d4c2771c..7376baca 100644
--- a/src/test/java/com/microsoft/sqlserver/jdbc/AlwaysEncrypted/JDBCEncryptionDecryptionTest.java
+++ b/src/test/java/com/microsoft/sqlserver/jdbc/AlwaysEncrypted/JDBCEncryptionDecryptionTest.java
@@ -64,6 +64,7 @@ import microsoft.sql.DateTimeOffset;
 @Tag(Constants.xSQLv12)
 @Tag(Constants.xAzureSQLDW)
 @Tag(Constants.xAzureSQLDB)
+@Tag(Constants.reqExternalSetup)
 public class JDBCEncryptionDecryptionTest extends AESetup {
     private boolean nullable = false;
 
diff --git a/src/test/java/com/microsoft/sqlserver/jdbc/AlwaysEncrypted/MultiUserAKVTest.java b/src/test/java/com/microsoft/sqlserver/jdbc/AlwaysEncrypted/MultiUserAKVTest.java
index c3332158..2619a79b 100644
--- a/src/test/java/com/microsoft/sqlserver/jdbc/AlwaysEncrypted/MultiUserAKVTest.java
+++ b/src/test/java/com/microsoft/sqlserver/jdbc/AlwaysEncrypted/MultiUserAKVTest.java
@@ -58,6 +58,7 @@ import com.microsoft.sqlserver.testframework.PrepUtil;
 @Tag(Constants.xSQLv12)
 @Tag(Constants.xAzureSQLDW)
 @Tag(Constants.xAzureSQLDB)
+@Tag(Constants.reqExternalSetup)
 public class MultiUserAKVTest extends AESetup {
 
     private static Map<String, SQLServerColumnEncryptionKeyStoreProvider> requiredKeyStoreProvider = new HashMap<>();
diff --git a/src/test/java/com/microsoft/sqlserver/jdbc/AlwaysEncrypted/ParameterMetaDataCacheTest.java b/src/test/java/com/microsoft/sqlserver/jdbc/AlwaysEncrypted/ParameterMetaDataCacheTest.java
index 5f84e7cb..cef219d4 100644
--- a/src/test/java/com/microsoft/sqlserver/jdbc/AlwaysEncrypted/ParameterMetaDataCacheTest.java
+++ b/src/test/java/com/microsoft/sqlserver/jdbc/AlwaysEncrypted/ParameterMetaDataCacheTest.java
@@ -32,6 +32,7 @@ import com.microsoft.sqlserver.testframework.PrepUtil;
 @Tag(Constants.xSQLv11)
 @Tag(Constants.xSQLv12)
 @Tag(Constants.xSQLv14)
+@Tag(Constants.reqExternalSetup)
 public class ParameterMetaDataCacheTest extends AESetup {
 
     @BeforeAll
diff --git a/src/test/java/com/microsoft/sqlserver/jdbc/AlwaysEncrypted/PrecisionScaleTest.java b/src/test/java/com/microsoft/sqlserver/jdbc/AlwaysEncrypted/PrecisionScaleTest.java
index 5343b1fc..162fe8ce 100644
--- a/src/test/java/com/microsoft/sqlserver/jdbc/AlwaysEncrypted/PrecisionScaleTest.java
+++ b/src/test/java/com/microsoft/sqlserver/jdbc/AlwaysEncrypted/PrecisionScaleTest.java
@@ -42,6 +42,7 @@ import com.microsoft.sqlserver.testframework.PrepUtil;
 @Tag(Constants.xSQLv12)
 @Tag(Constants.xAzureSQLDW)
 @Tag(Constants.xAzureSQLDB)
+@Tag(Constants.reqExternalSetup)
 public class PrecisionScaleTest extends AESetup {
     private static java.util.Date date = null;
     private static int offsetFromGMT = 0;
diff --git a/src/test/java/com/microsoft/sqlserver/jdbc/AlwaysEncrypted/RegressionAlwaysEncryptedTest.java b/src/test/java/com/microsoft/sqlserver/jdbc/AlwaysEncrypted/RegressionAlwaysEncryptedTest.java
index 87584eb7..82ec693e 100644
--- a/src/test/java/com/microsoft/sqlserver/jdbc/AlwaysEncrypted/RegressionAlwaysEncryptedTest.java
+++ b/src/test/java/com/microsoft/sqlserver/jdbc/AlwaysEncrypted/RegressionAlwaysEncryptedTest.java
@@ -29,6 +29,7 @@ import com.microsoft.sqlserver.testframework.PrepUtil;
 @Tag(Constants.xSQLv12)
 @Tag(Constants.xAzureSQLDW)
 @Tag(Constants.xAzureSQLDB)
+@Tag(Constants.reqExternalSetup)
 public class RegressionAlwaysEncryptedTest extends AESetup {
     static String numericTable[][] = {{"Bit", "bit"}, {"Tinyint", "tinyint"}, {"Smallint", "smallint"},};
 
diff --git a/src/test/java/com/microsoft/sqlserver/jdbc/KerberosTest.java b/src/test/java/com/microsoft/sqlserver/jdbc/KerberosTest.java
new file mode 100644
index 00000000..21283955
--- /dev/null
+++ b/src/test/java/com/microsoft/sqlserver/jdbc/KerberosTest.java
@@ -0,0 +1,120 @@
+package com.microsoft.sqlserver.jdbc;
+
+import com.microsoft.sqlserver.testframework.AbstractTest;
+import com.microsoft.sqlserver.testframework.Constants;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.Tag;
+import org.junit.jupiter.api.Test;
+import org.junit.platform.runner.JUnitPlatform;
+import org.junit.runner.RunWith;
+
+import javax.security.auth.login.AppConfigurationEntry;
+import javax.security.auth.login.Configuration;
+import java.sql.DriverManager;
+import java.sql.ResultSet;
+import java.util.HashMap;
+import java.util.Map;
+
+@Tag(Constants.kerberos)
+@RunWith(JUnitPlatform.class)
+public class KerberosTest extends AbstractTest {
+
+    private static String kerberosAuth = "KERBEROS";
+    private static String authSchemeQuery = "select auth_scheme from sys.dm_exec_connections where session_id=@@spid";
+
+    @BeforeAll
+    public static void setupTests() throws Exception {
+        setConnection();
+    }
+
+    @Test
+    public void testUseDefaultJaasConfigConnectionStringPropertyTrue() throws Exception {
+        String connectionStringUseDefaultJaasConfig = connectionStringKerberos + ";useDefaultJaasConfig=true;";
+
+        // Initial connection should succeed with default JAAS config
+        createKerberosConnection(connectionStringUseDefaultJaasConfig);
+
+        // Attempt to overwrite JAAS config. Since useDefaultJaasConfig=true, this should have no effect
+        // and subsequent connections should succeed.
+        overwriteJaasConfig();
+
+        // New connection should successfully connect and continue to use the default JAAS config.
+        createKerberosConnection(connectionStringUseDefaultJaasConfig);
+    }
+
+    @Test
+    public void testUseDefaultJaasConfigConnectionStringPropertyFalse() throws Exception {
+
+        // useDefaultJaasConfig=false by default
+        // Initial connection should succeed with default JAAS config
+        createKerberosConnection(connectionStringKerberos);
+
+        // Overwrite JAAS config. Since useDefaultJaasConfig=false, overwriting should succeed and have an effect.
+        // Subsequent connections will fail.
+        overwriteJaasConfig();
+
+        // New connection should fail as it is attempting to connect using an overwritten JAAS config.
+        try {
+            createKerberosConnection(connectionStringKerberos);
+            Assertions.fail(TestResource.getResource("R_expectedExceptionNotThrown"));
+        } catch (SQLServerException e) {
+            Assertions.assertTrue(e.getMessage()
+                    .contains(TestResource.getResource("R_noLoginModulesConfiguredForJdbcDriver")));
+        }
+    }
+
+    @Test
+    public void testUseDefaultNativeGSSCredential() throws Exception {
+        // This is a negative test. This test should fail as expected as the JVM arg "-Dsun.security.jgss.native=true"
+        // isn't provided.
+        String connectionString = connectionStringKerberos + ";useDefaultGSSCredential=true;";
+
+        try {
+            createKerberosConnection(connectionString);
+            Assertions.fail(TestResource.getResource("R_expectedExceptionNotThrown"));
+        } catch (SQLServerException e) {
+            Assertions.assertEquals(e.getCause().getMessage(), TestResource.getResource("R_kerberosNativeGSSFailure"));
+        }
+    }
+
+    @Test
+    public void testBasicKerberosAuth() throws Exception {
+        createKerberosConnection(connectionStringKerberos);
+    }
+
+    private static void createKerberosConnection(String connectionString) throws Exception {
+        try (SQLServerConnection conn = (SQLServerConnection) DriverManager.getConnection(connectionString)) {
+            ResultSet rs = conn.createStatement().executeQuery(authSchemeQuery);
+            rs.next();
+            Assertions.assertEquals(kerberosAuth, rs.getString(1));
+        }
+    }
+
+    /**
+     * Overwrites the default JAAS config. Call before making a connection.
+     */
+    private static void overwriteJaasConfig() {
+        AppConfigurationEntry kafkaClientConfigurationEntry = new AppConfigurationEntry(
+                "com.sun.security.auth.module.Krb5LoginModule",
+                AppConfigurationEntry.LoginModuleControlFlag.REQUIRED,
+                new HashMap<>());
+        Map<String, AppConfigurationEntry[]> configurationEntries = new HashMap<>();
+        configurationEntries.put("CLIENT_CONTEXT_NAME",
+                new AppConfigurationEntry[] { kafkaClientConfigurationEntry });
+        Configuration.setConfiguration(new InternalConfiguration(configurationEntries));
+    }
+
+    private static class InternalConfiguration extends Configuration {
+        private final Map<String, AppConfigurationEntry[]> configurationEntries;
+
+        InternalConfiguration(Map<String, AppConfigurationEntry[]> configurationEntries) {
+            this.configurationEntries = configurationEntries;
+        }
+
+        @Override
+        public AppConfigurationEntry[] getAppConfigurationEntry(String name) {
+            return this.configurationEntries.get(name);
+        }
+    }
+}
diff --git a/src/test/java/com/microsoft/sqlserver/jdbc/SQLServerConnectionTest.java b/src/test/java/com/microsoft/sqlserver/jdbc/SQLServerConnectionTest.java
index f8e9f4dc..0ed94e3a 100644
--- a/src/test/java/com/microsoft/sqlserver/jdbc/SQLServerConnectionTest.java
+++ b/src/test/java/com/microsoft/sqlserver/jdbc/SQLServerConnectionTest.java
@@ -138,6 +138,9 @@ public class SQLServerConnectionTest extends AbstractTest {
         ds.setJAASConfigurationName(stringPropValue);
         assertEquals(stringPropValue, ds.getJAASConfigurationName(), TestResource.getResource("R_valuesAreDifferent"));
 
+        ds.setUseDefaultJaasConfig(booleanPropValue);
+        assertEquals(booleanPropValue, ds.getUseDefaultJaasConfig(), TestResource.getResource("R_valuesAreDifferent"));
+
         ds.setMSIClientId(stringPropValue);
         assertEquals(stringPropValue, ds.getMSIClientId(), TestResource.getResource("R_valuesAreDifferent"));
 
@@ -198,6 +201,10 @@ public class SQLServerConnectionTest extends AbstractTest {
         assertEquals(Boolean.toString(booleanPropValue), ds.getEncrypt(),
                 TestResource.getResource("R_valuesAreDifferent"));
 
+        ds.setUseDefaultGSSCredential(booleanPropValue);
+        assertEquals(booleanPropValue, ds.getUseDefaultGSSCredential(),
+                TestResource.getResource("R_valuesAreDifferent"));
+
         ds.setServerCertificate(stringPropValue);
         assertEquals(stringPropValue, ds.getServerCertificate(), TestResource.getResource("R_valuesAreDifferent"));
 
@@ -442,6 +449,42 @@ public class SQLServerConnectionTest extends AbstractTest {
         }
     }
 
+    /**
+     * Tests whether connectRetryCount and connectRetryInterval are properly respected in the login loop. As well, tests
+     * that connection is retried the proper number of times.
+     */
+    @Test
+    public void testConnectCountInLoginAndCorrectRetryCount() {
+        long timerStart = 0;
+
+        int connectRetryCount = 3;
+        int connectRetryInterval = 1;
+        int longLoginTimeout = loginTimeOutInSeconds * 4; // 120 seconds
+
+        try {
+            SQLServerDataSource ds = new SQLServerDataSource();
+            ds.setURL(connectionString);
+            ds.setLoginTimeout(longLoginTimeout);
+            ds.setConnectRetryCount(connectRetryCount);
+            ds.setConnectRetryInterval(connectRetryInterval);
+            ds.setDatabaseName(RandomUtil.getIdentifier("DataBase"));
+            timerStart = System.currentTimeMillis();
+
+            try (Connection con = ds.getConnection()) {
+                assertTrue(con == null, TestResource.getResource("R_shouldNotConnect"));
+            }
+        } catch (Exception e) {
+            assertTrue(e.getMessage().contains(TestResource.getResource("R_cannotOpenDatabase")), e.getMessage());
+            long totalTime = System.currentTimeMillis() - timerStart;
+            int expectedMinimumTimeInMillis = (connectRetryCount * connectRetryInterval) * 1000; // 3 seconds
+
+            // Minimum time is 0 seconds per attempt and connectRetryInterval * connectRetryCount seconds of interval.
+            // Maximum is unknown, but is needs to be less than longLoginTimeout or else this is an issue.
+            assertTrue(totalTime > expectedMinimumTimeInMillis, TestResource.getResource("R_executionNotLong"));
+            assertTrue(totalTime < (longLoginTimeout * 1000L), TestResource.getResource("R_executionTooLong"));
+        }
+    }
+
     @Test
     @Tag(Constants.xAzureSQLDW)
     @Tag(Constants.xAzureSQLDB)
diff --git a/src/test/java/com/microsoft/sqlserver/jdbc/TestResource.java b/src/test/java/com/microsoft/sqlserver/jdbc/TestResource.java
index 1df53941..9a4c4177 100644
--- a/src/test/java/com/microsoft/sqlserver/jdbc/TestResource.java
+++ b/src/test/java/com/microsoft/sqlserver/jdbc/TestResource.java
@@ -43,6 +43,7 @@ public final class TestResource extends ListResourceBundle {
             {"R_lengthTruncated", " The inserted length is truncated or not correct!"},
             {"R_timeValueTruncated", " The time value is truncated or not correct!"},
             {"R_invalidErrorMessage", "Invalid Error Message: "},
+            {"R_kerberosNativeGSSFailure", "No valid credentials provided (Mechanism level: Failed to find any Kerberos tgt)"},
             {"R_expectedFailPassed", "Expected failure did not fail"}, {"R_dataTypeNotFound", "Cannot find data type"},
             {"R_illegalCharWktPosition", "Illegal character in Well-Known text at position {0}."},
             {"R_illegalCharWkt", "Illegal Well-Known text. Please make sure Well-Known text is valid."},
@@ -203,5 +204,6 @@ public final class TestResource extends ListResourceBundle {
             {"R_connectTimedOut", "connect timed out"},
             {"R_sessionKilled", "Cannot continue the execution because the session is in the kill state"},
             {"R_failedFedauth", "Failed to acquire fedauth token: "},
+            {"R_noLoginModulesConfiguredForJdbcDriver", "javax.security.auth.login.LoginException (No LoginModules configured for SQLJDBCDriver)"},
             {"R_unexpectedThreadCount", "Thread count is higher than expected."}};
 }
diff --git a/src/test/java/com/microsoft/sqlserver/jdbc/bulkCopy/BulkCopyAllTypesTest.java b/src/test/java/com/microsoft/sqlserver/jdbc/bulkCopy/BulkCopyAllTypesTest.java
index 11d88d61..a7e69b01 100644
--- a/src/test/java/com/microsoft/sqlserver/jdbc/bulkCopy/BulkCopyAllTypesTest.java
+++ b/src/test/java/com/microsoft/sqlserver/jdbc/bulkCopy/BulkCopyAllTypesTest.java
@@ -8,6 +8,8 @@ import java.sql.Connection;
 import java.sql.ResultSet;
 import java.sql.SQLException;
 import java.sql.Statement;
+import java.sql.Timestamp;
+import java.sql.Types;
 
 import org.junit.jupiter.api.BeforeAll;
 import org.junit.jupiter.api.Tag;
@@ -16,8 +18,12 @@ import org.junit.platform.runner.JUnitPlatform;
 import org.junit.runner.RunWith;
 
 import com.microsoft.sqlserver.jdbc.ComparisonUtil;
+import com.microsoft.sqlserver.jdbc.RandomData;
+import com.microsoft.sqlserver.jdbc.RandomUtil;
 import com.microsoft.sqlserver.jdbc.SQLServerBulkCopy;
+import com.microsoft.sqlserver.jdbc.SQLServerBulkCopyOptions;
 import com.microsoft.sqlserver.jdbc.TestUtils;
+import com.microsoft.sqlserver.testframework.AbstractSQLGenerator;
 import com.microsoft.sqlserver.testframework.AbstractTest;
 import com.microsoft.sqlserver.testframework.Constants;
 import com.microsoft.sqlserver.testframework.DBConnection;
@@ -25,6 +31,20 @@ import com.microsoft.sqlserver.testframework.DBStatement;
 import com.microsoft.sqlserver.testframework.DBTable;
 import com.microsoft.sqlserver.testframework.PrepUtil;
 
+import javax.sql.RowSetMetaData;
+import javax.sql.rowset.CachedRowSet;
+import javax.sql.rowset.RowSetFactory;
+import javax.sql.rowset.RowSetMetaDataImpl;
+import javax.sql.rowset.RowSetProvider;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.stream.Collectors;
+import java.util.stream.IntStream;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
 
 @RunWith(JUnitPlatform.class)
 public class BulkCopyAllTypesTest extends AbstractTest {
@@ -98,6 +118,71 @@ public class BulkCopyAllTypesTest extends AbstractTest {
         try (Statement stmt = connection.createStatement()) {
             TestUtils.dropTableIfExists(tableSrc.getEscapedTableName(), stmt);
             TestUtils.dropTableIfExists(tableDest.getEscapedTableName(), stmt);
+            TestUtils.dropTableIfExists(dateTimeTestTable, stmt);
+        }
+    }
+
+    private static final int DATETIME_COL_COUNT = 2;
+    private static final int DATETIME_ROW_COUNT = 1;
+    private static final String dateTimeTestTable =
+            AbstractSQLGenerator.escapeIdentifier(RandomUtil.getIdentifier("bulkCopyTimestampTest"));
+
+    @Test
+    public void testBulkCopyTimestamp() throws SQLException {
+        List<Timestamp> timeStamps = new ArrayList<>();
+        try (Connection con = getConnection(); Statement stmt = connection.createStatement()) {
+            String colSpec = IntStream.range(1, DATETIME_COL_COUNT + 1).mapToObj(x -> String.format("c%d datetime", x)).collect(
+                    Collectors.joining(","));
+            String sql1 = String.format("create table %s (%s)", dateTimeTestTable, colSpec);
+            stmt.execute(sql1);
+
+            RowSetFactory rsf = RowSetProvider.newFactory();
+            CachedRowSet crs = rsf.createCachedRowSet();
+            RowSetMetaData rsmd = new RowSetMetaDataImpl();
+            rsmd.setColumnCount(DATETIME_COL_COUNT);
+
+            for (int i = 1; i <= DATETIME_COL_COUNT; i++) {
+                rsmd.setColumnName(i, String.format("c%d", i));
+                rsmd.setColumnType(i, Types.TIMESTAMP);
+            }
+            crs.setMetaData(rsmd);
+
+            for (int i = 0; i < DATETIME_COL_COUNT; i++) {
+                timeStamps.add(RandomData.generateDatetime(false));
+            }
+
+            for (int ri = 0; ri < DATETIME_ROW_COUNT; ri++) {
+                crs.moveToInsertRow();
+
+                for (int i = 1; i <= DATETIME_COL_COUNT; i++) {
+                        crs.updateTimestamp(i, timeStamps.get(i - 1));
+                }
+                crs.insertRow();
+            }
+            crs.moveToCurrentRow();
+
+            try (SQLServerBulkCopy bcOperation = new SQLServerBulkCopy(con)) {
+                SQLServerBulkCopyOptions bcOptions = new SQLServerBulkCopyOptions();
+                bcOptions.setBatchSize(5000);
+                bcOperation.setDestinationTableName(dateTimeTestTable);
+                bcOperation.setBulkCopyOptions(bcOptions);
+                bcOperation.writeToServer(crs);
+            }
+
+            try (ResultSet rs = stmt.executeQuery("select * from " + dateTimeTestTable)) {
+                assertTrue(rs.next());
+
+                for (int i = 1; i <= DATETIME_COL_COUNT; i++) {
+                    long expectedTimestamp = getTime(timeStamps.get(i - 1));
+                    long actualTimestamp = getTime(rs.getTimestamp(i));
+
+                    assertEquals(expectedTimestamp, actualTimestamp);
+                }
+            }
         }
     }
+
+    private static long getTime(Timestamp time) {
+        return (3 * time.getTime() + 5) / 10;
+    }
 }
diff --git a/src/test/java/com/microsoft/sqlserver/jdbc/connection/RequestBoundaryMethodsTest.java b/src/test/java/com/microsoft/sqlserver/jdbc/connection/RequestBoundaryMethodsTest.java
index d0ce2556..da2d4d73 100644
--- a/src/test/java/com/microsoft/sqlserver/jdbc/connection/RequestBoundaryMethodsTest.java
+++ b/src/test/java/com/microsoft/sqlserver/jdbc/connection/RequestBoundaryMethodsTest.java
@@ -74,6 +74,7 @@ public class RequestBoundaryMethodsTest extends AbstractTest {
         boolean useBulkCopyForBatchInsert1 = true;
         boolean useFmtOnly1 = true;
         boolean delayLoadingLobs1 = false;
+        boolean ignoreOffsetOnDateTimeOffsetConversion1 = true;
 
         boolean autoCommitMode2 = false;
         int transactionIsolationLevel2 = SQLServerConnection.TRANSACTION_SERIALIZABLE;
@@ -88,6 +89,7 @@ public class RequestBoundaryMethodsTest extends AbstractTest {
         boolean useBulkCopyForBatchInsert2 = false;
         boolean useFmtOnly2 = false;
         boolean delayLoadingLobs2 = true;
+        boolean ignoreOffsetOnDateTimeOffsetConversion2 = false;
 
         try (SQLServerConnection con = getConnection(); Statement stmt = con.createStatement()) {
             if (TestUtils.isJDBC43OrGreater(con)) {
@@ -98,53 +100,53 @@ public class RequestBoundaryMethodsTest extends AbstractTest {
                 setConnectionFields(con, autoCommitMode1, transactionIsolationLevel1, networkTimeout1, holdability1,
                         sendTimeAsDatetime1, statementPoolingCacheSize1, disableStatementPooling1,
                         serverPreparedStatementDiscardThreshold1, enablePrepareOnFirstPreparedStatementCall1, sCatalog1,
-                        useBulkCopyForBatchInsert1, useFmtOnly1, delayLoadingLobs1);
+                        useBulkCopyForBatchInsert1, useFmtOnly1, delayLoadingLobs1, ignoreOffsetOnDateTimeOffsetConversion1);
                 con.beginRequest();
                 // Call setters with the second set of values inside beginRequest()/endRequest() block.
                 setConnectionFields(con, autoCommitMode2, transactionIsolationLevel2, networkTimeout2, holdability2,
                         sendTimeAsDatetime2, statementPoolingCacheSize2, disableStatementPooling2,
                         serverPreparedStatementDiscardThreshold2, enablePrepareOnFirstPreparedStatementCall2, sCatalog2,
-                        useBulkCopyForBatchInsert2, useFmtOnly2, delayLoadingLobs2);
+                        useBulkCopyForBatchInsert2, useFmtOnly2, delayLoadingLobs2, ignoreOffsetOnDateTimeOffsetConversion2);
                 con.endRequest();
                 // Test if endRequest() resets the SQLServerConnection properties back to the first set of values.
                 compareValuesAgainstConnection(con, autoCommitMode1, transactionIsolationLevel1, networkTimeout1,
                         holdability1, sendTimeAsDatetime1, statementPoolingCacheSize1, disableStatementPooling1,
                         serverPreparedStatementDiscardThreshold1, enablePrepareOnFirstPreparedStatementCall1, sCatalog1,
-                        useBulkCopyForBatchInsert1, useFmtOnly1, delayLoadingLobs1);
+                        useBulkCopyForBatchInsert1, useFmtOnly1, delayLoadingLobs1, ignoreOffsetOnDateTimeOffsetConversion1);
 
                 // Multiple calls to beginRequest() without an intervening call to endRequest() are no-op.
                 setConnectionFields(con, autoCommitMode2, transactionIsolationLevel2, networkTimeout2, holdability2,
                         sendTimeAsDatetime2, statementPoolingCacheSize2, disableStatementPooling2,
                         serverPreparedStatementDiscardThreshold2, enablePrepareOnFirstPreparedStatementCall2, sCatalog2,
-                        useBulkCopyForBatchInsert2, useFmtOnly2, delayLoadingLobs2);
+                        useBulkCopyForBatchInsert2, useFmtOnly2, delayLoadingLobs2, ignoreOffsetOnDateTimeOffsetConversion2);
                 con.beginRequest();
                 setConnectionFields(con, autoCommitMode1, transactionIsolationLevel1, networkTimeout1, holdability1,
                         sendTimeAsDatetime1, statementPoolingCacheSize1, disableStatementPooling1,
                         serverPreparedStatementDiscardThreshold1, enablePrepareOnFirstPreparedStatementCall1, sCatalog1,
-                        useBulkCopyForBatchInsert1, useFmtOnly1, delayLoadingLobs1);
+                        useBulkCopyForBatchInsert1, useFmtOnly1, delayLoadingLobs1, ignoreOffsetOnDateTimeOffsetConversion1);
                 con.beginRequest();
                 con.endRequest();
                 // Same values as before the first beginRequest()
                 compareValuesAgainstConnection(con, autoCommitMode2, transactionIsolationLevel2, networkTimeout2,
                         holdability2, sendTimeAsDatetime2, statementPoolingCacheSize2, disableStatementPooling2,
                         serverPreparedStatementDiscardThreshold2, enablePrepareOnFirstPreparedStatementCall2, sCatalog2,
-                        useBulkCopyForBatchInsert2, useFmtOnly2, delayLoadingLobs2);
+                        useBulkCopyForBatchInsert2, useFmtOnly2, delayLoadingLobs2, ignoreOffsetOnDateTimeOffsetConversion2);
 
                 // A call to endRequest() without an intervening call to beginRequest() is no-op.
                 setConnectionFields(con, autoCommitMode1, transactionIsolationLevel1, networkTimeout1, holdability1,
                         sendTimeAsDatetime1, statementPoolingCacheSize1, disableStatementPooling1,
                         serverPreparedStatementDiscardThreshold1, enablePrepareOnFirstPreparedStatementCall1, sCatalog1,
-                        useBulkCopyForBatchInsert1, useFmtOnly1, delayLoadingLobs1);
+                        useBulkCopyForBatchInsert1, useFmtOnly1, delayLoadingLobs1, ignoreOffsetOnDateTimeOffsetConversion1);
                 setConnectionFields(con, autoCommitMode2, transactionIsolationLevel2, networkTimeout2, holdability2,
                         sendTimeAsDatetime2, statementPoolingCacheSize2, disableStatementPooling2,
                         serverPreparedStatementDiscardThreshold2, enablePrepareOnFirstPreparedStatementCall2, sCatalog2,
-                        useBulkCopyForBatchInsert2, useFmtOnly2, delayLoadingLobs2);
+                        useBulkCopyForBatchInsert2, useFmtOnly2, delayLoadingLobs2, ignoreOffsetOnDateTimeOffsetConversion2);
                 con.endRequest();
                 // No change.
                 compareValuesAgainstConnection(con, autoCommitMode2, transactionIsolationLevel2, networkTimeout2,
                         holdability2, sendTimeAsDatetime2, statementPoolingCacheSize2, disableStatementPooling2,
                         serverPreparedStatementDiscardThreshold2, enablePrepareOnFirstPreparedStatementCall2, sCatalog2,
-                        useBulkCopyForBatchInsert2, useFmtOnly2, delayLoadingLobs2);
+                        useBulkCopyForBatchInsert2, useFmtOnly2, delayLoadingLobs2, ignoreOffsetOnDateTimeOffsetConversion2);
             }
         } finally {
             TestUtils.dropDatabaseIfExists(sCatalog2, connectionString);
@@ -397,7 +399,7 @@ public class RequestBoundaryMethodsTest extends AbstractTest {
             int networkTimeout, int holdability, boolean sendTimeAsDatetime, int statementPoolingCacheSize,
             boolean disableStatementPooling, int serverPreparedStatementDiscardThreshold,
             boolean enablePrepareOnFirstPreparedStatementCall, String sCatalog, boolean useBulkCopyForBatchInsert,
-            boolean useFmtOnly, boolean delayLoadingLobs) throws SQLException {
+            boolean useFmtOnly, boolean delayLoadingLobs, boolean ignoreOffsetOnDateTimeOffsetConversion) throws SQLException {
         con.setAutoCommit(autoCommitMode);
         con.setTransactionIsolation(transactionIsolationLevel);
         con.setNetworkTimeout(null, networkTimeout);
@@ -411,13 +413,14 @@ public class RequestBoundaryMethodsTest extends AbstractTest {
         con.setUseBulkCopyForBatchInsert(useBulkCopyForBatchInsert);
         con.setUseFmtOnly(useFmtOnly);
         con.setDelayLoadingLobs(delayLoadingLobs);
+        con.setIgnoreOffsetOnDateTimeOffsetConversion(ignoreOffsetOnDateTimeOffsetConversion);
     }
 
     private void compareValuesAgainstConnection(SQLServerConnection con, boolean autoCommitMode,
             int transactionIsolationLevel, int networkTimeout, int holdability, boolean sendTimeAsDatetime,
             int statementPoolingCacheSize, boolean disableStatementPooling, int serverPreparedStatementDiscardThreshold,
             boolean enablePrepareOnFirstPreparedStatementCall, String sCatalog, boolean useBulkCopyForBatchInsert,
-            boolean useFmtOnly, boolean delayLoadingLobs) throws SQLException {
+            boolean useFmtOnly, boolean delayLoadingLobs, boolean ignoreOffsetOnDateTimeOffsetConversion) throws SQLException {
         final String description = " values do not match.";
         assertEquals(autoCommitMode, con.getAutoCommit(), "autoCommitmode" + description);
         assertEquals(transactionIsolationLevel, con.getTransactionIsolation(),
@@ -438,6 +441,7 @@ public class RequestBoundaryMethodsTest extends AbstractTest {
                 "useBulkCopyForBatchInsert" + description);
         assertEquals(useFmtOnly, con.getUseFmtOnly(), "useFmtOnly" + description);
         assertEquals(delayLoadingLobs, con.getDelayLoadingLobs(), "delayLoadingLobs" + description);
+        assertEquals(ignoreOffsetOnDateTimeOffsetConversion, con.getIgnoreOffsetOnDateTimeOffsetConversion(), "ignoreOffsetOnDateTimeOffsetConversion" + description);
     }
 
     private void generateWarning(Connection con) throws SQLException {
@@ -495,6 +499,7 @@ public class RequestBoundaryMethodsTest extends AbstractTest {
         verifiedMethodNames.add("createArrayOf");
         verifiedMethodNames.add("setUseFmtOnly");
         verifiedMethodNames.add("setDelayLoadingLobs");
+        verifiedMethodNames.add("setIgnoreOffsetOnDateTimeOffsetConversion");
         verifiedMethodNames.add("registerColumnEncryptionKeyStoreProvidersOnConnection");
         verifiedMethodNames.add("getPrepareMethod");
         verifiedMethodNames.add("setPrepareMethod");
diff --git a/src/test/java/com/microsoft/sqlserver/jdbc/databasemetadata/DatabaseMetaDataTest.java b/src/test/java/com/microsoft/sqlserver/jdbc/databasemetadata/DatabaseMetaDataTest.java
index 1a47af0b..d4bab399 100644
--- a/src/test/java/com/microsoft/sqlserver/jdbc/databasemetadata/DatabaseMetaDataTest.java
+++ b/src/test/java/com/microsoft/sqlserver/jdbc/databasemetadata/DatabaseMetaDataTest.java
@@ -18,6 +18,7 @@ import java.io.File;
 import java.io.FileInputStream;
 import java.io.IOException;
 import java.io.InputStream;
+import java.sql.CallableStatement;
 import java.sql.Connection;
 import java.sql.DatabaseMetaData;
 import java.sql.ResultSet;
@@ -43,6 +44,7 @@ import org.junit.runner.RunWith;
 import com.microsoft.sqlserver.jdbc.RandomUtil;
 import com.microsoft.sqlserver.jdbc.SQLServerDataSource;
 import com.microsoft.sqlserver.jdbc.SQLServerDatabaseMetaData;
+import com.microsoft.sqlserver.jdbc.SQLServerException;
 import com.microsoft.sqlserver.jdbc.StringUtils;
 import com.microsoft.sqlserver.jdbc.TestResource;
 import com.microsoft.sqlserver.jdbc.TestUtils;
@@ -155,8 +157,6 @@ public class DatabaseMetaDataTest extends AbstractTest {
      * @throws SQLException
      */
     @Test
-    @Tag(Constants.xAzureSQLDW)
-    @Tag(Constants.xAzureSQLDB)
     public void testDBUserLogin() throws SQLException {
         try (Connection conn = getConnection()) {
             DatabaseMetaData databaseMetaData = conn.getMetaData();
@@ -187,6 +187,45 @@ public class DatabaseMetaDataTest extends AbstractTest {
         }
     }
 
+    @Test
+    @Tag(Constants.xAzureSQLDW)
+    public void testImpersonateGetUserName() throws SQLException {
+        String newUser = "newUser" + UUID.randomUUID();
+
+        try (Connection conn = getConnection(); Statement stmt = conn.createStatement()) {
+            String escapedNewUser = AbstractSQLGenerator.escapeIdentifier(newUser);
+            String password = "password" + UUID.randomUUID();
+
+            stmt.execute("IF EXISTS (select * from sys.sysusers where name = '" + escapedNewUser + "') DROP USER"
+                    + escapedNewUser);
+
+            // create new user and login
+            try {
+                stmt.execute("CREATE USER " + escapedNewUser + " WITH password='" + password + "'");
+            } catch (SQLServerException e) {
+                // handle failed cases when database is master
+                if (e.getMessage().contains("contained database")) {
+                    stmt.execute("CREATE LOGIN " + escapedNewUser + " WITH password='" + password + "'");
+                    stmt.execute("CREATE USER " + escapedNewUser);
+                }
+            }
+
+            DatabaseMetaData databaseMetaData = conn.getMetaData();
+            try (CallableStatement asOtherUser = conn.prepareCall("EXECUTE AS USER = '" + newUser + "'")) {
+                asOtherUser.execute();
+                assertTrue(newUser.equalsIgnoreCase(databaseMetaData.getUserName()),
+                        TestResource.getResource("R_userNameNotMatch"));
+            } catch (Exception e) {
+                fail(TestResource.getResource("R_unexpectedErrorMessage") + e.getMessage());
+            } finally {
+                stmt.execute("IF EXISTS (select * from sys.sysusers where name = '" + escapedNewUser + "') DROP USER"
+                        + escapedNewUser);
+                stmt.execute("IF EXISTS (select * from sys.sysusers where name = '" + escapedNewUser + "') DROP LOGIN"
+                        + escapedNewUser);
+            }
+        }
+    }
+
     /**
      * Testing of {@link SQLServerDatabaseMetaData#getSchemas()}
      * 
@@ -306,6 +345,57 @@ public class DatabaseMetaDataTest extends AbstractTest {
         }
     }
 
+    /**
+     * Tests that the schemaPattern parameter containing _ and % are escaped by
+     * {@link SQLServerDatabaseMetaData#getSchemas(String catalog, String schemaPattern)}.
+     *
+     * @throws SQLException
+     */
+    @Test
+    @Tag(Constants.xAzureSQLDW)
+    @Tag(Constants.xAzureSQLDB)
+    public void testDBSchemasForSchemaPatternWithWildcards() throws SQLException {
+        UUID id = UUID.randomUUID();
+        String testCatalog = "catalog" + id;
+        String[] schemas = {"some_schema", "some%schema", "some[schema"};
+        String[] schemaPatterns = {"some\\_schema", "some\\%schema", "some\\[schema"};
+
+        try (Connection conn = getConnection(); Statement stmt = conn.createStatement()) {
+            TestUtils.dropDatabaseIfExists(testCatalog, connectionString);
+            stmt.execute(String.format("CREATE DATABASE [%s]", testCatalog));
+            stmt.execute(String.format("USE [%s]", testCatalog));
+
+            for (int i = 0; i < schemas.length; ++i) {
+                stmt.execute(String.format("CREATE SCHEMA [%s]", schemas[i]));
+
+                try (ResultSet rs = conn.getMetaData().getSchemas(testCatalog, schemaPatterns[i])) {
+
+                    MessageFormat schemaEmptyFormat = new MessageFormat(TestResource.getResource("R_nameEmpty"));
+                    Object[] schemaMsgArgs = {schemas[i]};
+                    Object[] catalogMsgArgs = {testCatalog};
+
+                    boolean hasResults = false;
+                    while (rs.next()) {
+                        hasResults = true;
+                        String schemaName = rs.getString(1);
+                        String catalogName = rs.getString(2);
+                        assertTrue(!StringUtils.isEmpty(schemaName), schemaEmptyFormat.format(schemaMsgArgs));
+                        assertTrue(!StringUtils.isEmpty(catalogName), schemaEmptyFormat.format(catalogMsgArgs));
+                        assertEquals(schemaName, schemaMsgArgs[0]);
+                        assertEquals(catalogName, catalogMsgArgs[0]);
+                    }
+
+                    MessageFormat atLeastOneFoundFormat = new MessageFormat(TestResource.getResource("R_atLeastOneFound"));
+                    assertTrue(hasResults, atLeastOneFoundFormat.format(schemaMsgArgs));
+                }
+            }
+        } catch (Exception e) {
+            fail(TestResource.getResource("R_unexpectedErrorMessage") + e.getMessage());
+        } finally {
+            TestUtils.dropDatabaseIfExists(testCatalog, connectionString);
+        }
+    }
+
     /**
      * Get All Tables.
      * 
diff --git a/src/test/java/com/microsoft/sqlserver/jdbc/datatypes/DataTypesTest.java b/src/test/java/com/microsoft/sqlserver/jdbc/datatypes/DataTypesTest.java
index 4ddff40d..de8e160a 100644
--- a/src/test/java/com/microsoft/sqlserver/jdbc/datatypes/DataTypesTest.java
+++ b/src/test/java/com/microsoft/sqlserver/jdbc/datatypes/DataTypesTest.java
@@ -22,7 +22,9 @@ import java.time.Instant;
 import java.time.LocalDate;
 import java.time.LocalDateTime;
 import java.time.LocalTime;
+import java.time.OffsetDateTime;
 import java.time.ZoneId;
+import java.time.ZoneOffset;
 import java.time.temporal.ChronoUnit;
 import java.time.zone.ZoneOffsetTransition;
 import java.time.zone.ZoneRules;
@@ -1624,6 +1626,23 @@ public class DataTypesTest extends AbstractTest {
                         fail(TestResource.getResource("R_expectedExceptionNotThrown"));
                     }
 
+
+                    exceptionThrown = true;
+                    // Verify SQLState 22005 is in exception for conversion errors
+                    try {
+                        Timestamp timestamp = Timestamp.valueOf("9999-12-31 23:59:59.998");
+                        rs.updateTimestamp(1, timestamp);
+                        rs.updateRow();
+                        rs.getLong(1);
+                        exceptionThrown = false;
+                    } catch (SQLServerException e) {
+                        assertEquals("22005", e.getSQLState());
+                    }
+
+                    if (!exceptionThrown) {
+                        fail(TestResource.getResource("R_expectedExceptionNotThrown"));
+                    }
+
                     // Update time(5) from Timestamp with nanos more precise than 100ns
                     Timestamp ts = Timestamp.valueOf("2010-01-12 11:05:23");
                     ts.setNanos(987659999);
@@ -1855,6 +1874,47 @@ public class DataTypesTest extends AbstractTest {
         }
     }
 
+    @Test
+    public void testGetLocalDateTimeTypes() throws Exception {
+        // test value needs to be in a time zone other than local
+        OffsetDateTime value = OffsetDateTime.now().truncatedTo(ChronoUnit.MILLIS); // Linux has more precision than SQL Server
+        int offsetSeconds = value.getOffset().getTotalSeconds();
+        offsetSeconds += offsetSeconds < 0 ? 3600 : -3600;
+        value = value.withOffsetSameLocal(ZoneOffset.ofTotalSeconds(offsetSeconds));
+        LocalDateTime valueWithOffsetConversion = value.atZoneSameInstant(ZoneId.systemDefault()).toLocalDateTime();
+
+        try (SQLServerConnection conn = PrepUtil.getConnection(connectionString)) {
+            try (PreparedStatement stmt = conn.prepareStatement("SELECT ?")) {
+                stmt.setObject(1, value);
+
+                ResultSet rs = stmt.executeQuery();
+                rs.next();
+
+                // default behavior is to apply the time zone offset when converting DATETIMEOFFSET to local java.time types
+                assertEquals(value, rs.getObject(1, OffsetDateTime.class));
+                assertEquals(valueWithOffsetConversion, rs.getObject(1, LocalDateTime.class));
+                assertEquals(valueWithOffsetConversion.toLocalDate(), rs.getObject(1, LocalDate.class));
+                assertEquals(valueWithOffsetConversion.toLocalTime(), rs.getObject(1, LocalTime.class));
+            }
+            
+            // change the behavior to be compatible with java.time conversion methods
+            conn.setIgnoreOffsetOnDateTimeOffsetConversion(true);
+            
+            try (PreparedStatement stmt = conn.prepareStatement("SELECT ?")) {
+                stmt.setObject(1, value);
+
+                ResultSet rs = stmt.executeQuery();
+                rs.next();
+
+                // now the offset should be ignored instead of converting to local time zone
+                assertEquals(value, rs.getObject(1, OffsetDateTime.class));
+                assertEquals(value.toLocalDateTime(), rs.getObject(1, LocalDateTime.class));
+                assertEquals(value.toLocalDate(), rs.getObject(1, LocalDate.class));
+                assertEquals(value.toLocalTime(), rs.getObject(1, LocalTime.class));
+            }
+        }
+    }
+
     /**
      * Test example from https://github.com/microsoft/mssql-jdbc/issues/1143
      * 
diff --git a/src/test/java/com/microsoft/sqlserver/jdbc/fips/FipsTest.java b/src/test/java/com/microsoft/sqlserver/jdbc/fips/FipsTest.java
index 5a032c59..10853f2f 100644
--- a/src/test/java/com/microsoft/sqlserver/jdbc/fips/FipsTest.java
+++ b/src/test/java/com/microsoft/sqlserver/jdbc/fips/FipsTest.java
@@ -86,7 +86,7 @@ public class FipsTest extends AbstractTest {
         props.remove(Constants.ENCRYPT);
         props.remove(Constants.TRUST_SERVER_CERTIFICATE);
 
-        try (Connection con = PrepUtil.getConnection(connectionString, props)) {
+        try (Connection con = PrepUtil.getConnection(connectionString + ";encrypt=false", props)) {
             Assertions.assertTrue(!StringUtils.isEmpty(con.getSchema()));
         } catch (Exception e) {
             fail(TestResource.getResource("R_unexpectedErrorMessage") + e.getMessage());
diff --git a/src/test/java/com/microsoft/sqlserver/jdbc/parametermetadata/ParameterMetaDataTest.java b/src/test/java/com/microsoft/sqlserver/jdbc/parametermetadata/ParameterMetaDataTest.java
index 61b9936a..736a431c 100644
--- a/src/test/java/com/microsoft/sqlserver/jdbc/parametermetadata/ParameterMetaDataTest.java
+++ b/src/test/java/com/microsoft/sqlserver/jdbc/parametermetadata/ParameterMetaDataTest.java
@@ -4,6 +4,7 @@
  */
 package com.microsoft.sqlserver.jdbc.parametermetadata;
 
+import static org.junit.Assert.assertThrows;
 import static org.junit.jupiter.api.Assertions.assertSame;
 import static org.junit.jupiter.api.Assertions.assertTrue;
 
@@ -127,6 +128,10 @@ public class ParameterMetaDataTest extends AbstractTest {
                     assert (metadata.getPrecision(2) == 38);
                     assert (metadata.getScale(2) == 5);
                     assert (!metadata.isSigned(1));
+
+                    // test invalid index
+                    assertThrows(SQLException.class, () -> metadata.getParameterType(0));
+                    assertThrows(SQLException.class, () -> metadata.getParameterType(3));
                 }
             } finally {
                 TestUtils.dropTableIfExists(AbstractSQLGenerator.escapeIdentifier(tableName), stmt);
@@ -160,6 +165,10 @@ public class ParameterMetaDataTest extends AbstractTest {
                 assert (metadata.getScale(1) == 0);
                 assert (metadata.isNullable(1) == ParameterMetaData.parameterNullable);
                 assert (!metadata.isSigned(1));
+
+                // test invalid index
+                assertThrows(SQLException.class, () -> metadata.getParameterType(0));
+                assertThrows(SQLException.class, () -> metadata.getParameterType(3));
             }
         }
     }
diff --git a/src/test/java/com/microsoft/sqlserver/jdbc/preparedStatement/BatchExecutionWithBulkCopyTest.java b/src/test/java/com/microsoft/sqlserver/jdbc/preparedStatement/BatchExecutionWithBulkCopyTest.java
index dbccf4e7..353648dc 100644
--- a/src/test/java/com/microsoft/sqlserver/jdbc/preparedStatement/BatchExecutionWithBulkCopyTest.java
+++ b/src/test/java/com/microsoft/sqlserver/jdbc/preparedStatement/BatchExecutionWithBulkCopyTest.java
@@ -59,6 +59,7 @@ public class BatchExecutionWithBulkCopyTest extends AbstractTest {
     static String squareBracketTableName = RandomUtil.getIdentifier("BulkCopy]]]]test'");
     static String doubleQuoteTableName = RandomUtil.getIdentifier("\"BulkCopy\"\"\"\"test\"");
     static String schemaTableName = "\"dbo\"         . /*some comment */     " + squareBracketTableName;
+    static String tableNameBulkComputedCols = RandomUtil.getIdentifier("BulkCopyComputedCols");
 
     private Object[] generateExpectedValues() {
         float randomFloat = RandomData.generateReal(false);
@@ -760,6 +761,46 @@ public class BatchExecutionWithBulkCopyTest extends AbstractTest {
         }
     }
 
+    @Test
+    @Tag(Constants.xAzureSQLDW)
+    @Tag(Constants.xSQLv11)
+    @Tag(Constants.xSQLv12)
+    public void testComputedCols() throws Exception {
+        String valid = "insert into " + AbstractSQLGenerator.escapeIdentifier(tableNameBulkComputedCols) + " (id, json)"
+                + " values (?, ?)";
+
+        try (Connection connection = PrepUtil.getConnection(connectionString + ";useBulkCopyForBatchInsert=true;");
+             SQLServerPreparedStatement pstmt = (SQLServerPreparedStatement) connection.prepareStatement(valid);
+             Statement stmt = (SQLServerStatement) connection.createStatement();) {
+            Field f1 = SQLServerConnection.class.getDeclaredField("isAzureDW");
+            f1.setAccessible(true);
+            f1.set(connection, true);
+
+            TestUtils.dropTableIfExists(AbstractSQLGenerator.escapeIdentifier(tableNameBulkComputedCols), stmt);
+            String createTable = "create table " + AbstractSQLGenerator.escapeIdentifier(tableNameBulkComputedCols)
+                    + " (id nvarchar(100) not null, json nvarchar(max) not null,"
+                    + " vcol1 as json_value([json], '$.vcol1'), vcol2 as json_value([json], '$.vcol2'))";
+            stmt.execute(createTable);
+
+            String jsonValue =
+                    "{\"vcol1\":\"" + UUID.randomUUID().toString() + "\",\"vcol2\":\"" + UUID.randomUUID().toString()
+                            + "\" }";
+            String idValue = UUID.randomUUID().toString();
+            pstmt.setString(1, idValue);
+            pstmt.setString(2, jsonValue);
+            pstmt.addBatch();
+            pstmt.executeBatch();
+
+            try (ResultSet rs = stmt.executeQuery(
+                    "select * from " + AbstractSQLGenerator.escapeIdentifier(tableNameBulkComputedCols))) {
+                rs.next();
+
+                assertEquals(idValue, rs.getObject(1));
+                assertEquals(jsonValue, rs.getObject(2));
+            }
+        }
+    }
+
     @BeforeAll
     public static void setupTests() throws Exception {
         setConnection();
diff --git a/src/test/java/com/microsoft/sqlserver/jdbc/resiliency/ResultSetsWithResiliencyTest.java b/src/test/java/com/microsoft/sqlserver/jdbc/resiliency/ResultSetsWithResiliencyTest.java
index 2a381976..dfc1048b 100644
--- a/src/test/java/com/microsoft/sqlserver/jdbc/resiliency/ResultSetsWithResiliencyTest.java
+++ b/src/test/java/com/microsoft/sqlserver/jdbc/resiliency/ResultSetsWithResiliencyTest.java
@@ -12,7 +12,6 @@ import static org.junit.jupiter.api.Assertions.assertTrue;
 import java.sql.CallableStatement;
 import java.sql.Connection;
 import java.sql.DriverManager;
-import java.sql.PreparedStatement;
 import java.sql.ResultSet;
 import java.sql.SQLException;
 import java.sql.Statement;
@@ -36,19 +35,26 @@ import com.microsoft.sqlserver.testframework.Constants;
 @Tag(Constants.xSQLv11)
 @Tag(Constants.xAzureSQLDW)
 public class ResultSetsWithResiliencyTest extends AbstractTest {
-    static String tableName = AbstractSQLGenerator.escapeIdentifier("resilencyTestTable");
+    static String tableName = AbstractSQLGenerator.escapeIdentifier(RandomUtil.getIdentifier("resiliencyTestTable"));
     static int numberOfRows = 10;
 
-    private static String callableStatementICROnDoneTestSp = AbstractSQLGenerator.escapeIdentifier(RandomUtil.getIdentifier("CallableStatement_ICROnDoneTest_SP"));
-    private static String callableStatementICROnDoneErrorTestSp = AbstractSQLGenerator.escapeIdentifier(RandomUtil.getIdentifier("CallableStatement_ICROnDoneErrorTest_SP"));
+    private static String callableStatementICROnDoneTestSp = AbstractSQLGenerator
+            .escapeIdentifier(RandomUtil.getIdentifier("CallableStatement_ICROnDoneTest_SP"));
+    private static String callableStatementICROnDoneErrorTestSp = AbstractSQLGenerator
+            .escapeIdentifier(RandomUtil.getIdentifier("CallableStatement_ICROnDoneErrorTest_SP"));
     private static String createClientCursorInitTableQuery = "create table %s (col1 int, col2 varchar(8000), col3 int identity(1,1))";
     private static String createFetchBufferTableQuery = "create table %s (col1 int not null)";
     private static String insertIntoFetchBufferTableQuery = "insert into %s (col1) values (%s);";
-    private static final String clientCursorInitTable1 = AbstractSQLGenerator.escapeIdentifier(RandomUtil.getIdentifier("clientCursorInitTable1"));
-    private static final String clientCursorInitTable2 = AbstractSQLGenerator.escapeIdentifier(RandomUtil.getIdentifier("clientCursorInitTable2"));
-    private static final String clientCursorInitTable3 = AbstractSQLGenerator.escapeIdentifier(RandomUtil.getIdentifier("clientCursorInitTable3"));
-    private static final String fetchBufferTestTable1 = AbstractSQLGenerator.escapeIdentifier(RandomUtil.getIdentifier("fetchBufferTestTable1"));
-    private static final String fetchBufferTestTable2 = AbstractSQLGenerator.escapeIdentifier(RandomUtil.getIdentifier("fetchBufferTestTable2"));
+    private static final String clientCursorInitTable1 = AbstractSQLGenerator
+            .escapeIdentifier(RandomUtil.getIdentifier("clientCursorInitTable1"));
+    private static final String clientCursorInitTable2 = AbstractSQLGenerator
+            .escapeIdentifier(RandomUtil.getIdentifier("clientCursorInitTable2"));
+    private static final String clientCursorInitTable3 = AbstractSQLGenerator
+            .escapeIdentifier(RandomUtil.getIdentifier("clientCursorInitTable3"));
+    private static final String fetchBufferTestTable1 = AbstractSQLGenerator
+            .escapeIdentifier(RandomUtil.getIdentifier("fetchBufferTestTable1"));
+    private static final String fetchBufferTestTable2 = AbstractSQLGenerator
+            .escapeIdentifier(RandomUtil.getIdentifier("fetchBufferTestTable2"));
     private static final String clientCursorInitTableQuery1 = "select * from " + clientCursorInitTable1;
     private static final String clientCursorInitTableQuery2 = "select * from " + clientCursorInitTable2;
     private static final String clientCursorInitTableQuery3 = "select * from " + clientCursorInitTable3;
@@ -165,93 +171,12 @@ public class ResultSetsWithResiliencyTest extends AbstractTest {
         }
     }
 
-    /*
-     * Test killing a session while retrieving result set should result in exception thrown
-     */
-    @Test
-    public void testKillSession() throws Exception {
-        // setup test with big tables
-        String table1 = AbstractSQLGenerator.escapeIdentifier(RandomUtil.getIdentifier("killSessionTestTable1"));
-        String table2 = AbstractSQLGenerator.escapeIdentifier(RandomUtil.getIdentifier("killSessionTestTable2"));
-        String table3 = AbstractSQLGenerator.escapeIdentifier(RandomUtil.getIdentifier("killSessionTestTable3"));
-
-        try (Connection c = DriverManager.getConnection(connectionString); Statement s = c.createStatement();
-                PreparedStatement ps1 = c.prepareStatement("INSERT INTO " + table1 + " values (?)");
-                PreparedStatement ps2 = c.prepareStatement("INSERT INTO " + table2 + " values (?)");
-                PreparedStatement ps3 = c.prepareStatement("INSERT INTO " + table3 + " values (?)")) {
-            TestUtils.dropTableIfExists(table1, s);
-            TestUtils.dropTableIfExists(table2, s);
-            TestUtils.dropTableIfExists(table3, s);
-            s.execute("CREATE TABLE " + table1
-                    + " (ID int primary key IDENTITY(1,1) NOT NULL, NAME varchar(255) NOT NULL); CREATE TABLE " + table2
-                    + " (ID int primary key IDENTITY(1,1) NOT NULL, NAME varchar(255) NOT NULL); CREATE TABLE " + table3
-                    + "( ID int primary key IDENTITY(1,1) NOT NULL, NAME varchar(255) NOT NULL);");
-
-            for (int i = 0; i < 1000; i++) {
-                ps1.setString(1, "value" + i);
-                ps2.setString(1, "value" + i);
-                ps3.setString(1, "value" + i);
-
-                ps1.addBatch();
-                ps2.addBatch();
-                ps3.addBatch();
-            }
-
-            ps1.executeBatch();
-            ps2.executeBatch();
-            ps3.executeBatch();
-
-            c.commit();
-
-            try (Connection c2 = DriverManager.getConnection(connectionString)) {
-                int sessionId = ResiliencyUtils.getSessionId(c2);
-
-                Runnable r1 = () -> {
-                    try {
-                        ResiliencyUtils.killConnection(sessionId, connectionString, c2, 10);
-                    } catch (Exception e) {
-                        fail(e.getMessage());;
-                    }
-                };
-
-                Thread t1 = new Thread(r1);
-                t1.start();
-
-                // execute query which takes a long time and kill session in another thread
-                try (PreparedStatement ps = c2.prepareStatement("SELECT e1.* FROM " + table1 + " e1, " + table2
-                        + " e2, " + table3 + " e3, " + table1
-                        + " e4 where e1.name = 'abc' or e2.name = 'def'or e3.name = 'ghi' or e4.name = 'xxx' and e1.name not in (select name  FROM "
-                        + table2 + ") and e2.name not in (select name  FROM " + table1
-                        + " ) and e3.name not in (SELECT name FROM " + table2
-                        + ") and e4.name not in (SELECT name FROM " + table3 + ");");
-                        ResultSet rs = ps.executeQuery()) {
-
-                    fail(TestResource.getResource("R_expectedExceptionNotThrown"));
-                } catch (SQLException e) {
-                    // may get different error message depending on SQL servers.
-                    // Local servers will report a TDS error where as Azure servers will have a DONE error
-                    assertTrue(
-                            e.getMessage().matches(TestUtils.formatErrorMsg("R_serverError"))
-                                    || e.getMessage().contains(TestResource.getResource("R_sessionKilled"))
-                                    || e.getMessage().contains(TestResource.getResource("R_connectionReset")),
-                            e.getMessage());
-                }
-                t1.join();
-
-            } finally {
-                TestUtils.dropTableIfExists(table1, s);
-                TestUtils.dropTableIfExists(table2, s);
-                TestUtils.dropTableIfExists(table3, s);
-            }
-        }
-    }
-
     @Test
     public void testResultSetClientCursorInitializerOnDone() throws SQLException {
         try (Connection con = ResiliencyUtils.getConnection(connectionString); Statement stmt = con.createStatement()) {
 
-            boolean hasResults = stmt.execute(clientCursorInitTableQuery1+ "; " + clientCursorInitTableQuery2);
-            while(hasResults) {
+            boolean hasResults = stmt.execute(clientCursorInitTableQuery1 + "; " + clientCursorInitTableQuery2);
+            while (hasResults) {
                 ResultSet rs = stmt.getResultSet();
                 while (rs.next()) {}
                 hasResults = stmt.getMoreResults();
@@ -259,9 +184,8 @@ public class ResultSetsWithResiliencyTest extends AbstractTest {
 
             ResiliencyUtils.killConnection(con, connectionString, 1);
 
-            try (ResultSet rs = con.createStatement()
-                    .executeQuery(clientCursorInitTableQuery3)) {
-                while(rs.next()) {}
+            try (ResultSet rs = con.createStatement().executeQuery(clientCursorInitTableQuery3)) {
+                while (rs.next()) {}
             }
         }
     }
@@ -272,7 +196,7 @@ public class ResultSetsWithResiliencyTest extends AbstractTest {
 
             try {
                 boolean hasResults = stmt.execute(clientCursorInitTableQuery1 + "; " + errorQuery);
-                while(hasResults) {
+                while (hasResults) {
                     ResultSet rs = stmt.getResultSet();
                     while (rs.next()) {}
                     hasResults = stmt.getMoreResults();
@@ -286,8 +210,7 @@ public class ResultSetsWithResiliencyTest extends AbstractTest {
 
             ResiliencyUtils.killConnection(con, connectionString, 1);
 
-            try (ResultSet rs = con.createStatement()
-                    .executeQuery(clientCursorInitTableQuery3)) {
+            try (ResultSet rs = con.createStatement().executeQuery(clientCursorInitTableQuery3)) {
                 while (rs.next()) {}
             }
         }
@@ -319,10 +242,8 @@ public class ResultSetsWithResiliencyTest extends AbstractTest {
 
     @Test
     public void testCallableStatementErrorOnDone() throws SQLException {
-        String errorCallableStmt = "{CALL "
-                + callableStatementICROnDoneErrorTestSp + " (?, ?)}";
-        String validCallableStmt = "{CALL "
-                + callableStatementICROnDoneTestSp + " (?, ?)}";
+        String errorCallableStmt = "{CALL " + callableStatementICROnDoneErrorTestSp + " (?, ?)}";
+        String validCallableStmt = "{CALL " + callableStatementICROnDoneTestSp + " (?, ?)}";
 
         try (Connection con = ResiliencyUtils.getConnection(connectionString)) {
 
@@ -367,7 +288,7 @@ public class ResultSetsWithResiliencyTest extends AbstractTest {
 
             try (Statement stmt = con.createStatement()) {
                 boolean hasResults = stmt.execute(fetchBufferTableQuery1 + "; " + fetchBufferTableQuery2);
-                while(hasResults) {
+                while (hasResults) {
                     ResultSet rs = stmt.getResultSet();
                     while (rs.next()) {}
                     hasResults = stmt.getMoreResults();
@@ -407,7 +328,7 @@ public class ResultSetsWithResiliencyTest extends AbstractTest {
 
             try (Statement stmt = con.createStatement()) {
                 boolean hasResults = stmt.execute(fetchBufferTableQuery1 + "; " + errorQuery);
-                while(hasResults) {
+                while (hasResults) {
                     ResultSet rs = stmt.getResultSet();
                     while (rs.next()) {}
                     hasResults = stmt.getMoreResults();
@@ -467,7 +388,7 @@ public class ResultSetsWithResiliencyTest extends AbstractTest {
     @Test
     public void testResultSetWithException() throws Exception {
         try (Connection c = ResiliencyUtils.getConnection(connectionString); Statement s = c.createStatement();
-             ResultSet rs = s.executeQuery("SELECT 1/0")) {
+                ResultSet rs = s.executeQuery("SELECT 1/0")) {
 
             while (rs.next()) {
                 ResiliencyUtils.killConnection(c, connectionString, 0);
diff --git a/src/test/java/com/microsoft/sqlserver/jdbc/unit/statement/PreparedStatementTest.java b/src/test/java/com/microsoft/sqlserver/jdbc/unit/statement/PreparedStatementTest.java
index a61a9b2e..c7bd1041 100644
--- a/src/test/java/com/microsoft/sqlserver/jdbc/unit/statement/PreparedStatementTest.java
+++ b/src/test/java/com/microsoft/sqlserver/jdbc/unit/statement/PreparedStatementTest.java
@@ -50,6 +50,7 @@ public class PreparedStatementTest extends AbstractTest {
     final static String tableName = RandomUtil.getIdentifier("tableTestStatementPoolingInternal1");
     final static String tableName2 = RandomUtil.getIdentifier("tableTestStatementPoolingInternal2");
     final static String tableName3 = RandomUtil.getIdentifier("tableTestPreparedStatementWithSpPrepare");
+    final static String tableName4 = RandomUtil.getIdentifier("tableTestPreparedStatementWithMultipleParams");
 
     @BeforeAll
     public static void setupTests() throws Exception {
@@ -117,6 +118,94 @@ public class PreparedStatementTest extends AbstractTest {
         }
     }
 
+    @Test
+    public void testPreparedStatementParamNameSpacingWithMultipleParams() throws SQLException {
+        int paramNameCount = 105;
+
+        StringBuilder insertSql = new StringBuilder("insert into " + AbstractSQLGenerator.escapeIdentifier(tableName4)
+                + "values(");
+
+        for (int i = 1; i <= paramNameCount; i++) {
+            insertSql.append(i % 10);
+
+            if (i != paramNameCount) {
+                insertSql.append(",");
+            } else {
+                insertSql.append(")");
+            }
+        }
+
+        StringBuilder createTableSql = new StringBuilder("create table " + AbstractSQLGenerator.escapeIdentifier(tableName4) + "(");
+
+        for (int i = 1; i <= paramNameCount; i++) {
+            createTableSql.append("c" + i + " char(1)");
+
+            if (i != paramNameCount) {
+                createTableSql.append(",");
+            } else {
+                createTableSql.append(")");
+            }
+        }
+
+        try (SQLServerConnection con = (SQLServerConnection) getConnection()) {
+            executeSQL(con, createTableSql.toString());
+            executeSQL(con, insertSql.toString());
+
+            // There are no typos in the queries eg. The 'c1=?and' is not a typo. We are testing the spacing, or lack of spacing.
+            // The driver should automatically space out the params eg. 'c1=?and' becomes 'c1= ? and' in the final string we send to the server.
+
+            // Testing with less than 10 params
+            String sql1 = "select * from " + AbstractSQLGenerator.escapeIdentifier(tableName4) + " where c1=?and c2=?";
+
+            // Testing with number of params between 10 and 100
+            String sql2 = "select * from " + AbstractSQLGenerator.escapeIdentifier(tableName4)
+                    + " where c1=?and c2=? and c3=?and c4=? and c5=? and c6=? and c7=? and c8=? and c9=? and c10=? and c11=? and c12=?";
+
+            // Testing with more than 100 params
+            StringBuilder sql3 = new StringBuilder("select * from " + AbstractSQLGenerator.escapeIdentifier(tableName4)
+                    + " where c1=?and ");
+
+            for (int i = 2; i <= paramNameCount; i++) {
+                sql3.append("c" + i + "=?");
+
+                if (i != paramNameCount) {
+                    sql3.append(" and ");
+                }
+            }
+
+            try (SQLServerPreparedStatement ps = (SQLServerPreparedStatement) con.prepareStatement(sql1)) {
+                ps.setString(1, "1");
+                ps.setString(2, "2");
+                ps.executeQuery();
+            }
+
+            try (SQLServerPreparedStatement ps = (SQLServerPreparedStatement) con.prepareStatement(sql2)) {
+                ps.setString(1, "1");
+                ps.setString(2, "2");
+                ps.setString(3, "3");
+                ps.setString(4, "4");
+                ps.setString(5, "5");
+                ps.setString(6, "6");
+                ps.setString(7, "7");
+                ps.setString(8, "8");
+                ps.setString(9, "9");
+                ps.setString(10, "0");
+                ps.setString(11, "1");
+                ps.setString(12, "2");
+                ps.executeQuery();
+            }
+
+            try (SQLServerPreparedStatement ps = (SQLServerPreparedStatement) con.prepareStatement(sql3.toString())) {
+                ps.setString(1, "1");
+                for (int i = 2; i <= paramNameCount; i++) {
+                    ps.setString(i, Integer.toString(i % 10));
+                }
+
+                ps.executeQuery();
+            }
+        }
+    }
+
     @Test
     public void testPreparedStatementPoolEvictionWithSpPrepare() throws SQLException {
         int cacheSize = 3;
@@ -787,6 +876,7 @@ public class PreparedStatementTest extends AbstractTest {
             TestUtils.dropTableIfExists(AbstractSQLGenerator.escapeIdentifier(tableName), stmt);
             TestUtils.dropTableIfExists(AbstractSQLGenerator.escapeIdentifier(tableName2), stmt);
             TestUtils.dropTableIfExists(AbstractSQLGenerator.escapeIdentifier(tableName3), stmt);
+            TestUtils.dropTableIfExists(AbstractSQLGenerator.escapeIdentifier(tableName4), stmt);
         }
     }
 
diff --git a/src/test/java/com/microsoft/sqlserver/testframework/AbstractTest.java b/src/test/java/com/microsoft/sqlserver/testframework/AbstractTest.java
index 1d9eb244..9113ca19 100644
--- a/src/test/java/com/microsoft/sqlserver/testframework/AbstractTest.java
+++ b/src/test/java/com/microsoft/sqlserver/testframework/AbstractTest.java
@@ -66,6 +66,10 @@ public abstract class AbstractTest {
     protected static String[] enclaveAttestationUrl = null;
     protected static String[] enclaveAttestationProtocol = null;
 
+
+    protected static String kerberosServer = null;
+    protected static String kerberosServerPort = null;
+
     protected static String clientCertificate = null;
     protected static String clientKey = null;
     protected static String clientKeyPassword = "";
@@ -103,6 +107,7 @@ public abstract class AbstractTest {
     protected static Connection connectionAzure = null;
     protected static String connectionString = null;
     protected static String connectionStringNTLM;
+    protected static String connectionStringKerberos;
 
     protected static ConfidentialClientApplication fedauthClientApp = null;
 
@@ -193,6 +198,9 @@ public abstract class AbstractTest {
 
         clientKeyPassword = getConfiguredProperty("clientKeyPassword", "");
 
+        kerberosServer = getConfiguredProperty("kerberosServer", null);
+        kerberosServerPort = getConfiguredProperty("kerberosServerPort", null);
+
         trustStore = getConfiguredProperty("trustStore", "");
         if (!trustStore.trim().isEmpty()) {
             connectionString = TestUtils.addOrOverrideProperty(connectionString, "trustStore", trustStore);
@@ -250,7 +258,7 @@ public abstract class AbstractTest {
     protected static void setupConnectionString() {
         connectionStringNTLM = connectionString;
 
-        // if these properties are defined then NTLM is desired, modify connection string accordingly
+        // If these properties are defined then NTLM is desired, modify connection string accordingly
         String domain = getConfiguredProperty("domainNTLM");
         String user = getConfiguredProperty("userNTLM");
         String password = getConfiguredProperty("passwordNTLM");
@@ -273,6 +281,14 @@ public abstract class AbstractTest {
             connectionStringNTLM = TestUtils.addOrOverrideProperty(connectionStringNTLM, "integratedSecurity", "true");
         }
 
+        if (null != kerberosServer && null != kerberosServerPort) {
+            connectionStringKerberos = "jdbc:sqlserver://" + kerberosServer + ":" + kerberosServerPort + ";";
+            connectionStringKerberos = TestUtils.addOrOverrideProperty(connectionStringKerberos, "authenticationScheme", "JavaKerberos");
+            connectionStringKerberos = TestUtils.addOrOverrideProperty(connectionStringKerberos, "integratedSecurity", "true");
+            connectionStringKerberos = TestUtils.addOrOverrideProperty(connectionStringKerberos, "trustServerCertificate", "true");
+            connectionStringKerberos = TestUtils.addOrOverrideProperty(connectionStringKerberos, "encrypt", "false");
+        }
+
         ds = updateDataSource(connectionString, new SQLServerDataSource());
         dsXA = updateDataSource(connectionString, new SQLServerXADataSource());
         dsPool = updateDataSource(connectionString, new SQLServerConnectionPoolDataSource());
diff --git a/src/test/java/com/microsoft/sqlserver/testframework/Constants.java b/src/test/java/com/microsoft/sqlserver/testframework/Constants.java
index ce50791f..92ba9081 100644
--- a/src/test/java/com/microsoft/sqlserver/testframework/Constants.java
+++ b/src/test/java/com/microsoft/sqlserver/testframework/Constants.java
@@ -25,6 +25,7 @@ public final class Constants {
      * xAzureSQLDW - - - - For tests not compatible with Azure Data Warehouse
      * xAzureSQLMI - - - - For tests not compatible with Azure SQL Managed Instance
      * NTLM  - - - - - - - For NTLM tests
+     * Kerberos  - - - - - For Kerberos tests
      * reqExternalSetup  - For tests requiring external setup
      * clientCertAuth  - - For tests requiring client certificate authentication setup
      * Fedauth - - - - - - For Fedauth tests
@@ -41,6 +42,7 @@ public final class Constants {
     public static final String xAzureSQLDW = "xAzureSQLDW";
     public static final String xAzureSQLMI = "xAzureSQLMI";
     public static final String NTLM = "NTLM";
+    public static final String kerberos = "kerberos";
     public static final String MSI = "MSI";
     public static final String reqExternalSetup = "reqExternalSetup";
     public static final String clientCertAuth = "clientCertAuth";