This code will cause an exception to be thrown:

SimpMessagingTemplate template = ...;

template.convertAndSendToUser(dest, currentUserId, null); // payload param is null

There are many cases when you might want to send a WS message without a payload. E.g. "account activated" is received only by the same user who's account was just activated, so having a null/empty payload is completely reasonable since the client only cares that a message was sent to the /user/<my id>/activated endpoint.

But passing payload as null causes the following exception:

Cannot invoke "Object.getClass()" because "payload" is null
    at org.springframework.messaging.converter.AbstractMessageConverter.canConvertTo(AbstractMessageConverter.java:233) ~[spring-messaging-5.3.6.jar:5.3.6]
    at org.springframework.messaging.converter.AbstractMessageConverter.toMessage(AbstractMessageConverter.java:197) ~[spring-messaging-5.3.6.jar:5.3.6]
    at org.springframework.messaging.converter.CompositeMessageConverter.toMessage(CompositeMessageConverter.java:96) ~[spring-messaging-5.3.6.jar:5.3.6]
    at org.springframework.messaging.core.AbstractMessageSendingTemplate.doConvert(AbstractMessageSendingTemplate.java:181) ~[spring-messaging-5.3.6.jar:5.3.6]
    at org.springframework.messaging.core.AbstractMessageSendingTemplate.convertAndSend(AbstractMessageSendingTemplate.java:150) ~[spring-messaging-5.3.6.jar:5.3.6]
    at org.springframework.messaging.simp.SimpMessagingTemplate.convertAndSendToUser(SimpMessagingTemplate.java:230) ~[spring-messaging-5.3.6.jar:5.3.6]
    at org.springframework.messaging.simp.SimpMessagingTemplate.convertAndSendToUser(SimpMessagingTemplate.java:218) ~[spring-messaging-5.3.6.jar:5.3.6]
    at org.springframework.messaging.simp.SimpMessagingTemplate.convertAndSendToUser(SimpMessagingTemplate.java:204) ~[spring-messaging-5.3.6.jar:5.3.6]
    at com.teslagov.clarakm.net.ws.WebsocketMessageSender.lambda$sendToUsers$0(WebsocketMessageSender.java:67) ~[net-ws-1.0.4.jar:na]
    at java.base/java.util.stream.ForEachOps$ForEachOp$OfRef.accept(ForEachOps.java:183) ~[na:na]
    at java.base/java.util.AbstractList$RandomAccessSpliterator.forEachRemaining(AbstractList.java:720) ~[na:na]
    at java.base/java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:509) ~[na:na]
    at java.base/java.util.stream.ForEachOps$ForEachTask.compute(ForEachOps.java:290) ~[na:na]
    at java.base/java.util.concurrent.CountedCompleter.exec(CountedCompleter.java:754) ~[na:na]
    at java.base/java.util.concurrent.ForkJoinTask.doExec$$$capture(ForkJoinTask.java:373) ~[na:na]
    at java.base/java.util.concurrent.ForkJoinTask.doExec(ForkJoinTask.java) ~[na:na]
    at java.base/java.util.concurrent.ForkJoinTask.invoke(ForkJoinTask.java:686) ~[na:na]
    at java.base/java.util.stream.ForEachOps$ForEachOp.evaluateParallel(ForEachOps.java:159) ~[na:na]
    at java.base/java.util.stream.ForEachOps$ForEachOp$OfRef.evaluateParallel(ForEachOps.java:173) ~[na:na]
    at java.base/java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:233) ~[na:na]
    at java.base/java.util.stream.ReferencePipeline.forEach(ReferencePipeline.java:596) ~[na:na]
    at java.base/java.util.stream.ReferencePipeline$Head.forEach(ReferencePipeline.java:765) ~[na:na]

I can get around this by sending an arbitrary object as the payload (e.g. ""), but this produces a "code smell".

Comment From: rstoyanchev

This comes from the MessageSendingOperations contract, where payload is not nullable, and all the down to the MessageConverter and Message contracts that require some kind of payload. It would be unclear what SimpMessagingTemplate should do to fill in for a null, and what type to use. Defining an EMPTY_PAYLOAD constant and passing that explicitly I think is the most clear way to express the intent rather than leaving it null, which leaves more questions like what's in the message payload then?

Comment From: JoshMcCullough

I don't really follow why a null payload is unclear. The use case is a message where the destination alone is the enough to trigger some action, and there is no relevant payload to go along with the message. Sending an empty string just because the call-chain enforces the payload be non-null is less than ideal. E.g. what's the harm in sending a null/empty payload (and yes, I consider in this case that a null payload == an empty payload for all intents and purposes).