Bug 65458

Summary: NPE when using a lambda in Session.addMessageHandler
Product: Tomcat 8 Reporter: Emmanuel L <elecharny>
Component: WebSocketAssignee: Tomcat Developers Mailing List <dev>
Status: RESOLVED FIXED    
Severity: critical    
Priority: P2    
Version: 8.5.50   
Target Milestone: ----   
Hardware: PC   
OS: Mac OS X 10.1   

Description Emmanuel L 2021-07-20 15:38:50 UTC
This is close to 57788.

When we write that:

session.addMessageHandler( ( MessageHandler.Whole<String> ) msg -> {
...}) ;

we get a NPE:

java.lang.NullPointerException
at org.apache.tomcat.websocket.Util.getGenericType(Util.java:216)
at org.apache.tomcat.websocket.Util.getMessageType(Util.java:170)
at org.apache.tomcat.websocket.WsSession.addMessageHandler(WsSession.java:206)

OTOH, doing that :

session.addMessageHandler( new MessageHandler.Whole<String>()
  {
  ...
  });

works just fine.

I think it's because MessageHandler.Whole is an Interface, and not a class, when in the second piece of code we have a class that get created. Of course, there might be some Java side effect I don't know about, but at this point, this is what I infer from the Tomcat code (org.apache.tomcat.websocket.Util):

185 private static <T> TypeResult getGenericType(Class<T> type,
            Class<? extends T> clazz) {

        // Look to see if this class implements the interface of interest

190     // Get all the interfaces
        Type[] interfaces = clazz.getGenericInterfaces();
        for (Type iface : interfaces) {
        ... (ignored because it's not a Parameterized type)
        }

206     // Interface not found on this class. Look at the superclass.
        @SuppressWarnings("unchecked")
        Class<? extends T> superClazz =
                (Class<? extends T>) clazz.getSuperclass();
        if (superClazz == null) {
            // Finished looking up the class hierarchy without finding anything
            return null;
        }

Here, Whole extends MessageHandler so we keep going :

215     TypeResult superClassTypeResult = getGenericType(type, superClazz);


and the recursive call do the same thing with the MessageHandler interface, which has no parent, thus returns null :

210        if (superClazz == null) {
            // Finished looking up the class hierarchy without finding anything
            return null;
        }

and back to the caller (the same method) on line :
216     int dimension = superClassTypeResult.getDimension();

and SNAP because superClassTypeResult is null.

Note: the pb should also occurs on 8.5.69 (same code) and I guess in TC 9 and TC 10.
Comment 1 Mark Thomas 2021-07-22 16:51:15 UTC
This is why the method:

public <T> void addMessageHandler(Class<T> clazz, Whole<T> handler)

was added to WsSession. There are some instances, and this is one, where the generic type isn't available at run time.

We could catch the NPE and have Util.getGenericType() return null but that will just move the NPE one step up the call stack. There doesn't seem much point in adding the code to do that.
Comment 2 Emmanuel L 2021-07-23 02:14:31 UTC
Makes sense, and agreed that there is no need to catch the NPE here.

It's a bit sad that the way Java generates the Lambda code results in a none typed handler.

Thanks for the pointer to the other method, will try that.