Handshake failure after JDK upgrade

by Brian Fitzgerald

Introduction

After upgrading to JDK 8u481, 11.0.30, 17.0.18, 21.0.10, or 25.0.2 or later, this error appears:

java.sql.SQLRecoverableException: IO Error: IO Error (handshake_failure)
Received fatal alert: handshake_failure, Authentication lapse

The correct solution preserves forward secrecy.

Scenario

Your JDBC connection to Oracle Database using SSL (TLS) has been working for a long time. Your JVM runs with -Djavax.net.ssl.trustStore, and your database connection string is “jdbc:oracle:thin:@tcps://..” or “jdbc:oracle:thin:@(..(PROTOCOL=TCPS)..)”.

In this example, let us assume that you upgraded JDK from version 17.0.17 to 17.0.19. You are now getting the handshake error mentioned earlier.

Triage

You can rule out a certificate issue. This type of handshake error happens before the certificate check. This will be clearer in the trace analysis in the next section.

Diagnosis

Trace your java program

java -Djavax.net.debug=ssl:handshake ...

Trace the old and new JDKs.

Look for ClientHello. Look for the list of client cipher suites

"ClientHello": {
...
"cipher suites" : "[
    TLS_AES_256_GCM_SHA384(0x1302), 
    TLS_AES_128_GCM_SHA256(0x1301), 
...
    TLS_EMPTY_RENEGOTIATION_INFO_SCSV(0x00FF) (disregard this one)
]" ,

some 30 cipher suites in this example. Next, look at the list of server cipher suites. In the RDS option group, option SSL. Here is an example:

    "OptionName": "SSL",
    "OptionSettings": [
        ...
        {
            "Name": "SQLNET.CIPHER_SUITE",
            "Value": "SSL_RSA_WITH_AES_256_CBC_SHA"
        },

Perform this comparison mentally, or by saving the outputs above as two lists. For apples to apples comparison, strip the codes and the commas from ClientHello. In the option group, map prefix “SSL_” to “TLS_”, Strip the quotes and commas. Compare the lists and notice that no cipher suite is common to both lists.

In the trace of the old (working) JDK, also look for ServerHello. For example:

ServerHello.java:883|Consuming ServerHello handshake message (
"ServerHello": {
  "server version"      : "TLSv1.2",
  ... 
 "cipher suite"        : "TLS_RSA_WITH_AES_256_CBC_SHA(0x0035)",
  ...
 }
)

In this example, the server only allows cipher suite TLS_RSA_WITH_AES_256_CBC_SHA.

Note that the server returns the certificate only after a successful cipher suite negotiation.

$ egrep -n 'ServerHello' debug.old.output.txt | head -1
6973:javax.net.ssl|DEBUG|E5|C3P0PooledConnectionPoolManager[..]-HelperThread-#0|2026-07-28 02:16:22.119 UTC|ServerHello.java:883|Consuming ServerHello handshake message (
$ egrep -n 'Consuming server Certificate handshake message' debug.old.output.txt | head -1
7104:javax.net.ssl|DEBUG|F5|C3P0PooledConnectionPoolManager[..]-HelperThread-#1|2026-07-28 02:16:22.126 UTC|CertificateMessage.java:366|Consuming server Certificate handshake message (

In the failed case, there was no ServerHello or server certificate return. This trace analysis reinforces the previous claim that no certificate issue played a role in the handshake error.

What happened?

You didn’t do anything wrong. When you set up the SSL option, the ECDHE suites did not exist in RDS. AWS added them to the SSL option on February 3, 2023. If your instance predates that, static RSA suites were the only ones on the menu.

Times have changed.

Cipher suites named like “SSL_RSA_” are static key exchange cipher suites and lack forward secrecy. The cipher suites in JDK 17.0.18+ that are allowed by default preserve forward secrecy. In fact, all cipher suites that are allowed by default in JDK version 8u481, 11.0.30, 17.0.18, 21.0.10, or 25.0.2, or later preserve forward secrecy. These versions were released in the January 2026 Oracle Critical Patch Update. This year’s shift to forward secrecy has led to a new manifestation of the handshake error.

If you check AWS now, you will find that a few of the JDK 17.0.18+ suites are available in the AWS RDS SSL option.

Solution

Your task is to extend your cipher suite list with at least one JDK 17.0.18+ cipher suite. I picked TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384.

Implementation

You can make this change on the fly. No db instance restart required.

options="$(cat $json_file)" 
aws rds add-option-to-option-group 
  --no-cli-pager 
  --apply-immediately 
  --options "$options" 
  --option-group-name $option_group_name

where json_file points to a file containing:

[
    {
        "OptionName": "SSL",
        "VpcSecurityGroupMemberships": [
            "sg-0a567891234"
        ],
        "Port": 2484,
        "OptionSettings": [
            {
                "Name": "FIPS.SSLFIPS_140",
                "Value": "FALSE"
            },
            {
                "Name": "SQLNET.CIPHER_SUITE",
                "Value": "SSL_RSA_WITH_AES_256_CBC_SHA,TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384"
            },
            {
                "Name": "SQLNET.SSL_VERSION",
                "Value": "1.2"
            }
        ]
    }
]

Notice that you must fully specify the option, including port, security group, and non-default options. Be sure to specify version “1.2”, not “1.0” or “1.0,1.2”.

The choice of TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384

If the provided script solved your problem, then this section is optional reading.

Find out the cipher suites that are available in RDS.

$ aws rds describe-option-group-options --engine-name oracle-ee   --major-engine-version 19 --query "OptionGroupOptions[?Name=='SSL']" | jq '.[].OptionGroupOptionSettings[] | select (.SettingName == "SQLNET.CIPHER_SUITE")'
{
  "SettingName": "SQLNET.CIPHER_SUITE",
  "SettingDescription": "Specifies the desired SSL cipher suite",
  "DefaultValue": "SSL_RSA_WITH_AES_256_CBC_SHA",
  "ApplyType": "STATIC",
  "AllowedValues": "SSL_RSA_WITH_AES_256_CBC_SHA,..
  "IsModifiable": true,
  "IsRequired": false,
  "MinimumEngineVersionPerAllowedValue": []
}

Despite “ApplyType”: “STATIC”, experience shows that no RDS db instance restart is required.

The available cipher suites in RDS are, in 19c, as of today:

SSL_RSA_WITH_AES_256_CBC_SHA
SSL_RSA_WITH_AES_256_CBC_SHA256
SSL_RSA_WITH_AES_256_GCM_SHA384
TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384
TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384
TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA
TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256
TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256
TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA
TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384
TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384

Choosing the cipher suite

We can choose our cipher suite by process of elimination.

Let’s eliminate “SSL_RSA_*” (the static key exchange suites).

Given that AES 256 is available in JDK 17.0.18+, rule out AES 128.

SHA 384 is available, so rule out SHA and SHA 256.

Look in the trace of the old JVM.

javax.net.ssl...CertificateMessage.java:366
|Consuming server Certificate handshake message (...
"signature algorithm": "SHA256withRSA",
"issuer" : "L=Seattle, CN=Amazon RDS us-east-1 Subordinate CA RSA2048 G1.A.4, 
ST=WA, OU=Amazon RDS, O="Amazon Web Services, Inc.", C=US",
...
"subject public key" : "RSA",

The certificate “subject public key” is RSA and will not work with ECDSA – rule out.

CBC — cipher block chaining. Encrypted data can be tampered with undetected unless a separate integrity step is added. Flaws in that arrangement have been exploited.

GCM — Galois/Counter Mode. Encrypts and verifies integrity together. No such flaw, and faster.

Our final choice: TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384

Not recommended: re-enabling static key exchange

The correct solution is to use cipher suites that preserve forward secrecy. Instead of using static key exchange, use cipher suites that implement ephemeral key agreement. Make this change on the server side.

On the client side, in file conf/security/java.security, this section can be modified:

jdk.tls.disabledAlgorithms=SSLv3, TLSv1, TLSv1.1, DTLSv1.0, RC4, DES, \
    MD5withRSA, DH keySize < 1024, EC keySize < 224, 3DES_EDE_CBC, anon, NULL, \
    ECDH, TLS_RSA_*, rsa_pkcs1_sha1 usage HandshakeSignature, \
    ecdsa_sha1 usage HandshakeSignature, dsa_sha1 usage HandshakeSignature

If you delete ECDH or TLS_RSA_*, you will stop the error message, but you will re-enable cipher suites that do not preserve forward secrecy.

Conclusion

Oracle DBAs can contribute to application security in an RDS environment by updating their DB option group’s SSL option to use the most secure available cipher suites. You can make this change behind the scenes without requiring any application-side changes. No database downtime is required.

Appendices

A. diff and comm

This appendix compares and contrasts the Linux diff and comm commands and shows how to use comm to reconcile lists.

Use of diff

For side by side comparison, you can do:

$ diff --side-by-side ./17.0.17/ClientHello.l ./17.0.19/ClientHello.l
TLS_AES_128_GCM_SHA256                                          TLS_AES_128_GCM_SHA256
TLS_AES_256_GCM_SHA384                                          TLS_AES_256_GCM_SHA384
TLS_CHACHA20_POLY1305_SHA256                                    TLS_CHACHA20_POLY1305_SHA256
TLS_DHE_DSS_WITH_AES_128_CBC_SHA                                TLS_DHE_DSS_WITH_AES_128_CBC_SHA
TLS_DHE_DSS_WITH_AES_128_CBC_SHA256                             TLS_DHE_DSS_WITH_AES_128_CBC_SHA256
TLS_DHE_DSS_WITH_AES_128_GCM_SHA256                             TLS_DHE_DSS_WITH_AES_128_GCM_SHA256
TLS_DHE_DSS_WITH_AES_256_CBC_SHA                                TLS_DHE_DSS_WITH_AES_256_CBC_SHA
TLS_DHE_DSS_WITH_AES_256_CBC_SHA256                             TLS_DHE_DSS_WITH_AES_256_CBC_SHA256
TLS_DHE_DSS_WITH_AES_256_GCM_SHA384                             TLS_DHE_DSS_WITH_AES_256_GCM_SHA384
TLS_DHE_RSA_WITH_AES_128_CBC_SHA                                TLS_DHE_RSA_WITH_AES_128_CBC_SHA
TLS_DHE_RSA_WITH_AES_128_CBC_SHA256                             TLS_DHE_RSA_WITH_AES_128_CBC_SHA256
TLS_DHE_RSA_WITH_AES_128_GCM_SHA256                             TLS_DHE_RSA_WITH_AES_128_GCM_SHA256
TLS_DHE_RSA_WITH_AES_256_CBC_SHA                                TLS_DHE_RSA_WITH_AES_256_CBC_SHA
TLS_DHE_RSA_WITH_AES_256_CBC_SHA256                             TLS_DHE_RSA_WITH_AES_256_CBC_SHA256
TLS_DHE_RSA_WITH_AES_256_GCM_SHA384                             TLS_DHE_RSA_WITH_AES_256_GCM_SHA384
TLS_DHE_RSA_WITH_CHACHA20_POLY1305_SHA256                       TLS_DHE_RSA_WITH_CHACHA20_POLY1305_SHA256
TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA                            TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA
TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256                         TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256
TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256                         TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256
TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA                            TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA
TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384                         TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384
TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384                         TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384
TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256                   TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256
TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA                              TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA
TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256                           TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256
TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256                           TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256
TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA                              TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA
TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384                           TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384
TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384                           TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384
TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256                     TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256
TLS_EMPTY_RENEGOTIATION_INFO_SCSV                               TLS_EMPTY_RENEGOTIATION_INFO_SCSV
TLS_RSA_WITH_AES_128_CBC_SHA                                  <
TLS_RSA_WITH_AES_128_CBC_SHA256                               <
TLS_RSA_WITH_AES_128_GCM_SHA256                               <
TLS_RSA_WITH_AES_256_CBC_SHA                                  <
TLS_RSA_WITH_AES_256_CBC_SHA256                               <
TLS_RSA_WITH_AES_256_GCM_SHA384                               <

However, if you want a neater list, use comm.

Use of comm

Given two sorted list files, use comm to get the items common to both lists, or items that are only in one list or the other.

Use comm -23 to get the items only in the first list.  For the cipher suites allowed by default in version 17.0.17 but not in 17.0.19, run:

$ comm -23 ./17.0.17/ClientHello.l ./17.0.19/ClientHello.l
TLS_RSA_WITH_AES_128_CBC_SHA
TLS_RSA_WITH_AES_128_CBC_SHA256
TLS_RSA_WITH_AES_128_GCM_SHA256
TLS_RSA_WITH_AES_256_CBC_SHA
TLS_RSA_WITH_AES_256_CBC_SHA256
TLS_RSA_WITH_AES_256_GCM_SHA384

Use comm -12 to show the cipher suites that are allowed by default in JDK 17.0.18+ and are available in RDS:

$ comm -12 jdk/17.0.19/ClientHello.l  <(sed 's/^SSL_/TLS_/' oracle-ee.19.SSL.cipher-suites.l | sort)
TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384
TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384
TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA
TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256
TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256
TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA
TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384
TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384

Of the eight cipher suites in common between JDK and RDS, we ruled out ECDSA because of certificate incompatibility. We ruled out AES 128 in favor of AES 256, and we ruled out SHA and SHA 256 in favor of SHA 384. We ruled out CBC because of known flaws in that scheme. Decision: TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384.

B. Listing the default cipher suites

Even before setting up a client-server connection, you can check the cipher suites that are allowed by default:

import java.util.Arrays;
import javax.net.ssl.SSLContext;

public class DefaultCipherSuites {
    public static void main(String[] args) throws Exception {
        String[] suites = SSLContext.getDefault()
                                    .getDefaultSSLParameters()
                                    .getCipherSuites();
        Arrays.sort(suites);
        for (String s : suites) {
            System.out.println(s);
        }
    }
}
$ ./jdk1.8.0_501/bin/javac DefaultCipherSuites.java
$ ./jdk1.8.0_501/bin/java DefaultCipherSuites
TLS_AES_128_GCM_SHA256
TLS_AES_256_GCM_SHA384
...
$ ./jdk-25.0.4/bin/javac DefaultCipherSuites.java
$ ./jdk-25.0.4/bin/java DefaultCipherSuites
TLS_AES_128_GCM_SHA256
TLS_AES_256_GCM_SHA384
...