Repository: netty/netty
Issue: Netty 4.1 should support ALPN on JDK9
Labels: feature

This is obviously a known issue, but it's worth tracking. It may also be tricky to pull this off while compiling against JDK6; in particular, invoking `SSLEngine#setHandshakeApplicationProtocolSelector` requires passing a `BiFunction` implementation. I see a few ways of accomplishing this, assuming that calling this method turns out to be necessary:

1. Take advantage of [JEP 238](http://openjdk.java.net/jeps/238) (Multi-Release JAR Files) to compile and link a small amount of code directly against JDK9
2. Use reflection to call `java.lang.invoke.MethodHandleProxies#asInterfaceInstance`
3. Use a library like ASM to manually generate a `BiFunction` implementation at runtime

Issue comments:
I forgot the most obvious solution, which is to create a `java.lang.reflect.Proxy` from a simple `InvocationHandler` that delegates to `ProtocolSelector#select`. This is much simpler than defining a class directly from an ASM-produced `byte[]`, but I don't know what the performance implications are.
@rschmitt not yet and it may take some time before I have cycles to spend here. 
@rschmitt maybe you could create a POC with a JMH benchmark?
@johnou Actually, I think I see a way to go through the `MethodHandle` API instead, which should combine the performance of the ASM approach with the encapsulation properties of the Proxy approach. (The ASM approach requires a `setAccessible(true)` call so that `defineClass` can be invoked.)
Actually, the ASM approach could work nicely on Java 9 via this public method: http://download.java.net/java/jdk9/docs/api/java/lang/invoke/MethodHandles.Lookup.html#defineClass-byte:A-
Fixed by https://github.com/netty/netty/pull/7126
@normanmaurer did you by chance run the benchmarks to compare Java 9 ALPN to the other engines? 
@johnou nope... that said I expect it to be slow still as SSL perf in JDK is still a way worse compared to netty-tcnative or conscrypt.
Hi, I have JDK 9 and took the latest sample from the developer blog. Still I get a Connection Timed Out error.  Do I have to update anything ?

Pull request review comments:
Do we care about double spaces? 
And server mode? Throw unsupported operation exception? 
Does this need to be invoked every setHandshakeSuccess? 
Yes need to fix
This way I not need any checks in SslHandler....
I guess it would be better to only call it once... let me fix
actually this is good as it is 
ObjectUtil.checkNotNull ?
nit: check that the args are the correct count and type?
should this be logged?
optional, but I would put jdk ahead of alpn.  
yep thats a good idea I think... at least with an assert...
maybe... I am not sure.. @Scottmitch ?
sure why not...
not needed as we instance if only internally and we know it can't be null.
as we cast arg[1] below I not added another assert here.
You might consider renaming selectProtocol to clientSelectProtocol, or selectProtocolIfClient or something like.
`selectProtocolIfClient` sounds good.
Include what the name was?  i.e. 

`assert "apply".equals(method.getName()); : "unexpected method " + method.getName();`
same comment as above.
out of curiosity, what exception do you expect here that you want to ignore?
in regard to the above, can you move this above the `if (JettyAlpnSslEngine.isAvailable()) {` ?  Jetty is a horrible hack and should be avoided if possible.
We actually throw an exception if we want it to fail for example:

https://github.com/netty/netty/blob/4.1/handler/src/main/java/io/netty/handler/ssl/JdkBaseApplicationProtocolNegotiator.java#L205
+1
What's the point of this? In normal usage `setApplicationProtocols` method won't throw; `SSLParameters` is independent of the provider/engine implementation.
Not a show-stopper, but...

This makes me a bit uncomfortable, because it is assuming quite a few things are working in concert[1]. For instance, this won't behave correctly if the default provider doesn't match the default SSLContext. That could be fixed by using `SSLContext.getInstance("TLS")` here instead. But it also doesn't work when explicitly providing a sslContextProvider.

The only part of this logic that is provider-specific is the `getter.invoke(engine);`. In `JdkSslContext`, could we pass the `sslContextProvider` to `JdkAlpnApplicationProtocolNegotiator`'s constructor, and plumb it into the decision-making? That would mean we would create a throw-away `SSLContext` and `SSLEngine` once per Netty-`SslContext` creation, though.

Technically, right now the provider can be different each `SSLContext/SSLEngine`, since the provider isn't fully-chosen until `JdkSslClientContext.newSSLContext()`. If `sslContextProvider == null`, it uses the default at the time of the call via `SSLContext.getInstance(PROTOCOL)`. It may be a good idea to "choose" the provider earlier, like in the constructor via `Security.getProvider("SSLContext." + PROTOCOL)`.

1. This was fairly unavoidable with Jetty ALPN, but seems avoidable (if still a PITA) with JDK 9-provided ALPN.
the point was to try to use the method via reflection to ensure nothing will blow-up on runtime 
@ejona86 the idea of this method was just to ensure all the reflective calls will work before we try to use these at runtime. I think if we change this to `SSLContext.getInstance("TLS")` this should be good enough, no ? 
@ejona86 actually I think I understand now what you are suggesting. Let me try this...
@ejona86 after thinking a bt more about it and hacking on it I think we should just use `SSLContext.getInstance("TLS")` for now and be happy.  The problem is that we still have constructors that take `JdkApplicationProtocolNegotiator` and so its hard to to make this work without any hacks. That said I deprecated these in another PR:

https://github.com/netty/netty/pull/7138
Oh, okay. FYI: setHandshakeApplicationProtocolSelector isn't tried here. That doesn't really bother me, though.
Hmm... that is even more troublesome. Yeah, swapping to `SSLContext.getInstance("TLS")` is enough at this point.
nit: this is not necessary as it it is used in `configureAlpn` before this method is called.
> 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.

Returning `null` allows the underlying layers to determine what todo. If the implementation strictly follows the RFC it should fail the handshake with a no_application_protocol [1]. However some TLS implementations (e.g. OpenSSL) have not implemented support for this alert until recently and would no advertise support for ALPN instead of failing the handshake. Throwing a `SSLHandshakeException` ensured that we failed the handshake (maybe not with the correct alert because it wasn't exposed at the time). That is the purpose of `SelectorFailureBehavior` and `SelectedListenerFailureBehavior` to allow us to have control over this at the application level since lower levels allowed us more freedom.

[1] https://tools.ietf.org/rfc/rfc7301.txt
nit: kill newline?
should we try to use http://download.java.net/java/jdk9/docs/api/javax/net/ssl/SSLEngine.html#getApplicationProtocol-- here?
discussed with @normanmaurer offline. This may lead to treating the SslSession object differently in JDK9 and lower ... can we avoid this?
If `Java9SslUtils.getApplicationProtocol(getWrappedEngine())` returns `null` (and maybe empty) (assuming the handshake is done) shouldn't we call [ProtocolSelectionListener#unsupported](https://github.com/netty/netty/blob/4.1/handler/src/main/java/io/netty/handler/ssl/JdkApplicationProtocolNegotiator.java#L104)?

http://download.java.net/java/jdk9/docs/api/javax/net/ssl/SSLEngine.html#getApplicationProtocol--
> Returns:
null if it has not yet been determined if application protocols might be used for this connection, an empty String if application protocols values will not be used, or a non-empty application protocol String if a value was successfully negotiated.
under what conditions can we invoke `ProtocolSelector#unsupported()`? Will this callback be called if the peer doesn't advertise ALPN, and if so what will the arguments be? Consider adding a comment.
+1
![MAJOR](https://raw.githubusercontent.com/SonarCommunity/sonar-github/master/images/severity-major.png) The Cyclomatic Complexity of this class is 520 which is greater than 300 authorized. [![rule](https://raw.githubusercontent.com/SonarCommunity/sonar-github/master/images/rule.png)](https://garage.netty.io/sonarqube/coding_rules#rule_key=squid%3AClassCyclomaticComplexity)

nit: kill extra line
nit: `log` -> `logger` for consistency?
can we do this outside the scope of the constructor to avoid allowing `this` to escape? maybe in `wrapEngine` or create a static factory method in here?
nit: consider adding a static import for `ProtocolSelector` to help with formatting.
nit: consider adding a static import for `ProtocolSelectionListener` to help with formatting.
https://github.com/netty/netty/pull/7126#discussion_r134666394

> under what conditions can we invoke `ProtocolSelector#unsupported()`? Will this BiFunction be called if the peer doesn't advertise ALPN, and if so what will the arguments be? Consider adding a comment.
nit: consider reformatting:

```java
    private static final boolean AVAILABLE = Conscrypt.isAvailable() ||
                                             jdkAlpnSupported() ||
                                             JettyAlpnSslEngine.isAvailable();
```
can `applicationProtocol` be volatile instead of requiring synchronization?
it would be nice not to have to call this on every wrap/unwrap. It looks like this is not necessary on the server. Is it worth having a `Java9ServerSslEngine` and `Java9ClientSslEngine` ... and the `Java9ServerSslEngine` wouldn't have todo this at all? `Java9ClientSslEngine ` wouldn't need to have `setHandshakeApplicationProtocolSelector` or `getHandshakeApplicationProtocolSelector` either.
Good idea
I can do this that said we still need the set and get as this must delegate to the wrapped engine to be able to delegate all methods of the java9 SSLEngine 
Yep I think this should be fine 
Let me find out...
after thinking more about this I think it is fine to just keep one implementation and just reverse the checks as the JIT should be smart enough to know it will never match when the engine is server side. if it really shows up to be an issue we can do a followup.
done
> as the JIT should be smart enough to know it will never match when the engine is server side.

This would be an interesting optimization ... conditional code elimination based upon the usage of the class. Are you assuming the JIT would effectively? create the two different classes behind the scenes?
basically I think the JIT should figure out that a boolean will always match or not match and if not just optimise the branch away ;)

That said after addressing your comment I need extra code for server and client side anyway.
this call can be moved into the conditional below
nit: fix argument alignment
nit: fix argument alignment
![MINOR](https://raw.githubusercontent.com/SonarCommunity/sonar-github/master/images/severity-minor.png) Consider using varargs for methods or constructors which take an array the last parameter. [![rule](https://raw.githubusercontent.com/SonarCommunity/sonar-github/master/images/rule.png)](https://garage.netty.io/sonarqube/coding_rules#rule_key=pmd%3AUseVarargs)


Patch that resolved the issue:
diff --git a/handler/src/main/java/io/netty/handler/ssl/Java9SslEngine.java b/handler/src/main/java/io/netty/handler/ssl/Java9SslEngine.java
new file mode 100644
index 00000000000..6534f39b503
--- /dev/null
+++ b/handler/src/main/java/io/netty/handler/ssl/Java9SslEngine.java
@@ -0,0 +1,175 @@
+/*
+ * Copyright 2017 The Netty Project
+ *
+ * The Netty Project licenses this file to you under the Apache License,
+ * version 2.0 (the "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at:
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+package io.netty.handler.ssl;
+
+import io.netty.util.internal.StringUtil;
+
+import javax.net.ssl.SSLEngine;
+import javax.net.ssl.SSLEngineResult;
+import javax.net.ssl.SSLException;
+
+import java.nio.ByteBuffer;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.function.BiFunction;
+
+import static io.netty.handler.ssl.SslUtils.toSSLHandshakeException;
+import static io.netty.handler.ssl.JdkApplicationProtocolNegotiator.ProtocolSelectionListener;
+import static io.netty.handler.ssl.JdkApplicationProtocolNegotiator.ProtocolSelector;
+
+final class Java9SslEngine extends JdkSslEngine {
+    private final ProtocolSelectionListener selectionListener;
+    private final AlpnSelector alpnSelector;
+
+    private final class AlpnSelector implements BiFunction<SSLEngine, List<String>, String> {
+        private final ProtocolSelector selector;
+        private boolean called;
+
+        AlpnSelector(ProtocolSelector selector) {
+            this.selector = selector;
+        }
+
+        @Override
+        public String apply(SSLEngine sslEngine, List<String> strings) {
+            assert !called;
+            called = true;
+
+            try {
+                String selected = selector.select(strings);
+                return selected == null ? StringUtil.EMPTY_STRING : selected;
+            } catch (Exception cause) {
+                // Returning null means we want to fail the handshake.
+                //
+                // See http://download.java.net/java/jdk9/docs/api/javax/net/ssl/
+                // SSLEngine.html#setHandshakeApplicationProtocolSelector-java.util.function.BiFunction-
+                return null;
+            }
+        }
+
+        void checkUnsupported() {
+            if (called) {
+                // ALPN message was received by peer and so apply(...) was called.
+                // See:
+                // http://hg.openjdk.java.net/jdk9/dev/jdk/file/65464a307408/src/
+                // java.base/share/classes/sun/security/ssl/ServerHandshaker.java#l933
+                return;
+            }
+            String protocol = getApplicationProtocol();
+            assert protocol != null;
+
+            if (protocol.isEmpty()) {
+                // ALPN is not supported
+                selector.unsupported();
+            }
+        }
+    }
+
+    Java9SslEngine(SSLEngine engine, JdkApplicationProtocolNegotiator applicationNegotiator, boolean isServer) {
+        super(engine);
+        if (isServer) {
+            selectionListener = null;
+            alpnSelector = new AlpnSelector(applicationNegotiator.protocolSelectorFactory().
+                    newSelector(this, new LinkedHashSet<String>(applicationNegotiator.protocols())));
+            Java9SslUtils.setHandshakeApplicationProtocolSelector(engine, alpnSelector);
+        } else {
+            selectionListener = applicationNegotiator.protocolListenerFactory()
+                    .newListener(this, applicationNegotiator.protocols());
+            alpnSelector = null;
+            Java9SslUtils.setApplicationProtocols(engine, applicationNegotiator.protocols());
+        }
+    }
+
+    private SSLEngineResult verifyProtocolSelection(SSLEngineResult result) throws SSLException {
+        if (result.getHandshakeStatus() == SSLEngineResult.HandshakeStatus.FINISHED) {
+            if (alpnSelector == null) {
+                // This means we are using client-side and
+                try {
+                    String protocol = getApplicationProtocol();
+                    assert protocol != null;
+                    if (protocol.isEmpty()) {
+                        // If empty the server did not announce ALPN:
+                        // See:
+                        // http://hg.openjdk.java.net/jdk9/dev/jdk/file/65464a307408/src/java.base/
+                        // share/classes/sun/security/ssl/ClientHandshaker.java#l741
+                        selectionListener.unsupported();
+                    } else {
+                        selectionListener.selected(protocol);
+                    }
+                } catch (Throwable e) {
+                    throw toSSLHandshakeException(e);
+                }
+            } else {
+                assert selectionListener == null;
+                alpnSelector.checkUnsupported();
+            }
+        }
+        return result;
+    }
+
+    @Override
+    public SSLEngineResult wrap(ByteBuffer src, ByteBuffer dst) throws SSLException {
+        return verifyProtocolSelection(super.wrap(src, dst));
+    }
+
+    @Override
+    public SSLEngineResult wrap(ByteBuffer[] srcs, ByteBuffer dst) throws SSLException {
+        return verifyProtocolSelection(super.wrap(srcs, dst));
+    }
+
+    @Override
+    public SSLEngineResult wrap(ByteBuffer[] srcs, int offset, int len, ByteBuffer dst) throws SSLException {
+        return verifyProtocolSelection(super.wrap(srcs, offset, len, dst));
+    }
+
+    @Override
+    public SSLEngineResult unwrap(ByteBuffer src, ByteBuffer dst) throws SSLException {
+        return verifyProtocolSelection(super.unwrap(src, dst));
+    }
+
+    @Override
+    public SSLEngineResult unwrap(ByteBuffer src, ByteBuffer[] dsts) throws SSLException {
+        return verifyProtocolSelection(super.unwrap(src, dsts));
+    }
+
+    @Override
+    public SSLEngineResult unwrap(ByteBuffer src, ByteBuffer[] dst, int offset, int len) throws SSLException {
+        return verifyProtocolSelection(super.unwrap(src, dst, offset, len));
+    }
+
+    @Override
+    void setApplicationProtocol(String applicationProtocol) {
+        // Do nothing as this is handled internally by the Java9 implementation of SSLEngine.
+    }
+
+    @Override
+    public String getApplicationProtocol() {
+        return Java9SslUtils.getApplicationProtocol(getWrappedEngine());
+    }
+
+    // These methods will override the methods defined by Java 9. As we compile with Java8 we can not add
+    // @Override annotations here.
+    public String getHandshakeApplicationProtocol() {
+        return Java9SslUtils.getHandshakeApplicationProtocol(getWrappedEngine());
+    }
+
+    public void setHandshakeApplicationProtocolSelector(BiFunction<SSLEngine, List<String>, String> selector) {
+        Java9SslUtils.setHandshakeApplicationProtocolSelector(getWrappedEngine(), selector);
+    }
+
+    public BiFunction<SSLEngine, List<String>, String> getHandshakeApplicationProtocolSelector() {
+        return Java9SslUtils.getHandshakeApplicationProtocolSelector(getWrappedEngine());
+    }
+}
diff --git a/handler/src/main/java/io/netty/handler/ssl/Java9SslUtils.java b/handler/src/main/java/io/netty/handler/ssl/Java9SslUtils.java
new file mode 100644
index 00000000000..2fd3bbcd64b
--- /dev/null
+++ b/handler/src/main/java/io/netty/handler/ssl/Java9SslUtils.java
@@ -0,0 +1,175 @@
+/*
+ * Copyright 2017 The Netty Project
+ *
+ * The Netty Project licenses this file to you under the Apache License,
+ * version 2.0 (the "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at:
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+package io.netty.handler.ssl;
+
+
+import javax.net.ssl.SSLContext;
+import javax.net.ssl.SSLEngine;
+import javax.net.ssl.SSLParameters;
+import java.lang.reflect.Method;
+import java.security.AccessController;
+import java.security.PrivilegedExceptionAction;
+import java.util.List;
+import java.util.function.BiFunction;
+
+import io.netty.util.internal.EmptyArrays;
+import io.netty.util.internal.PlatformDependent;
+import io.netty.util.internal.logging.InternalLogger;
+import io.netty.util.internal.logging.InternalLoggerFactory;
+
+final class Java9SslUtils {
+    private static final InternalLogger logger = InternalLoggerFactory.getInstance(Java9SslUtils.class);
+    private static final Method SET_APPLICATION_PROTOCOLS;
+    private static final Method GET_APPLICATION_PROTOCOL;
+    private static final Method GET_HANDSHAKE_APPLICATION_PROTOCOL;
+    private static final Method SET_HANDSHAKE_APPLICATION_PROTOCOL_SELECTOR;
+    private static final Method GET_HANDSHAKE_APPLICATION_PROTOCOL_SELECTOR;
+
+    static {
+        Method getHandshakeApplicationProtocol = null;
+        Method getApplicationProtocol = null;
+        Method setApplicationProtocols = null;
+        Method setHandshakeApplicationProtocolSelector = null;
+        Method getHandshakeApplicationProtocolSelector = null;
+
+        try {
+            SSLContext context = SSLContext.getInstance(JdkSslContext.PROTOCOL);
+            context.init(null, null, null);
+            SSLEngine engine = context.createSSLEngine();
+            getHandshakeApplicationProtocol = AccessController.doPrivileged(new PrivilegedExceptionAction<Method>() {
+                @Override
+                public Method run() throws Exception {
+                    return SSLEngine.class.getMethod("getHandshakeApplicationProtocol");
+                }
+            });
+            getHandshakeApplicationProtocol.invoke(engine);
+            getApplicationProtocol = AccessController.doPrivileged(new PrivilegedExceptionAction<Method>() {
+                @Override
+                public Method run() throws Exception {
+                    return SSLEngine.class.getMethod("getApplicationProtocol");
+                }
+            });
+            getApplicationProtocol.invoke(engine);
+
+            setApplicationProtocols = AccessController.doPrivileged(new PrivilegedExceptionAction<Method>() {
+                @Override
+                public Method run() throws Exception {
+                    return SSLParameters.class.getMethod("setApplicationProtocols", String[].class);
+                }
+            });
+            setApplicationProtocols.invoke(engine.getSSLParameters(), new Object[]{EmptyArrays.EMPTY_STRINGS});
+
+            setHandshakeApplicationProtocolSelector =
+                    AccessController.doPrivileged(new PrivilegedExceptionAction<Method>() {
+                @Override
+                public Method run() throws Exception {
+                    return SSLEngine.class.getMethod("setHandshakeApplicationProtocolSelector", BiFunction.class);
+                }
+            });
+            setHandshakeApplicationProtocolSelector.invoke(engine, new BiFunction<SSLEngine, List<String>, String>() {
+                @Override
+                public String apply(SSLEngine sslEngine, List<String> strings) {
+                    return null;
+                }
+            });
+
+            getHandshakeApplicationProtocolSelector =
+                    AccessController.doPrivileged(new PrivilegedExceptionAction<Method>() {
+                @Override
+                public Method run() throws Exception {
+                    return SSLEngine.class.getMethod("getHandshakeApplicationProtocolSelector");
+                }
+            });
+            getHandshakeApplicationProtocolSelector.invoke(engine);
+        } catch (Throwable t) {
+            logger.error("Unable to initialize Java9SslUtils, but the detected javaVersion was: {}",
+                    PlatformDependent.javaVersion(), t);
+            getHandshakeApplicationProtocol = null;
+            getApplicationProtocol = null;
+            setApplicationProtocols = null;
+            setHandshakeApplicationProtocolSelector = null;
+            getHandshakeApplicationProtocolSelector = null;
+        }
+        GET_HANDSHAKE_APPLICATION_PROTOCOL = getHandshakeApplicationProtocol;
+        GET_APPLICATION_PROTOCOL = getApplicationProtocol;
+        SET_APPLICATION_PROTOCOLS = setApplicationProtocols;
+        SET_HANDSHAKE_APPLICATION_PROTOCOL_SELECTOR = setHandshakeApplicationProtocolSelector;
+        GET_HANDSHAKE_APPLICATION_PROTOCOL_SELECTOR = getHandshakeApplicationProtocolSelector;
+    }
+
+    private Java9SslUtils() {
+    }
+
+    static boolean supportsAlpn() {
+        return GET_APPLICATION_PROTOCOL != null;
+    }
+
+    static String getApplicationProtocol(SSLEngine sslEngine) {
+        try {
+            return (String) GET_APPLICATION_PROTOCOL.invoke(sslEngine);
+        } catch (UnsupportedOperationException ex) {
+            throw ex;
+        } catch (Exception ex) {
+            throw new IllegalStateException(ex);
+        }
+    }
+
+    static String getHandshakeApplicationProtocol(SSLEngine sslEngine) {
+        try {
+            return (String) GET_HANDSHAKE_APPLICATION_PROTOCOL.invoke(sslEngine);
+        } catch (UnsupportedOperationException ex) {
+            throw ex;
+        } catch (Exception ex) {
+            throw new IllegalStateException(ex);
+        }
+    }
+
+    static void setApplicationProtocols(SSLEngine engine, List<String> supportedProtocols) {
+        SSLParameters parameters = engine.getSSLParameters();
+
+        String[] protocolArray = supportedProtocols.toArray(EmptyArrays.EMPTY_STRINGS);
+        try {
+            SET_APPLICATION_PROTOCOLS.invoke(parameters, new Object[]{protocolArray});
+        } catch (UnsupportedOperationException ex) {
+            throw ex;
+        } catch (Exception ex) {
+            throw new IllegalStateException(ex);
+        }
+        engine.setSSLParameters(parameters);
+    }
+
+    static void setHandshakeApplicationProtocolSelector(
+            SSLEngine engine, BiFunction<SSLEngine, List<String>, String> selector) {
+        try {
+            SET_HANDSHAKE_APPLICATION_PROTOCOL_SELECTOR.invoke(engine, selector);
+        } catch (UnsupportedOperationException ex) {
+            throw ex;
+        } catch (Exception ex) {
+            throw new IllegalStateException(ex);
+        }
+    }
+
+    static BiFunction<SSLEngine, List<String>, String> getHandshakeApplicationProtocolSelector(SSLEngine engine) {
+        try {
+            return (BiFunction<SSLEngine, List<String>, String>)
+                    GET_HANDSHAKE_APPLICATION_PROTOCOL_SELECTOR.invoke(engine);
+        } catch (UnsupportedOperationException ex) {
+            throw ex;
+        } catch (Exception ex) {
+            throw new IllegalStateException(ex);
+        }
+    }
+}
diff --git a/handler/src/main/java/io/netty/handler/ssl/JdkAlpnApplicationProtocolNegotiator.java b/handler/src/main/java/io/netty/handler/ssl/JdkAlpnApplicationProtocolNegotiator.java
index cd3fe15051d..8d2c4109df1 100644
--- a/handler/src/main/java/io/netty/handler/ssl/JdkAlpnApplicationProtocolNegotiator.java
+++ b/handler/src/main/java/io/netty/handler/ssl/JdkAlpnApplicationProtocolNegotiator.java
@@ -16,13 +16,18 @@
 package io.netty.handler.ssl;
 
 import io.netty.buffer.ByteBufAllocator;
+import io.netty.util.internal.PlatformDependent;
+
 import javax.net.ssl.SSLEngine;
 
 /**
  * The {@link JdkApplicationProtocolNegotiator} to use if you need ALPN and are using {@link SslProvider#JDK}.
  */
 public final class JdkAlpnApplicationProtocolNegotiator extends JdkBaseApplicationProtocolNegotiator {
-    private static final boolean AVAILABLE = Conscrypt.isAvailable() || JettyAlpnSslEngine.isAvailable();
+    private static final boolean AVAILABLE = Conscrypt.isAvailable() ||
+                                             jdkAlpnSupported() ||
+                                             JettyAlpnSslEngine.isAvailable();
+
     private static final SslEngineWrapperFactory ALPN_WRAPPER = AVAILABLE ? new AlpnWrapper() : new FailureWrapper();
 
     /**
@@ -126,6 +131,9 @@ public SSLEngine wrapSslEngine(SSLEngine engine, ByteBufAllocator alloc,
                 return isServer ? ConscryptAlpnSslEngine.newServerEngine(engine, alloc, applicationNegotiator)
                         : ConscryptAlpnSslEngine.newClientEngine(engine, alloc, applicationNegotiator);
             }
+            if (jdkAlpnSupported()) {
+                return new Java9SslEngine(engine, applicationNegotiator, isServer);
+            }
             if (JettyAlpnSslEngine.isAvailable()) {
                 return isServer ? JettyAlpnSslEngine.newServerEngine(engine, applicationNegotiator)
                         : JettyAlpnSslEngine.newClientEngine(engine, applicationNegotiator);
@@ -133,4 +141,8 @@ public SSLEngine wrapSslEngine(SSLEngine engine, ByteBufAllocator alloc,
             throw new RuntimeException("Unable to wrap SSLEngine of type " + engine.getClass().getName());
         }
     }
+
+    static boolean jdkAlpnSupported() {
+        return PlatformDependent.javaVersion() >= 9 && Java9SslUtils.supportsAlpn();
+    }
 }
diff --git a/handler/src/main/java/io/netty/handler/ssl/JdkBaseApplicationProtocolNegotiator.java b/handler/src/main/java/io/netty/handler/ssl/JdkBaseApplicationProtocolNegotiator.java
index fc326f6488a..8e20c9b1b1c 100644
--- a/handler/src/main/java/io/netty/handler/ssl/JdkBaseApplicationProtocolNegotiator.java
+++ b/handler/src/main/java/io/netty/handler/ssl/JdkBaseApplicationProtocolNegotiator.java
@@ -137,14 +137,14 @@ static class NoFailProtocolSelector implements ProtocolSelector {
 
         @Override
         public void unsupported() {
-            engineWrapper.getSession().setApplicationProtocol(null);
+            engineWrapper.setApplicationProtocol(null);
         }
 
         @Override
         public String select(List<String> protocols) throws Exception {
             for (String p : supportedProtocols) {
                 if (protocols.contains(p)) {
-                    engineWrapper.getSession().setApplicationProtocol(p);
+                    engineWrapper.setApplicationProtocol(p);
                     return p;
                 }
             }
@@ -152,7 +152,7 @@ public String select(List<String> protocols) throws Exception {
         }
 
         public String noSelectMatchFound() throws Exception {
-            engineWrapper.getSession().setApplicationProtocol(null);
+            engineWrapper.setApplicationProtocol(null);
             return null;
         }
     }
@@ -179,13 +179,13 @@ private static class NoFailProtocolSelectionListener implements ProtocolSelectio
 
         @Override
         public void unsupported() {
-            engineWrapper.getSession().setApplicationProtocol(null);
+            engineWrapper.setApplicationProtocol(null);
         }
 
         @Override
         public void selected(String protocol) throws Exception {
             if (supportedProtocols.contains(protocol)) {
-                engineWrapper.getSession().setApplicationProtocol(protocol);
+                engineWrapper.setApplicationProtocol(protocol);
             } else {
                 noSelectedMatchFound(protocol);
             }
diff --git a/handler/src/main/java/io/netty/handler/ssl/JdkSslEngine.java b/handler/src/main/java/io/netty/handler/ssl/JdkSslEngine.java
index 680eea6195c..1cc3bdfa32a 100644
--- a/handler/src/main/java/io/netty/handler/ssl/JdkSslEngine.java
+++ b/handler/src/main/java/io/netty/handler/ssl/JdkSslEngine.java
@@ -24,18 +24,26 @@
 import javax.net.ssl.SSLParameters;
 import javax.net.ssl.SSLSession;
 
-class JdkSslEngine extends SSLEngine {
+class JdkSslEngine extends SSLEngine implements ApplicationProtocolAccessor {
     private final SSLEngine engine;
-    private final JdkSslSession session;
+    private volatile String applicationProtocol;
 
     JdkSslEngine(SSLEngine engine) {
         this.engine = engine;
-        session = new JdkSslSession(engine);
     }
 
     @Override
-    public JdkSslSession getSession() {
-        return session;
+    public String getApplicationProtocol() {
+        return applicationProtocol;
+    }
+
+    void setApplicationProtocol(String applicationProtocol) {
+        this.applicationProtocol = applicationProtocol;
+    }
+
+    @Override
+    public SSLSession getSession() {
+        return engine.getSession();
     }
 
     public SSLEngine getWrappedEngine() {
diff --git a/handler/src/main/java/io/netty/handler/ssl/JdkSslSession.java b/handler/src/main/java/io/netty/handler/ssl/JdkSslSession.java
deleted file mode 100644
index 183c2966327..00000000000
--- a/handler/src/main/java/io/netty/handler/ssl/JdkSslSession.java
+++ /dev/null
@@ -1,152 +0,0 @@
-/*
- * Copyright 2014 The Netty Project
- *
- * The Netty Project licenses this file to you under the Apache License,
- * version 2.0 (the "License"); you may not use this file except in compliance
- * with the License. You may obtain a copy of the License at:
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
- * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
- * License for the specific language governing permissions and limitations
- * under the License.
- */
-
-package io.netty.handler.ssl;
-
-import javax.net.ssl.SSLEngine;
-import javax.net.ssl.SSLPeerUnverifiedException;
-import javax.net.ssl.SSLSession;
-import javax.net.ssl.SSLSessionContext;
-import javax.security.cert.X509Certificate;
-import java.security.Principal;
-import java.security.cert.Certificate;
-
-final class JdkSslSession implements SSLSession, ApplicationProtocolAccessor {
-    private final SSLEngine engine;
-    private volatile String applicationProtocol;
-
-    JdkSslSession(SSLEngine engine) {
-        this.engine = engine;
-    }
-
-    private SSLSession unwrap() {
-        return engine.getSession();
-    }
-
-    @Override
-    public String getProtocol() {
-        return unwrap().getProtocol();
-    }
-
-    @Override
-    public String getApplicationProtocol() {
-        return applicationProtocol;
-    }
-
-    void setApplicationProtocol(String applicationProtocol) {
-        this.applicationProtocol = applicationProtocol;
-    }
-
-    @Override
-    public byte[] getId() {
-        return unwrap().getId();
-    }
-
-    @Override
-    public SSLSessionContext getSessionContext() {
-        return unwrap().getSessionContext();
-    }
-
-    @Override
-    public long getCreationTime() {
-        return unwrap().getCreationTime();
-    }
-
-    @Override
-    public long getLastAccessedTime() {
-        return unwrap().getLastAccessedTime();
-    }
-
-    @Override
-    public void invalidate() {
-        unwrap().invalidate();
-    }
-
-    @Override
-    public boolean isValid() {
-        return unwrap().isValid();
-    }
-
-    @Override
-    public void putValue(String s, Object o) {
-        unwrap().putValue(s, o);
-    }
-
-    @Override
-    public Object getValue(String s) {
-        return unwrap().getValue(s);
-    }
-
-    @Override
-    public void removeValue(String s) {
-        unwrap().removeValue(s);
-    }
-
-    @Override
-    public String[] getValueNames() {
-        return unwrap().getValueNames();
-    }
-
-    @Override
-    public Certificate[] getPeerCertificates() throws SSLPeerUnverifiedException {
-        return unwrap().getPeerCertificates();
-    }
-
-    @Override
-    public Certificate[] getLocalCertificates() {
-        return unwrap().getLocalCertificates();
-    }
-
-    @Override
-    public X509Certificate[] getPeerCertificateChain() throws SSLPeerUnverifiedException {
-        return unwrap().getPeerCertificateChain();
-    }
-
-    @Override
-    public Principal getPeerPrincipal() throws SSLPeerUnverifiedException {
-        return unwrap().getPeerPrincipal();
-    }
-
-    @Override
-    public Principal getLocalPrincipal() {
-        return unwrap().getLocalPrincipal();
-    }
-
-    @Override
-    public String getCipherSuite() {
-        return unwrap().getCipherSuite();
-    }
-
-    @Override
-    public String getPeerHost() {
-        return unwrap().getPeerHost();
-    }
-
-    @Override
-    public int getPeerPort() {
-        return unwrap().getPeerPort();
-    }
-
-    @Override
-    public int getPacketBufferSize() {
-        return unwrap().getPacketBufferSize();
-    }
-
-    @Override
-    public int getApplicationBufferSize() {
-        return unwrap().getApplicationBufferSize();
-    }
-}
diff --git a/handler/src/main/java/io/netty/handler/ssl/JettyAlpnSslEngine.java b/handler/src/main/java/io/netty/handler/ssl/JettyAlpnSslEngine.java
index 624719a5be9..ce9f14a57e7 100644
--- a/handler/src/main/java/io/netty/handler/ssl/JettyAlpnSslEngine.java
+++ b/handler/src/main/java/io/netty/handler/ssl/JettyAlpnSslEngine.java
@@ -38,7 +38,6 @@ static boolean isAvailable() {
     }
 
     private static boolean initAvailable() {
-        // TODO: Add support for ALPN when using Java9 and still be able to configure it the Netty way.
         if (PlatformDependent.javaVersion() <= 8) {
             try {
                 // Always use bootstrap class loader.
diff --git a/handler/src/main/java/io/netty/handler/ssl/ReferenceCountedOpenSslEngine.java b/handler/src/main/java/io/netty/handler/ssl/ReferenceCountedOpenSslEngine.java
index 9878a87d43d..2670e53b23d 100644
--- a/handler/src/main/java/io/netty/handler/ssl/ReferenceCountedOpenSslEngine.java
+++ b/handler/src/main/java/io/netty/handler/ssl/ReferenceCountedOpenSslEngine.java
@@ -87,7 +87,7 @@
  * the instance depends upon are released. Otherwise if any method of this class is called which uses the
  * the {@link ReferenceCountedOpenSslContext} JNI resources the JVM may crash.
  */
-public class ReferenceCountedOpenSslEngine extends SSLEngine implements ReferenceCounted {
+public class ReferenceCountedOpenSslEngine extends SSLEngine implements ReferenceCounted, ApplicationProtocolAccessor {
 
     private static final InternalLogger logger = InternalLoggerFactory.getInstance(ReferenceCountedOpenSslEngine.class);
 
@@ -159,6 +159,7 @@ private enum HandshakeState {
     private boolean renegotiationPending;
     private boolean receivedShutdown;
     private volatile int destroyed;
+    private volatile String applicationProtocol;
 
     // Reference Counting
     private final ResourceLeakTracker<ReferenceCountedOpenSslEngine> leak;
@@ -1827,7 +1828,12 @@ final boolean checkSniHostnameMatch(String hostname) {
         return Java8SslUtils.checkSniHostnameMatch(matchers, hostname);
     }
 
-    private final class OpenSslSession implements SSLSession, ApplicationProtocolAccessor {
+    @Override
+    public String getApplicationProtocol() {
+        return applicationProtocol;
+    }
+
+    private final class OpenSslSession implements SSLSession {
         private final OpenSslSessionContext sessionContext;
 
         // These are guarded by synchronized(OpenSslEngine.this) as handshakeFinished() may be triggered by any
@@ -1835,7 +1841,6 @@ private final class OpenSslSession implements SSLSession, ApplicationProtocolAcc
         private X509Certificate[] x509PeerCerts;
         private Certificate[] peerCerts;
         private String protocol;
-        private String applicationProtocol;
         private String cipher;
         private byte[] id;
         private long creationTime;
@@ -2043,14 +2048,14 @@ private void selectApplicationProtocol() throws SSLException {
                 case ALPN:
                     applicationProtocol = SSL.getAlpnSelected(ssl);
                     if (applicationProtocol != null) {
-                        this.applicationProtocol = selectApplicationProtocol(
+                        ReferenceCountedOpenSslEngine.this.applicationProtocol = selectApplicationProtocol(
                                 protocols, behavior, applicationProtocol);
                     }
                     break;
                 case NPN:
                     applicationProtocol = SSL.getNextProtoNegotiated(ssl);
                     if (applicationProtocol != null) {
-                        this.applicationProtocol = selectApplicationProtocol(
+                        ReferenceCountedOpenSslEngine.this.applicationProtocol = selectApplicationProtocol(
                                 protocols, behavior, applicationProtocol);
                     }
                     break;
@@ -2060,7 +2065,7 @@ private void selectApplicationProtocol() throws SSLException {
                         applicationProtocol = SSL.getNextProtoNegotiated(ssl);
                     }
                     if (applicationProtocol != null) {
-                        this.applicationProtocol = selectApplicationProtocol(
+                        ReferenceCountedOpenSslEngine.this.applicationProtocol = selectApplicationProtocol(
                                 protocols, behavior, applicationProtocol);
                     }
                     break;
@@ -2159,13 +2164,6 @@ public String getProtocol() {
             return protocol;
         }
 
-        @Override
-        public String getApplicationProtocol() {
-            synchronized (ReferenceCountedOpenSslEngine.this) {
-                return applicationProtocol;
-            }
-        }
-
         @Override
         public String getPeerHost() {
             return ReferenceCountedOpenSslEngine.this.getPeerHost();
diff --git a/handler/src/main/java/io/netty/handler/ssl/SslHandler.java b/handler/src/main/java/io/netty/handler/ssl/SslHandler.java
index b03417d15e3..3724b772719 100644
--- a/handler/src/main/java/io/netty/handler/ssl/SslHandler.java
+++ b/handler/src/main/java/io/netty/handler/ssl/SslHandler.java
@@ -573,12 +573,12 @@ public SSLEngine engine() {
      * @return the protocol name or {@code null} if application-level protocol has not been negotiated
      */
     public String applicationProtocol() {
-        SSLSession sess = engine().getSession();
-        if (!(sess instanceof ApplicationProtocolAccessor)) {
+        SSLEngine engine = engine();
+        if (!(engine instanceof ApplicationProtocolAccessor)) {
             return null;
         }
 
-        return ((ApplicationProtocolAccessor) sess).getApplicationProtocol();
+        return ((ApplicationProtocolAccessor) engine).getApplicationProtocol();
     }
 
     /**
diff --git a/pom.xml b/pom.xml
index ba17e15111b..84c370792c5 100644
--- a/pom.xml
+++ b/pom.xml
@@ -710,6 +710,7 @@
 
             <!-- JDK 8 -->
             <ignore>java.util.concurrent.atomic.LongAdder</ignore>
+            <ignore>java.util.function.BiFunction</ignore>
 
             <!-- Resolver -->
             <ignore>java.net.InetAddress</ignore>


Test patch:
diff --git a/handler/src/test/java/io/netty/handler/ssl/JdkSslEngineTest.java b/handler/src/test/java/io/netty/handler/ssl/JdkSslEngineTest.java
index c1205130fc5..110871b3603 100644
--- a/handler/src/test/java/io/netty/handler/ssl/JdkSslEngineTest.java
+++ b/handler/src/test/java/io/netty/handler/ssl/JdkSslEngineTest.java
@@ -25,6 +25,8 @@
 import java.security.Provider;
 import java.util.ArrayList;
 import java.util.Collection;
+
+import io.netty.util.internal.PlatformDependent;
 import org.junit.Ignore;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -43,7 +45,7 @@
 @RunWith(Parameterized.class)
 public class JdkSslEngineTest extends SSLEngineTest {
     public enum ProviderType {
-        NPN_DEFAULT {
+        NPN_JETTY {
             @Override
             boolean isAvailable() {
                 return JettyNpnSslEngine.isAvailable();
@@ -59,7 +61,7 @@ Provider provider() {
                 return null;
             }
         },
-        ALPN_DEFAULT {
+        ALPN_JETTY {
             @Override
             boolean isAvailable() {
                 return JettyAlpnSslEngine.isAvailable();
@@ -76,6 +78,23 @@ Provider provider() {
                 return null;
             }
         },
+        ALPN_JAVA9 {
+            @Override
+            boolean isAvailable() {
+                return PlatformDependent.javaVersion() >= 9 && Java9SslUtils.supportsAlpn();
+            }
+
+            @Override
+            Protocol protocol() {
+                return Protocol.ALPN;
+            }
+
+            @Override
+            Provider provider() {
+                // Use the default provider.
+                return null;
+            }
+        },
         ALPN_CONSCRYPT {
             private Provider provider;
 
@@ -178,7 +197,7 @@ public void testTlsExtensionNoCompatibleProtocolsNoHandshakeFailure() throws Exc
     public void testTlsExtensionNoCompatibleProtocolsClientHandshakeFailure() throws Exception {
         try {
             providerType.activate(this);
-            if (providerType == ProviderType.NPN_DEFAULT) {
+            if (providerType == ProviderType.NPN_JETTY) {
                 ApplicationProtocolConfig clientApn = failingNegotiator(providerType.protocol(),
                     PREFERRED_APPLICATION_LEVEL_PROTOCOL);
                 ApplicationProtocolConfig serverApn = acceptingNegotiator(providerType.protocol(),
@@ -249,7 +268,7 @@ public void testTlsExtensionNoCompatibleProtocolsServerHandshakeFailure() throws
     public void testAlpnCompatibleProtocolsDifferentClientOrder() throws Exception {
         try {
             providerType.activate(this);
-            if (providerType == ProviderType.NPN_DEFAULT) {
+            if (providerType == ProviderType.NPN_JETTY) {
                 // This test only applies to ALPN.
                 throw tlsExtensionNotFound(providerType.protocol());
             }
@@ -314,16 +333,14 @@ protected Provider serverSslContextProvider() {
         return provider;
     }
 
-    private ApplicationProtocolConfig failingNegotiator(Protocol protocol,
-                                                        String... supportedProtocols) {
+    private static ApplicationProtocolConfig failingNegotiator(Protocol protocol, String... supportedProtocols) {
         return new ApplicationProtocolConfig(protocol,
                 SelectorFailureBehavior.FATAL_ALERT,
                 SelectedListenerFailureBehavior.FATAL_ALERT,
                 supportedProtocols);
     }
 
-    private ApplicationProtocolConfig acceptingNegotiator(Protocol protocol,
-                                                          String... supportedProtocols) {
+    private static ApplicationProtocolConfig acceptingNegotiator(Protocol protocol, String... supportedProtocols) {
         return new ApplicationProtocolConfig(protocol,
                 SelectorFailureBehavior.NO_ADVERTISE,
                 SelectedListenerFailureBehavior.ACCEPT,