What is ALPN?

Some applications might want or need to negotiate a shared application level value before a TLS handshake has completed. For example, HTTP/2 uses the Application Layer Protocol Negotiation mechanism to help establish which HTTP version ("h2", "spdy/3", "http/1.1") can or will be used on a particular TCP or UDP port. ALPN (RFC 7301) does this without adding network round-trips between the client and the server. In the case of HTTP/2 the protocol must be established before the connection is negotiated, as client and server need to know what version of HTTP to use before they start communicating. Without ALPN it would not be possible to have application protocols HTTP/1 and HTTP/2 on the same port.

The client uses the ALPN extension at the beginning of the TLS handshake to send a list of supported application protocols to the server as part of the ClientHello. The server reads the list of supported application protocols in the ClientHello, and determines which of the supported protocols it prefers. It then sends a ServerHello message back to the client with the negotiation result. The message may contain either the name of the protocol that has been chosen or that no protocol has been chosen.

The application protocol negotiation can thus be accomplished within the TLS handshake, without adding network round-trips, and allows the server to associate a different certificate with each application protocol, if desired.

Unlike many other TLS extensions, this extension does not establish properties of the session, only of the connection. That's why you'll find the negotiated values in the SSLSocket/ SSLEngine, not the SSLSession. When session resumption or session tickets are used (see TLS Session Resumption without Server-Side State), the previously negotiated values are irrelevant, and only the values in the new handshake messages are considered.

Setting up ALPN on the Client

Set the Application Layer Protocol Negotiation (ALPN) values supported by the client. During the handshake with the server, the server will read the client's list of application protocols and will determine which is most suitable.

For the client, use the SSLParameters.setApplicationProtocols(String[]) method, followed by the setSSLParameters method of either SSLSocket or SSLEngine to set up the application protocols to send to the server.

For example, here are the steps to set ALPN values of "three" and "two", on the client.

To run the code the property javax.net.ssl.trustStore must be set to a valid root certificate. (This can be done on the command line).

import java.io.*; 
import java.util.*;
import javax.net.ssl.*; 
public class SSLClient {
    public static void main(String[] args) throws Exception {

        // Code for creating a client side SSLSocket
        SSLSocketFactory sslsf = (SSLSocketFactory) SSLSocketFactory.getDefault();
        SSLSocket sslSocket = (SSLSocket) sslsf.createSocket("localhost", 9999);

        // Get an SSLParameters object from the SSLSocket
        SSLParameters sslp = sslSocket.getSSLParameters();

        // Populate SSLParameters with the ALPN values
        // On the client side the order doesn't matter as
        // when connecting to a JDK server, the server's list takes priority
        String[] clientAPs = {"three", "two"};
        sslp.setApplicationProtocols(clientAPs);

        // Populate the SSLSocket object with the SSLParameters object
        // containing the ALPN values
        sslSocket.setSSLParameters(sslp);

        sslSocket.startHandshake();

        // After the handshake, get the application protocol that has been negotiated
        String ap = sslSocket.getApplicationProtocol();
        System.out.println("Application Protocol client side: \"" + ap + "\"");

        // Do simple write/read
        InputStream sslIS = sslSocket.getInputStream();
        OutputStream sslOS = sslSocket.getOutputStream();
        sslOS.write(280);
        sslOS.flush();
        sslIS.read();
        sslSocket.close();
    }
}

When this code is run and sends a ClientHello to a Java server that has set the ALPN values one, two, and three, the output will be:

Application Protocol client side: two

It is also possible to check the results of the negotiation during handshaking. See Determining Negotiated ALPN Value during Handshaking.

Setting up Default ALPN on the Server

Use the default ALPN mechanism to determine a suitable application protocol by setting ALPN values on the server.

To use the default mechanism for ALPN on the server, populate an SSLParameters object with the ALPN values you wish to set, and then use this SSLParameters object to populate either the SSLSocket object or the SSLEngine object with these parameters as you have done when you set up ALPN on the client (see the section Setting up ALPN on the Client). The first value of the ALPN values set on the server that matches any of the ALPN values contained in the ClientHello will be chosen and returned to the client as part of the ServerHello.

Here is the code for a Java server that uses the default approach for protocol negotiation. To run the code the property javax.net.ssl.keyStore must be set to a valid keystore. (This can be done on the command line, see Creating a Keystore to Use with JSSE).

import java.util.*; 
import javax.net.ssl.*; 
public class SSLServer {
    public static void main(String[] args) throws Exception {

        // Code for creating a server side SSLSocket
        SSLServerSocketFactory sslssf = 
            (SSLServerSocketFactory) SSLServerSocketFactory.getDefault();
        SSLServerSocket sslServerSocket = 
            (SSLServerSocket) sslssf.createServerSocket(9999);
        SSLSocket sslSocket = (SSLSocket) sslServerSocket.accept();

        // Get an SSLParameters object from the SSLSocket
        SSLParameters sslp = sslSocket.getSSLParameters();

        // Populate SSLParameters with the ALPN values
        // As this is server side, put them in order of preference
        String[] serverAPs ={ "one", "two", "three" };
        sslp.setApplicationProtocols(serverAPs);

        // If necessary at any time, get the ALPN values set on the 
        // SSLParameters object with:
        // String serverAPs = sslp.setApplicationProtocols();

        // Populate the SSLSocket object with the ALPN values
        sslSocket.setSSLParameters(sslp);

        sslSocket.startHandshake();

        // After the handshake, get the application protocol that 
        // has been negotiated

        String ap = sslSocket.getApplicationProtocol();
        System.out.println("Application Protocol server side: \"" + ap + "\"");

        // Continue with the work of the server
        InputStream sslIS = sslSocket.getInputStream();
        OutputStream sslOS = sslSocket.getOutputStream();
        sslIS.read();
        sslOS.write(85);
        sslOS.flush();
        sslSocket.close();
    }
}
When this code is run and a Java client sends a ClientHello with ALPN values three and two, the output is:
Application Protocol server side: two

It is also possible to check the results of the negotiation during handshaking. See Determining Negotiated ALPN Value during Handshaking.

Setting up Custom ALPN on the Server

Use the custom ALPN mechanism to determine a suitable application protocol by setting up a callback method.

If you do not want to use the server's default negotiation protocol, you can use the setHandshakeApplicationProtocolSelector method of SSLEngine or SSLSocket to register a BiFunction (lambda) callback that can examine the handshake state so far, and then make your selection based on the client's list of application protocols and any other relevant information. For example, you may consider using the cipher suite suggested, or the Server Name Indication (SNI) or any other data you can obtain in making the choice. If custom negotiation is used, the values set by the setApplicationProtocols method (default negotiation) will be ignored.

Here is the code for a Java server that uses the custom mechanism for protocol negotiation. To run the code the property javax.net.ssl.keyStore must be set to a valid certificate. (This can be done on the command line, see Creating a Keystore to Use with JSSE).

import java.util.*; 
import javax.net.ssl.*; 
public class SSLServer {
    public static void main(String[] args) throws Exception {

        // Code for creating a server side SSLSocket
        SSLServerSocketFactory sslssf =
            (SSLServerSocketFactory) SSLServerSocketFactory.getDefault();
        SSLServerSocket sslServerSocket = 
            (SSLServerSocket) sslssf.createServerSocket(9999);
        SSLSocket sslSocket = (SSLSocket) sslServerSocket.accept();

        // Code to set up a callback function
        // Pass in the current SSLSocket to be inspected and client AP values
        sslSocket.setHandshakeApplicationProtocolSelector(
            (serverSocket, clientProtocols) -> {
                SSLSession handshakeSession = serverSocket.getHandshakeSession();
                // callback function called with current SSLSocket and client AP values
                // plus any other useful information to help determine appropriate
                // application protocol. Here the protocol and ciphersuite are also
                // passed to the callback function.
                return chooseApplicationProtocol(
                    serverSocket,
                    clientProtocols,
                    handshakeSession.getProtocol(),
                    handshakeSession.getCipherSuite());
         }); 

        sslSocket.startHandshake();

        // After the handshake, get the application protocol that has been
        // returned from the callback method.

        String ap = sslSocket.getApplicationProtocol();
        System.out.println("Application Protocol server side: \"" + ap + "\"");

        // Continue with the work of the server
        InputStream sslIS = sslSocket.getInputStream();
        OutputStream sslOS = sslSocket.getOutputStream();
        sslIS.read();
        sslOS.write(85);
        sslOS.flush();
        sslSocket.close();
    }

    // The callback method. Note how the parameters match the call within 
    // the setHandshakeApplicationProtocolSelector method above.
    public static String chooseApplicationProtocol(SSLSocket serverSocket,
            List<String> clientProtocols, String protocol, String cipherSuite ) {
        // For example, check the cipher suite and return an application protocol
        // value based on that.
        if (cipherSuite.equals("<--a_particular_ciphersuite-->")) { 
            return "three";
        } else {
            return "";
        }
    } 
}

If the cipher suite matches the one you specify in the condition statement when this code is run , then the value three will be returned. Otherwise an empty string will be returned.

Note that the BiFunction object's return value is a String, which will be the application protocol name, or null to indicate that none of the advertised names are acceptable. If the return value is an empty String, then application protocol indications will not be used. If the return value is null (no value chosen) or is a value that was not advertised by the peer, the underlying protocol will determine what action to take. (For example, the server code will send a "no_application_protocol" alert and terminate the connection.)

After handshaking completes on both client and server, you can check the result of the negotiation by calling the getApplicationProtocol method on either the SSLSocket object or the SSLEngine object.

Determining Negotiated ALPN Value during Handshaking

To determine the ALPN value that has been negotiated during the handshaking, create a custom KeyManager or TrustManager class, and include in this custom class a call to the getHandshakeApplicationProtocol method.

There are some use cases where the selected ALPN and SNI values will affect the choices made by a KeyManager or TrustManager. For example, an application might want to select different certificate/private key sets depending on the attributes of the server and the chosen ALPN/SNI/ciphersuite values.

The sample code given illustrates how to call the getHandshakeApplicationProtocol method from within a custom X509ExtendedKeyManager that you create and register as the KeyManager object.

This example shows the entire code for a custom KeyManager that extends X509ExtendedKeyManager. Most methods simply return the value returned from the KeyManager class that is being wrapped by this MyX509ExtendedKeyManager class. However the chooseServerAlias method calls the getHandshakeApplicationProtocol on the SSLSocket object and therefore can determine the current negotiated ALPN value.

import java.net.Socket;
import java.security.*;
import javax.net.ssl.*;

public class MyX509ExtendedKeyManager extends X509ExtendedKeyManager {

    // X509ExtendedKeyManager is an abstract class so your new class 
    // needs to implement all the abstract methods in this class. 
    // The easiest way to do this is to wrap an existing KeyManager
    // and call its methods for each of the methods you need to implement.   

    X509ExtendedKeyManager akm;
    
    public MyX509ExtendedKeyManager(X509ExtendedKeyManager akm) {
        this.akm = akm;
    }

    @Override
    public String[] getClientAliases(String keyType, Principal[] issuers) {
        return akm.getClientAliases(keyType, issuers);
    }

    @Override
    public String chooseClientAlias(String[] keyType, Principal[] issuers, 
        Socket socket) {
        return akm.chooseClientAlias(keyType, issuers, socket);
    }

    @Override
    public String chooseServerAlias(String keyType, Principal[] issuers, 
        Socket socket) {
        
        // This method has access to a Socket, so it is possible to call the
        // getHandshakeApplicationProtocol method here. Note the cast from 
        // a Socket to an SSLSocket
        String ap = ((SSLSocket) socket).getHandshakeApplicationProtocol();
        System.out.println("In chooseServerAlias, ap is: " + ap);
        return akm.chooseServerAlias(keyType, issuers, socket);
    }

    @Override
    public String[] getServerAliases(String keyType, Principal[] issuers) {
        return akm.getServerAliases(keyType, issuers);
    }

    @Override
    public X509Certificate[] getCertificateChain(String alias) {
        return akm.getCertificateChain(alias);
    }

    @Override
    public PrivateKey getPrivateKey(String alias) {
        return akm.getPrivateKey(alias);
    }
}
When this code is registered as the KeyManager for a Java server and a Java client sends a ClientHello with ALPN values, the output will be:
In chooseServerAlias, ap is: <negotiated value>

This example shows a simple Java server that uses the default ALPN negotiation strategy and the custom KeyManager, MyX509ExtendedKeyManager, shown in the prior code sample.

import java.io.*;
import java.util.*;
import javax.net.ssl.*;
import java.security.KeyStore;

public class SSLServerHandshake {
    
    public static void main(String[] args) throws Exception {
        SSLContext ctx = SSLContext.getInstance("TLS");

        // You need to explicitly create a create a custom KeyManager

        // Keystores
        KeyStore keyKS = KeyStore.getInstance("PKCS12");
        keyKS.load(new FileInputStream("serverCert.p12"), 
            "password".toCharArray());

        // Generate KeyManager
        KeyManagerFactory kmf = KeyManagerFactory.getInstance("PKIX");
        kmf.init(keyKS, "password".toCharArray());
        KeyManager[] kms = kmf.getKeyManagers();

        // Code to substitute MyX509ExtendedKeyManager
        if (!(kms[0] instanceof X509ExtendedKeyManager)) {
            throw new Exception("kms[0] not X509ExtendedKeyManager");
        }

        // Create a new KeyManager array and set the first index 
        // of the array to an instance of MyX509ExtendedKeyManager.
        // Notice how creating this object is done by passing in the 
        // existing default X509ExtendedKeyManager 
        kms = new KeyManager[] { 
            new MyX509ExtendedKeyManager((X509ExtendedKeyManager) kms[0])};

        // Initialize SSLContext using the new KeyManager
        ctx.init(kms, null, null);

        // Instead of using SSLServerSocketFactory.getDefault(), 
        // get a SSLServerSocketFactory based on the SSLContext
        SSLServerSocketFactory sslssf = ctx.getServerSocketFactory();
        SSLServerSocket sslServerSocket = 
            (SSLServerSocket) sslssf.createServerSocket(9999);
        SSLSocket sslSocket = (SSLSocket) sslServerSocket.accept();
        SSLParameters sslp = sslSocket.getSSLParameters();
        String[] serverAPs ={"one","two","three"};
        sslp.setApplicationProtocols(serverAPs);
        sslSocket.setSSLParameters(sslp);
        sslSocket.startHandshake();

        String ap = sslSocket.getApplicationProtocol();
        System.out.println("Application Protocol server side: \"" + ap + "\"");

        InputStream sslIS = sslSocket.getInputStream();
        OutputStream sslOS = sslSocket.getOutputStream();
        sslIS.read();
        sslOS.write(85);
        sslOS.flush();

        sslSocket.close();
        sslServerSocket.close();
    }
}

With the custom X509ExtendedKeyManager in place, when chooseServerAlias is called during handshaking the KeyManager has the opportunity to examine the negotiated application protocol value. In the case of the example shown, this value is output to the console.

For example, when this code is run and a Java client sends a ClientHello with ALPN values three and two, the output will be:
Application Protocol server side: two

Reading and Writing ALPN Values with the SunJSSE Provider

ALPN transports data with byte arrays, which means that it expects text to be encoded with single byte character encodings such as US-ASCII. Java ALPN APIs use the String class for text, but prior to Java SE 16/11.0.2/8u301, the SunJSSE provider converts String instances to byte arrays with UTF-8. However, UTF-8 is a variable-width character encoding. It encodes characters above U+007F with more than one byte, which may not be expected by an ALPN peer.

In Java SE 16/11.0.2/8u301 and later, the SunJSSE provider encodes and decodes String characters as 8-bit ISO_8859_1/LATIN-1 characters.

ALPN values are now represented using the network byte representation expected by the peer, which should require no modification for standard 7-bit ASCII-based String instances.

The methods in the javax.net.ssl.SSLSocket and javax.net.ssl.SSLEngine return ApplicationProtocol String values in the network byte representation sent by the peer.

However, if you have Unicode data with characters that are above U+007F, then your application must correctly encode or decode them to byte arrays before sending or receiving them instead of relying on the SunJSSE provider to automatically encode or decode Unicode characters. Alternatively, you can set the security property jdk.tls.alpnCharset to UTF-8 to revert to the previous behavior.

To compare ALPN values with their expected values, you can convert them to byte arrays and then compare them.

The expected ALPN values in the following example are the string http/1.1 and the UTF-8 encoded string (in hexadecimal) 0xABCD0xABCE0xABCF (which are the Meetei Mayek letters "HUK UN I"). The example converts the ALPN value to a byte array with ISO-8859-1, converts http/1.1 to a byte array with UTF-8, and manually specifies the byte array representation of 0xABCD0xABCE0xABCF.

    // Get the ALPN value negotiated by the TLS handshake currently
    // in progress

    String networkString = sslEngine.getHandshakeApplicationProtocol();
    
    // Encode the ALPN value into a byte array with the ISO-8859-1
    // character encoding
        
    byte[] bytes = networkString.getBytes(StandardCharsets.ISO_8859_1);
  
    String HTTP1_1 = "http/1.1";
    
    // Encode the String "http/1.1" into a byte array with the
    // UTF-8 character set
    
    byte[] HTTP1_1_BYTES = HTTP1_1.getBytes(StandardCharsets.UTF_8);
    
    // Create a byte array representing the Unicode characters 0xABCD,
    // 0xABCE, and 0xABCF, which are the Meetei Mayek letters "HUK UN I"

    byte[] HUK_UN_I_BYTES = new byte[] {
        (byte) 0xab, (byte) 0xcd,
        (byte) 0xab, (byte) 0xce,
        (byte) 0xab, (byte) 0xcf};
        
    // Test whether the APLN value is equal to "http/1.1" or
    // 0xABCD0xABCE0xABCF

    if ((Arrays.compare(bytes, HTTP1_1_BYTES) == 0 ) ||
        Arrays.compare(bytes, HUK_UN_I_BYTES) == 0) {
        // ...
    }

Alternatively, you can compare ALPN values with the method String.equals() if you know that the ALPN value was encoded from a String using a certain character set, for example UTF-8. You must decode the ALPN value to a Unicode String before comparing it.

    String unicodeString = new String(bytes, StandardCharsets.UTF_8);
    if (unicodeString.equals(HTTP1_1) ||
        unicodeString.equals("\uabcd\uabce\uabcf")) {
        // ...
    }

For the method javax.net.ssl.SSLParameters.setApplicationProtocols(String[] protocols), you must convert its String arguments to the network byte representation expected by the peer. For example, if the peer expects ALPN values in UTF-8, you must convert it to a byte array with UTF-8 and then store it as a byte-oriented String:

// Convert Meetei Mayek letters "HUK UN I" (in hexadecimal, 0xABCD0xABCE0xABCF)
// to a byte array with UTF-8
byte[] bytes = "\uabcd\uabce\uabcf".getBytes(StandardCharsets.UTF_8);

// Create a byte-oriented String with ISO-8859-1
String HUK_UN_I = new String(bytes, StandardCharsets.ISO_8859_1);

// GREASE value {0x8A, 0x8A}
String rfc7301Grease8A = "\u008A\u008A";
SSLParameters p = sslSocket.getSSLParameters();
p.setApplicationProtocols(new String[] {"h2", "http/1.1", rfc7301Grease8A, HUK_UN_I});
sslSocket.setSSLParameters(p);

At the beginning of the TLS handshake, the client sends a list of ALPN values to the server, and the server selects which values it can use and ignores those that it doesn't recognize. However, a flawed TLS implementation might instead reject unrecognized ALPN values, which may prevent the handshake from proceeding, but developers or administrators may not notice this flaw because it will still enable clients and servers whose ALPN values it recognizes to connect.

Consequently, the TLS specification has introduced Generate Random Extensions And Sustain Extensibility (GREASE) values: a reserved set of TLS protocol values that a TLS implementation may randomly advertise to ensure that peers correctly handle unrecognized values.

In the previous example, one of the values passed to the method setApplicationProtocols, rfc7301Grease8A, is a GREASE value. The peer should ignore it instead of reject it.

ALPN Related Classes and Methods

These classes and methods are used when working with Application Layer Protocol Negotiation (ALPN).

SSLEngine and SSLSocket contain the same ALPN related methods and they have the same functionality.

Class Method Purpose
SSLParameters public String[] getApplicationProtocols(); Client-side and server-side: use the method to return a String array containing each protocol set.
SSLParameters public void setApplicationProtocols([] protocols);

Client-side: use the method to set the protocols that can be chosen by the server.

Server-side: use the method to set the protocols that the server can use. The String array should contain the protocols in order of preference.

SSLEngine SSLSocket public String getApplicationProtocol(); Client-side and server-side: use the method after TLS protocol negotiation has completed to return a String containing the protocol that has been chosen for the connection.
SSLEngine SSLSocket public String getHandshakeApplicationProtocol(); Client-side and server-side: use the method during handshaking to return a String containing the protocol that has been chosen for the connection. If this method is called before or after handshaking, it will return null. See Determining Negotiated ALPN Value during Handshaking for instructions on how to call this method.
SSLEngine SSLSocket public void setHandshakeApplicationProtocolSelector(BiFunction,String> selector) Server-side: use the method to register a callback function. The application protocol value can then be set in the callback based on any information available, for example the protocol or cipher suite. See Setting up Custom ALPN on the Server for instructions on how to use this method.

Copyright © 1993, 2024, Oracle and/or its affiliates. All rights reserved.