_AMQ_GROUP_ID present in message but JMSXGroupID null in #JmsListener - spring-boot

From this documentation:
Messages in a message group share the same group id, i.e. they have same group identifier property (JMSXGroupID for JMS, _AMQ_GROUP_ID for Apache ActiveMQ Artemis Core API).
I can see why the property originally set via JMSXGroupID becomes _AMQ_GROUP_ID when I browse the messages in the broker with a value of product=paper. However, In my #JmsListener annotated method I can see the _AMQ_GROUP_ID property is missing and the JMSXGroupID is coming through as null in the Message's headers hashmap.
#JmsListener(destination = "${artemis.destination}", subscription = "${artemis.subscriptionName}",
containerFactory = "containerFactory", concurrency = "15-15")
public void consumeMessage(Message<StatefulSpineEvent<?>> eventMessage)
So
My Producer application sends the message to the queue after setting the string property JMSXGroupID to 'product=paper'
I can see _AMQ_GROUP_ID has a value of 'product=paper' when I browse that message's headers in the Artemis UI
When I debug my listener application and look at the map of headers, _AMQ_GROUP_ID is absent and JMSXGroupID has a value of null instead of 'product=paper'.
Is the character '=' invalid or is there something else that can cause this? I'm running out of things to try.
Edit, with new code:
HeaderMapper:
#Component
public class GroupIdMessageMapper extends SimpleJmsHeaderMapper {
#Override
public MessageHeaders toHeaders(Message jmsMessage) {
MessageHeaders messageHeaders = super.toHeaders(jmsMessage);
Map<String, Object> messageHeadersMap = new HashMap<>(messageHeaders);
try {
messageHeadersMap.put("JMSXGroupID", jmsMessage.getStringProperty("_AMQ_GROUP_ID"));
} catch (JMSException e) {
e.printStackTrace();
}
// can see while debugging that this returns the correct headers
return new MessageHeaders(messageHeadersMap);
}
}
Listener:
#Component
public class CustomSpringJmsListener {
protected final Logger LOG = LoggerFactory.getLogger(getClass());
#JmsListener(destination = "local-queue", subscription = "groupid-example",
containerFactory = "myContainerFactory", concurrency = "15-15")
public void receive(Message message) throws JMSException {
LOG.info("Received message: " + message);
}
}
Application code:
#SpringBootApplication
#EnableJms
public class GroupidApplication implements CommandLineRunner {
private static Logger LOG = LoggerFactory
.getLogger(GroupidApplication.class);
#Autowired
private JmsTemplate jmsTemplate;
#Autowired MessageConverter messageConverter;
public static void main(String[] args) {
LOG.info("STARTING THE APPLICATION");
SpringApplication.run(GroupidApplication.class, args);
LOG.info("APPLICATION FINISHED");
}
#Override
public void run(String... args) {
LOG.info("EXECUTING : command line runner");
jmsTemplate.setPubSubDomain(true);
createAndSendObjectMessage("Message1");
createAndSendTextMessage("Message2");
createAndSendTextMessage("Message3");
createAndSendTextMessage("Message4");
createAndSendTextMessage("Message5");
createAndSendTextMessage("Message6");
}
private void createAndSendTextMessage(String messageBody) {
jmsTemplate.send("local-queue", session -> {
Message message = session.createTextMessage(messageBody);
message.setStringProperty("JMSXGroupID", "product=paper");
return message;
});
}
// BEANS
#Bean
public JmsListenerContainerFactory<?> myContainerFactory(ConnectionFactory connectionFactory,
DefaultJmsListenerContainerFactoryConfigurer configurer) {
DefaultJmsListenerContainerFactory factory = new DefaultJmsListenerContainerFactory();
// This provides all boot's default to this factory, including the message converter
configurer.configure(factory, connectionFactory);
// You could still override some of Boot's default if necessary.
factory.setSubscriptionDurable(true);
factory.setSubscriptionShared(true);
factory.setMessageConverter(messagingMessageConverter());
return factory;
}
#Bean
public MessagingMessageConverter messagingMessageConverter() {
return new MessagingMessageConverter(messageConverter, new GroupIdMessageMapper());
}
}
Stack trace of where SimpleJmsHeaderMapper is being called:
toHeaders:130, SimpleJmsHeaderMapper (org.springframework.jms.support)
toHeaders:57, SimpleJmsHeaderMapper (org.springframework.jms.support)
extractHeaders:148, MessagingMessageConverter
(org.springframework.jms.support.converter) access$100:466,
AbstractAdaptableMessageListener$MessagingMessageConverterAdapter
(org.springframework.jms.listener.adapter) getHeaders:552,
AbstractAdaptableMessageListener$MessagingMessageConverterAdapter$LazyResolutionMessage
(org.springframework.jms.listener.adapter) resolveArgumentInternal:68,
HeaderMethodArgumentResolver
(org.springframework.messaging.handler.annotation.support)
resolveArgument:100, AbstractNamedValueMethodArgumentResolver
(org.springframework.messaging.handler.annotation.support)
resolveArgument:117, HandlerMethodArgumentResolverComposite
(org.springframework.messaging.handler.invocation)
getMethodArgumentValues:148, InvocableHandlerMethod
(org.springframework.messaging.handler.invocation) invoke:116,
InvocableHandlerMethod
(org.springframework.messaging.handler.invocation) invokeHandler:114,
MessagingMessageListenerAdapter
(org.springframework.jms.listener.adapter) onMessage:77,
MessagingMessageListenerAdapter
(org.springframework.jms.listener.adapter) doInvokeListener:736,
AbstractMessageListenerContainer (org.springframework.jms.listener)
invokeListener:696, AbstractMessageListenerContainer
(org.springframework.jms.listener) doExecuteListener:674,
AbstractMessageListenerContainer (org.springframework.jms.listener)
doReceiveAndExecute:318, AbstractPollingMessageListenerContainer
(org.springframework.jms.listener) receiveAndExecute:257,
AbstractPollingMessageListenerContainer
(org.springframework.jms.listener) invokeListener:1190,
DefaultMessageListenerContainer$AsyncMessageListenerInvoker
(org.springframework.jms.listener) executeOngoingLoop:1180,
DefaultMessageListenerContainer$AsyncMessageListenerInvoker
(org.springframework.jms.listener) run:1077,
DefaultMessageListenerContainer$AsyncMessageListenerInvoker
(org.springframework.jms.listener) run:748, Thread (java.lang)

Try subclassing the SimpleJmsHeaderMapper and override toHeaders(). Call super.toHeaders(), create a new Map<> from the result; put() any additional headers you want into the map and return a new MessageHeaders from the map.
Pass the custom mapper into a new MessagingMessageConverter and pass that into the container factory.
If you are using Spring Boot, simply add the converter as a #Bean and boot will auto wire it into the factory.
EDIT
After all this; I just wrote an app and it works just fine for me without any customization at all...
#SpringBootApplication
public class So58399905Application {
public static void main(String[] args) {
SpringApplication.run(So58399905Application.class, args);
}
#JmsListener(destination = "foo")
public void listen(String in, MessageHeaders headers) {
System.out.println(in + headers);
}
#Bean
public ApplicationRunner runner(JmsTemplate template) {
return args -> template.convertAndSend("foo", "bar", msg -> {
msg.setStringProperty("JMSXGroupID", "product=x");
return msg;
});
}
}
and
bar{jms_redelivered=false, JMSXGroupID=product=x, jms_deliveryMode=2, JMSXDeliveryCount=1, ...
EDIT2
It's a bug in the artemis client - with 2.6.4 (Boot 2.1.9) only getStringProperty() returns the value of the _AMQ_GROUP_ID property when getting JMSXGroupID.
The mapper uses getObjectProperty() which returned null. With the 2.10.1 client; the message properly returns the value of the _AMQ_GROUP_ID property from getObjectProperty().

Related

How can i intercept incomig messages before they reach methods annotated with #RabbitListener?

I Started by setting up an interceptor for outgoing messages which is working smoothly, but
when i try to intercept incomming messages in the consumers, the postProcessMessage method
is skipped and the message reaches the method annotated with #RabbitListener, bellow is my code for the whole proccess, i ommited unimportant code.
Producer
RabbitMQProducerInterceptor
#Component
#Slf4j
public class RabbitMQProducerInterceptor implements MessagePostProcessor {
#Override
public Message postProcessMessage(Message message) throws AmqpException {
log.info("Getting the current HttpServletRequest");
HttpServletRequest req = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes())
.getRequest();
log.info("Extracting the X-REQUEST-ID from the header of the HttpServletRequest");
String XRequestId = req.getHeader(ShareableConstants.X_REQUEST_ID_HEADER);
log.info("Adding X-REQUEST-ID {} to the RabbitMQ Producer Header", XRequestId);
message.getMessageProperties().getHeaders().put(ShareableConstants.X_REQUEST_ID_HEADER, XRequestId);
return message;
}
}
RabbitMQProducerConfig
#Bean
public RabbitTemplate rabbitTemplate(ConnectionFactory connectionFactory) {
RabbitTemplate rabbitTemplate = new RabbitTemplate(connectionFactory);
rabbitTemplate.setReplyTimeout(60000);
MessagePostProcessor[] processors = {new RabbitMQProducerInterceptor()};
rabbitTemplate.addBeforePublishPostProcessors(processors);
return rabbitTemplate;
}
Sending a message to the consumer
User Producer
public UserRegistrationResponseDTO register(UserRegistrationDTO userRegistrationDTO) {
log.info("Sending user registration request {}", userRegistrationDTO);
UserRegistrationDTO response = (UserRegistrationDTO) rabbitTemplate
.convertSendAndReceive(ShareableConstants.EXCHANGE_NAME,
ShareableConstants.CREATE_USER_ROUTING_KEY,
userRegistrationDTO);
return UserRegistrationResponseDTO.builder()
.username(response.getUsername())
.id(response.getId())
.createdAt(response.getCreatedAt()).build();
}
Consumer
RabbitMQConsumerConfig
#Bean
public RabbitTemplate rabbitTemplate(ConnectionFactory connectionFactory) {
RabbitTemplate rabbitTemplate = new RabbitTemplate(connectionFactory);
MessagePostProcessor[] processors = {new RabbitMQConsumerInterceptor()};
rabbitTemplate.setAfterReceivePostProcessors(processors);
return rabbitTemplate;
}
RabbitMQConsumerInterceptor
#Component
public class RabbitMQConsumerInterceptor implements MessagePostProcessor {
#Override
public Message postProcessMessage(Message message) throws AmqpException {
String XRequestId = message.getMessageProperties().getHeader(ShareableConstants.X_REQUEST_ID_HEADER);
MDC.put(ShareableConstants.X_REQUEST_ID_HEADER, XRequestId);
return message;
}
}
User Consumer
#RabbitListener(bindings =
#QueueBinding(exchange = #Exchange(ShareableConstants.EXCHANGE_NAME),
key = ShareableConstants.CREATE_USER_ROUTING_KEY,
value = #Queue(ShareableConstants.USER_REGISTRATION_QUEUE_NAME)))
public UserRegistrationDTO receiveUser(UserRegistrationDTO userRegistrationDTO) {
log.info("receiving user {} to register ", userRegistrationDTO);
User user = Optional.of(userRegistrationDTO).map(User::new).get();
User createdUser = userService.register(user);
UserRegistrationDTO registrationDTO = UserRegistrationDTO.builder()
.id(createdUser.getId())
.username(createdUser.getUsername())
.createdAt(createdUser.getCreationDate())
.build();
return registrationDTO;
}
Here's the code, no exception is thrown, the only problem is the Interceptor being skipped
The RabbitTemplate is not used to receive messages for a #RabbitListener; messages are received by a listener container; you have to set the afterReceivePostProcessors on the listener container factory.
If you are using Spring Boot, just add the auto-configured SimpleRabbitListenerContainerFactory as a parameter to one of your other #Beans and set the MPP on it.

Kafka Consumer Invalid Payload Error Handler

I have the below configuration. When the message is invalid I want to send an email and for errors I want to save it in database. How can I handle this in errorHandler() ?
#Configuration
#EnableKafka
public class KafkaConsumerConfig implements KafkaListenerConfigurer{
#Bean
ErrorHandler errorHandler() {
return new SeekToCurrentErrorHandler((rec, ex) ->
{ dbService.saveErrorMsg(rec); }
,new FixedBackOff(5000, 3)) ;
}
#Override
public void configureKafkaListeners(KafkaListenerEndpointRegistrar registrar) {
registrar.setValidator(this.validator);
}
#KafkaListener(topics = "mytopic", concurrency = "3", groupId = "mytopic-1-groupid")
public void consumeFromTopic1(#Payload #Valid ValidatedClass val, ConsumerRecordMetadata meta) throws Exception
{
dbservice.callDB(val,"t");
}
I presume your emai code is in dbService.saveErrorMsg.
Spring Boot should automatically detect the ErrorHandler #Bean and wire it into the container factory.
See Boot's KafkaAnnotationDrivenConfiguration class and ConcurrentKafkaListenerContainerFactoryConfigurer.

Cannot publish Topic-message to two subscriber simlutaneously using ActiveMQ

I have referred to the SpringBoot application publish and read from ActiveMQ topic to, publish a Topic using ActiveMQ. I have created two receiver micro-services which reads message from the topic.I have also created rest endpoint to publish the Topic.However, I have to execute this rest end point two times to publish the message for two receivers 1). The first execution of rest endpoint will send message to Receiver1
2). The second execution of rest endpoint will send message to Receiver2
Hence 2 receivers could not read from the Topic simultaneously.
Here is my code.
PublisherApplication.java
package com.springboot;
//import statements
#SpringBootApplication
#EnableDiscoveryClient
#EnableJms
public class PublisherApplication {
#Bean
public JmsListenerContainerFactory<?> topicListenerFactory(ConnectionFactory connectionFactory,
DefaultJmsListenerContainerFactoryConfigurer configurer) {
DefaultJmsListenerContainerFactory factory = new DefaultJmsListenerContainerFactory();
// This provides all boot's default to this factory, including the message converter
configurer.configure(factory, connectionFactory);
//setPubSubDomain identifies Topic in ActiveMQ
factory.setPubSubDomain(true);
return factory;
}
public static void main(String[] args) {
SpringApplication.run(PublisherApplication.class, args);
}
#Bean
public RestTemplate restTemplate(RestTemplateBuilder builder) {
return builder.build();
}
}
PublishMessage.java
[Rest-end point to publish Topic]
package com.springboot.controller;
//import statements
#RestController
#RequestMapping(path = "/schoolDashboard/topic")
class PublishMessage {
public static final String MAILBOX_TOPIC = "mailbox.topic";
#Autowired
private JmsTemplate jmsTemplate;
#GetMapping(path = "/sendEmail")
public void sendStudentById() throws Exception{
System.out.println("Anindya-TopicSendMessage.java :: Publishing Email sent....");
jmsTemplate.convertAndSend(MAILBOX_TOPIC, "Topic - Email Sent");
}
}
ReceiverApplication01
[Note - Receiver01 is first microservice]
package com.springboot;
//import statements
#SpringBootApplication
#EnableJms
public class ReceiverApplication01 {
#Bean
public JmsListenerContainerFactory<?> topicListenerFactory(ConnectionFactory connectionFactory,
DefaultJmsListenerContainerFactoryConfigurer configurer) {
DefaultJmsListenerContainerFactory factory = new DefaultJmsListenerContainerFactory();
// This provides all boot's default to this factory, including the message converter
configurer.configure(factory, connectionFactory);
//setPubSubDomain identifies Topic in ActiveMQ
factory.setPubSubDomain(true);
return factory;
}
public static void main(String[] args) {
SpringApplication.run(ReceiverApplication01.class, args);
}
}
TopicMesssgeReceiver01.java
[Receiver01 Read message from Topic]
package com.springboot.message;
//import statement
#Component
public class TopicMesssgeReceiver01 {
private final SimpleMessageConverter converter = new SimpleMessageConverter();
public static final String MAILBOX_TOPIC = "mailbox.topic";
#JmsListener(destination = MAILBOX_TOPIC, containerFactory = "topicListenerFactory")
public void receiveMessage(final Message message) throws JMSException{
System.out.println("Receiver01 <" + String.valueOf(this.converter.fromMessage(message)) + ">");
}
}
ReceiverApplication02
[Note:-Receiver02 is second microservice]
package com.springboot;
//import statement
#SpringBootApplication
#EnableJms
public class ReaderApplication02 {
#Bean
public JmsListenerContainerFactory<?> topicListenerFactory(ConnectionFactory connectionFactory,
DefaultJmsListenerContainerFactoryConfigurer configurer) {
DefaultJmsListenerContainerFactory factory = new DefaultJmsListenerContainerFactory();
factory.setPubSubDomain(true);
configurer.configure(factory, connectionFactory);
return factory;
}
public static void main(String[] args) {
SpringApplication.run(ReaderApplication02.class, args);
}
}
TopicMesssgeReceiver02
[Receiver02 Read message from Topic]
package com.springboot.message;
//import statement
#Component
public class TopicMesssgeReceiver02 {
private final SimpleMessageConverter converter = new SimpleMessageConverter();
public static final String MAILBOX_TOPIC = "mailbox.topic";
#JmsListener(destination = MAILBOX_TOPIC, containerFactory = "topicListenerFactory")
public void receiveMessage(final Message message) throws Exception{
System.out.println("Receiver02 <" + String.valueOf(this.converter.fromMessage(message)) + ">");
}
}
Thanks Naveen!! Finally, I am able to do it.
We have to set only setPubSubDomain(true); and spring-boot will take care all boiler-plate code. Now, two receiver Microservices can read message from Topic simultaneously Following are the code changes
PublishMessage.java
[Rest-end point to publish Topic]
package com.springboot.controller;
//import statements
#RestController
#RequestMapping(path = "/schoolDashboard/topic")
class PublishMessage {
public static final String MAILBOX_TOPIC = "mailbox.topic";
#Autowired
private JmsTemplate jmsTemplate;
#GetMapping(path = "/sendEmail")
public void sendStudentById() throws Exception{
System.out.println("Publisher :: Message sent...");
/* Added this statement. setPubSubDomain(true) identifies Topic in ActiveMQ */
jmsTemplate.setPubSubDomain(true);
jmsTemplate.convertAndSend(MAILBOX_TOPIC, "Topic - Email Sent");
}
}
ReceiverApplication02
[Note:-Receiver02 is second microservice]
package com.springboot;
//import statement
#SpringBootApplication
#EnableJms
public class ReaderApplication02 {
#Bean
public JmsListenerContainerFactory<?> topicListenerFactory(ConnectionFactory connectionFactory,
DefaultJmsListenerContainerFactoryConfigurer configurer) {
DefaultJmsListenerContainerFactory factory = new DefaultJmsListenerContainerFactory();
configurer.configure(factory, connectionFactory);
/* setPubSubDomain(true) should be placed after
* configuration of the specified jms listener container factory*/
factory.setPubSubDomain(true);
return factory;
}
public static void main(String[] args) {
SpringApplication.run(ReaderApplication02.class, args);
}
}

Spring Boot: how to use FilteringMessageListenerAdapter

I have a Spring Boot application which listens to messages on a Kafka queue. To filter those messages, have the following two classs
#Component
public class Listener implements MessageListener {
private final CountDownLatch latch1 = new CountDownLatch(1);
#Override
#KafkaListener(topics = "${spring.kafka.topic.boot}")
public void onMessage(Object o) {
System.out.println("LISTENER received payload *****");
this.latch1.countDown();
}
}
#Configuration
#EnableKafka
public class KafkaConfig {
#Autowired
private Listener listener;
#Bean
public FilteringMessageListenerAdapter filteringReceiver() {
return new FilteringMessageListenerAdapter(listener, recordFilterStrategy() );
}
public RecordFilterStrategy recordFilterStrategy() {
return new RecordFilterStrategy() {
#Override
public boolean filter(ConsumerRecord consumerRecord) {
System.out.println("IN FILTER");
return false;
}
};
}
}
While messages are being processed by the Listener class, the RecordFilterStrategy implementation is not being invoked. What is the correct way to use FilteringMessageListenerAdapter?
Thanks
The solution was as follows:
No need for the FilteringMessageListenerAdapter class.
Rather, create a ConcurrentKafkaListenerContainerFactory, rather than relying on what Spring Boot provides out of the box. Then, set the RecordFilterStrategy implementation on this class.
#Bean
ConcurrentKafkaListenerContainerFactory<Integer, String>
kafkaListenerContainerFactory() {
ConcurrentKafkaListenerContainerFactory<Integer, String> factory =
new ConcurrentKafkaListenerContainerFactory<>();
factory.setConsumerFactory(consumerFactory());
factory.setRecordFilterStrategy(recordFilterStrategy());
return factory;
}

how to use and customize MessageConversion(Spring websocket client)

I wrote a web socket server and a client with spring. The codes is following. The codes sending message to server work, but the sesssion.subscribe method cannot receive message from the server. I search for many documents and check my codes. I don't why it cannot work.
Here is my client codes:
public class Test {
public static void main(String[] args) {
Thread thread = new Thread(new WebsocketThread());
thread.start();
Thread.sleep(5000);
}
}
class MyStompSessionHandler extends StompSessionHandlerAdapter {
#Override
public void afterConnected(StompSession session, StompHeaders connectedHeaders) {
session.send("/app/messages", "{'payload3':2222}".getBytes());
session.subscribe("/user/queue/position-updates", new StompFrameHandler() {
#Override
public Type getPayloadType(StompHeaders headers) {
return String.class;
}
#Override
public void handleFrame(StompHeaders headers, Object payload) {
System.out.println("test:" + payload);
}
});
}
}
class WebsocketThread implements Runnable{
#Override
public void run() {
List<Transport> transports = new ArrayList<>(1);
transports.add(new WebSocketTransport( new StandardWebSocketClient()) );
WebSocketClient webSocketClient = new SockJsClient(transports);
WebSocketStompClient stompClient = new WebSocketStompClient(webSocketClient);
String url = "ws://127.0.0.1:8860/orders";
StompSessionHandler sessionHandler = new MyStompSessionHandler();
ListenableFuture<StompSession> future = stompClient.connect(url, sessionHandler);
}
}
Here is my server codes:
#Controller
public class TestController {
#Autowired
private SimpMessagingTemplate simpMessagingTemplate;
#MessageMapping("/messages")
public void sendUserMsg(String messages) throws IOException {
System.out.println("webSocket:" + messages);
simpMessagingTemplate.convertAndSend("/queue/position-updates", "This is return message");
}
}
It is Exception:
org.springframework.messaging.converter.MessageConversionException: No suitable converter, payloadType=class java.lang.String, handlerType=class com.example.hello.MyStompSessionHandler
at org.springframework.messaging.simp.stomp.DefaultStompSession.invokeHandler(DefaultStompSession.java:419)
at org.springframework.messaging.simp.stomp.DefaultStompSession.handleMessage(DefaultStompSession.java:373)
at org.springframework.web.socket.messaging.WebSocketStompClient$WebSocketTcpConnectionHandlerAdapter.handleMessage(WebSocketStompClient.java:342)
at org.springframework.web.socket.sockjs.client.AbstractClientSockJsSession.handleMessageFrame(AbstractClientSockJsSession.java:267)
at org.springframework.web.socket.sockjs.client.AbstractClientSockJsSession.handleFrame(AbstractClientSockJsSession.java:200)
at org.springframework.web.socket.sockjs.client.WebSocketTransport$ClientSockJsWebSocketHandler.handleTextMessage(WebSocketTransport.java:162)
at org.springframework.web.socket.handler.AbstractWebSocketHandler.handleMessage(AbstractWebSocketHandler.java:43)
at org.springframework.web.socket.adapter.standard.StandardWebSocketHandlerAdapter.handleTextMessage(StandardWebSocketHandlerAdapter.java:110)
at org.springframework.web.socket.adapter.standard.StandardWebSocketHandlerAdapter.access$000(StandardWebSocketHandlerAdapter.java:42)
at org.springframework.web.socket.adapter.standard.StandardWebSocketHandlerAdapter$3.onMessage(StandardWebSocketHandlerAdapter.java:81)
at org.springframework.web.socket.adapter.standard.StandardWebSocketHandlerAdapter$3.onMessage(StandardWebSocketHandlerAdapter.java:78)
at org.apache.tomcat.websocket.WsFrameBase.sendMessageText(WsFrameBase.java:399)
at org.apache.tomcat.websocket.WsFrameBase.processDataText(WsFrameBase.java:500)
at org.apache.tomcat.websocket.WsFrameBase.processData(WsFrameBase.java:295)
at org.apache.tomcat.websocket.WsFrameBase.processInputBuffer(WsFrameBase.java:131)
at org.apache.tomcat.websocket.WsFrameClient.processSocketRead(WsFrameClient.java:73)
at org.apache.tomcat.websocket.WsFrameClient.access$300(WsFrameClient.java:31)
at org.apache.tomcat.websocket.WsFrameClient$WsFrameClientCompletionHandler.completed(WsFrameClient.java:131)
at org.apache.tomcat.websocket.WsFrameClient$WsFrameClientCompletionHandler.completed(WsFrameClient.java:114)
at sun.nio.ch.Invoker.invokeUnchecked(Invoker.java:126)
at sun.nio.ch.Invoker$2.run(Invoker.java:218)
at sun.nio.ch.AsynchronousChannelGroupImpl$1.run(AsynchronousChannelGroupImpl.java:112)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
at java.lang.Thread.run(Thread.java:745)
add a StringMessageConverter to Client, it works.
WebSocketStompClient stompClient = new WebSocketStompClient(webSocketClient);
stompClient.setMessageConverter(new StringMessageConverter());
But how to customize our own MessageConverter? Is there any article?
In my case, the server was sending both json and raw string messages on different channels.
To be able to handle both cases, I went through the MessageConverter implementations and found CompositeMessageConverter, which allows multiple converters to be setup on the client.
Code:
List<MessageConverter> converters = new ArrayList<MessageConverter>();
converters.add(new MappingJackson2MessageConverter()); // used to handle json messages
converters.add(new StringMessageConverter()); // used to handle raw strings
client.setMessageConverter(new CompositeMessageConverter(converters));
The StompFrameHandler will then decide, based on what getPayloadType() returns, which converter to use.
add a SimpleMessageConverter to Client, it works.
stompClient.setMessageConverter(new SimpleMessageConverter());
It seems like you don't have configured any org.springframework.messaging.converter.MessageConverter in web socket configuration.
If you have jackson jar on your class path then it will be automatically picked up for json conversion. For other convertors , you need to configure it in WebSocket Config file .
#Configuration
#EnableWebSocketMessageBroker
#ComponentScan(SpringScanPackageNames)
public class WebSocketConfig extends AbstractWebSocketMessageBrokerConfigurer {
#Override
public boolean configureMessageConverters(List<MessageConverter> arg0) {
StringMessageConverter strConvertor = new StringMessageConverter();
arg0.add(strConvertor);
return true;
}
// Other config
I had a similar problem (though I was sending custom objects) and what worked for me was to simply set the Jackson2Message message converter as
webSocketStompClient.messageConverter = new MappingJackson2MessageConverter()
I found useful info (and examples) about this on: https://github.com/Noozen/spring-boot-websocket-client#the-java-client

Resources