Affects: 5.3.23

I have a working websocket configured and running on Spring 5.3.23 with Jetty 9.4.48.v20220622. When I attempt to upgrade to Jetty 10.0.12, the server responds with 500 Server Error when clients try to open a websocket connection due to the following exception:

org.sf.web.socket.server.HandshakeFailureException: Failed to upgrade; nested exception is java.lang.IllegalArgumentException: org.eclipse.jetty.websocket.server.JettyWebSocketCreator referenced from a method is not visible from class loader at org.sf.web.socket.server.jetty.Jetty10RequestUpgradeStrategy.upgrade(Jetty10RequestUpgradeStrategy.java:124) at org.sf.web.socket.server.support.AbstractHandshakeHandler.doHandshake(AbstractHandshakeHandler.java:297) at org.sf.web.socket.server.support.WebSocketHttpRequestHandler.handleRequest(WebSocketHttpRequestHandler.java:178) at org.sf.web.servlet.mvc.HttpRequestHandlerAdapter.handle(HttpRequestHandlerAdapter.java:52) at org.sf.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:1071) … at org.eclipse.jetty.io.ssl.SslConnection$1.run(SslConnection.java:132) at org.eclipse.jetty.util.thread.QueuedThreadPool.runJob(QueuedThreadPool.java:933) at org.eclipse.jetty.util.thread.QueuedThreadPool$Runner.run(QueuedThreadPool.java:1077) at java.base/java.lang.Thread.run(Thread.java:833)

Caused by: java.lang.IllegalArgumentException: org.eclipse.jetty.websocket.server.JettyWebSocketCreator referenced from a method is not visible from class loader at java.base/java.lang.reflect.Proxy$ProxyBuilder.ensureVisible(Proxy.java:883) at java.base/java.lang.reflect.Proxy$ProxyBuilder.validateProxyInterfaces(Proxy.java:721) at java.base/java.lang.reflect.Proxy$ProxyBuilder.(Proxy.java:648) at java.base/java.lang.reflect.Proxy.lambda$getProxyConstructor$1(Proxy.java:440) at java.base/jdk.internal.loader.AbstractClassLoaderValue$Memoizer.get(AbstractClassLoaderValue.java:329) at java.base/jdk.internal.loader.AbstractClassLoaderValue.computeIfAbsent(AbstractClassLoaderValue.java:205) at java.base/java.lang.reflect.Proxy.getProxyConstructor(Proxy.java:438) at java.base/java.lang.reflect.Proxy.newProxyInstance(Proxy.java:1037) at org.sf.aop.framework.JdkDynamicAopProxy.getProxy(JdkDynamicAopProxy.java:126) at org.sf.aop.framework.JdkDynamicAopProxy.getProxy(JdkDynamicAopProxy.java:118) at org.sf.aop.framework.ProxyFactory.getProxy(ProxyFactory.java:97) at org.sf.web.socket.server.jetty.Jetty10RequestUpgradeStrategy.createJettyWebSocketCreator(Jetty10RequestUpgradeStrategy.java:134) at org.sf.web.socket.server.jetty.Jetty10RequestUpgradeStrategy.upgrade(Jetty10RequestUpgradeStrategy.java:116) ... 116 more

The IllegalArgumentException originates in java.lang.reflect.Proxy.ensureVisible, which is attempting to ensure the visibility of JettyWebSocketCreator to a org.eclipse.jetty.webapp.WebAppClassLoader. Note that JettyWebSocketCreator has already been successfully loaded by the main class loader. For some reason though, the WebAppClassLoader can't find it.

Comment From: rstoyanchev

@2is10, I've updated how JettyWebSocketCreator is created to refer to the same ClassLoader as the one in which the Class itself was loaded. If you're able to give it a try and confirm it works for you with a 5.3.26-SNAPSHOT that would be great.

Comment From: 2is10

@rstoyanchev Missed the earlier notification. Will try this out. Thank you!

Comment From: rstoyanchev

Thanks, that would be great! I can't reproduce the issue myself, but I did try with a sample to confirm the change works.

Comment From: 2is10

Hmm. 5.3.26-SNAPSHOT is not found in our configured maven repositories. I may just wait for release 5.3.26 to confirm the fix.

Comment From: bclozel

@2is10 You can configure additional repositories like this:

  <repositories>
    <repository>
      <id>spring-milestones</id>
      <name>Spring Milestones</name>
      <url>https://repo.spring.io/milestone</url>
      <snapshots>
        <enabled>false</enabled>
      </snapshots>
    </repository>
    <repository>
      <id>spring-snapshots</id>
      <name>Spring Snapshots</name>
      <url>https://repo.spring.io/snapshot</url>
      <releases>
        <enabled>false</enabled>
      </releases>
    </repository>
  </repositories>

or this:

repositories {
  mavenCentral()
  maven { url 'https://repo.spring.io/milestone' }
  maven { url 'https://repo.spring.io/snapshot' }
}

Comment From: 2is10

Thanks @bclozel. I just tried again with 5.3.26-SNAPSHOT. JettyWebSocketCreator is now accessible, but a NullPointerException occurs in Jetty10RequestUpgradeStrategy a few lines later. The container returned here is null:

Object container = ReflectionUtils.invokeMethod(getContainerMethod, null, servletContext);

The method invoked by reflection there is JettyWebSocketServerContainer.getContainer:

public static JettyWebSocketServerContainer getContainer(ServletContext servletContext)
{
    return (JettyWebSocketServerContainer)servletContext.getAttribute(JETTY_WEBSOCKET_CONTAINER_ATTRIBUTE);
}

That returns null if JettyWebSocketServerContainer.ensureContainer is never called. There is a single caller of that method in that library (websocket-jetty-server-10.0.14.jar): JettyWebSocketServletContainerInitializer.initialize. Its doc says it should be invoked via JettyWebSocketServletContainerInitializer.configure. Is that Spring’s job or mine?

Comment From: bclozel

Rossen did verify the fix with a sample, but maybe your application has a different setup or use case. Could you share a minimal sample that demonstrates the issue?

Comment From: 2is10

Our server code is nearly 10 years old, quite large, and doesn’t use Spring Boot. It would take me hours to produce a minimal sample. Here’s an idea. Could you run your sample with a breakpoint in JettyWebSocketServletContainerInitializer.initialize and share the stack trace? That would help me understand how you expect it to get called.