Skip to content

How to Serialize JMS Messages as JSON with Spring's MessageConverter

Problem

When I tried to send a POJO over JMS in my Spring Boot application, I got this error:

Terminal window
org.springframework.jms.support.converter.MessageConversionException: Failed to resolve type for message payload
at org.springframework.jms.support.converter.SimpleMessageConverter.toMessage(SimpleMessageConverter.java:78)

I expected Spring to automatically convert my object to JSON. Instead, I found that Spring JMS uses SimpleMessageConverter by default, which only handles three types: String, byte[], and Map.

Environment

  • Spring Boot 3.2.x
  • Java 21
  • ActiveMQ / IBM MQ (any JMS broker)
  • Jackson 2.15.x

What happened?

I was building a messaging system where I needed to send article objects between services. Here’s my POJO:

src/main/java/com/example/Article.java
public record Article(
Long id,
String title,
String content,
String author,
LocalDateTime publishedAt
) {}

I tried sending it directly with JmsTemplate:

src/main/java/com/example/ArticlePublisher.java
@Service
public class ArticlePublisher {
@Autowired
private JmsTemplate jmsTemplate;
public void publishArticle(Article article) {
jmsTemplate.convertAndSend("article.queue", article);
}
}

And my listener:

src/main/java/com/example/ArticleListener.java
@Component
public class ArticleListener {
@JmsListener(destination = "article.queue")
public void receiveArticle(Article article) {
System.out.println("Received article: " + article.title());
}
}

When I ran the application, the SimpleMessageConverter threw an error because it cannot serialize POJOs directly. It only knows how to handle basic types.

How to solve it?

I needed to implement a custom MessageConverter that uses Jackson’s JsonMapper to serialize objects as JSON. Here’s my solution:

src/main/java/com/example/config/JsonMessageConverter.java
package com.example.config;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonMapper;
import jakarta.jms.JMSException;
import jakarta.jms.Message;
import jakarta.jms.Session;
import jakarta.jms.TextMessage;
import org.springframework.jms.support.converter.MessageConversionException;
import org.springframework.jms.support.converter.MessageConverter;
public class JsonMessageConverter implements MessageConverter {
private final JsonMapper jsonMapper;
public JsonMessageConverter() {
this.jsonMapper = JsonMapper.builder()
.findAndAddModules()
.build();
}
@Override
public Message toMessage(Object object, Session session) throws JMSException, MessageConversionException {
String json;
try {
json = jsonMapper.writeValueAsString(object);
} catch (JsonProcessingException e) {
throw new MessageConversionException("Failed to serialize object to JSON", e);
}
TextMessage message = session.createTextMessage(json);
message.setStringProperty("_type", object.getClass().getName());
return message;
}
@Override
public Object fromMessage(Message message) throws JMSException, MessageConversionException {
if (!(message instanceof TextMessage textMessage)) {
throw new MessageConversionException("Expected TextMessage but received: " + message.getClass().getName());
}
String json = textMessage.getText();
String typeName = message.getStringProperty("_type");
if (typeName == null) {
throw new MessageConversionException("Missing _type property in message");
}
try {
Class<?> type = Class.forName(typeName);
return jsonMapper.readValue(json, type);
} catch (ClassNotFoundException e) {
throw new MessageConversionException("Unknown type: " + typeName, e);
} catch (JsonProcessingException e) {
throw new MessageConversionException("Failed to parse JSON: " + json, e);
}
}
}

I can explain the key parts:

  • JsonMapper is more efficient than ObjectMapper for simple use cases
  • findAndAddModules() enables support for Java 8+ types like LocalDateTime
  • _type property stores the fully qualified class name for reconstruction
  • toMessage() serializes the POJO to JSON and wraps it in a TextMessage
  • fromMessage() deserializes JSON back to the original type using the _type property

Configuration

I registered the converter as a Spring bean:

src/main/java/com/example/config/JmsConfig.java
package com.example.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.jms.support.converter.MessageConverter;
@Configuration
public class JmsConfig {
@Bean
public MessageConverter jsonMessageConverter() {
return new JsonMessageConverter();
}
}

Spring Boot auto-wires this MessageConverter bean into:

  • JmsTemplate for sending messages
  • @JmsListener methods for receiving messages
  • JmsClient for synchronous operations

Sending and Receiving

Now my publisher works without any changes:

src/main/java/com/example/ArticlePublisher.java
@Service
public class ArticlePublisher {
@Autowired
private JmsTemplate jmsTemplate;
public void publishArticle(Article article) {
// JsonMessageConverter automatically converts Article to JSON
jmsTemplate.convertAndSend("article.queue", article);
}
}

And my listener automatically receives typed objects:

src/main/java/com/example/ArticleListener.java
@Component
public class ArticleListener {
@JmsListener(destination = "article.queue")
public void receiveArticle(Article article) {
// JsonMessageConverter deserializes JSON back to Article
System.out.println("Received article: " + article.title());
}
}

The Reason

The SimpleMessageConverter has limited support for object types:

TypeSupport
StringYes - sent as TextMessage
byte[]Yes - sent as BytesMessage
MapYes - sent as MapMessage
POJONo - throws MessageConversionException

The _type property is critical for deserialization. Without it, Jackson wouldn’t know what class to deserialize the JSON into. The message would just be a generic JSON string with no type information.

Here’s what the JSON message looks like on the wire:

{
"id": 123,
"title": "Understanding Spring JMS",
"content": "A comprehensive guide...",
"author": "cowrie",
"publishedAt": "2026-03-26T10:00:00"
}

And the JMS message properties include:

_type = com.example.Article

Common Mistakes

I made several mistakes while implementing this:

1. Forgetting the _type Property

Without the _type property, I couldn’t reconstruct the original object:

Wrong:
@Override
public Message toMessage(Object object, Session session) throws JMSException {
String json = jsonMapper.writeValueAsString(object);
return session.createTextMessage(json); // Missing _type!
}

The deserializer wouldn’t know what class to use.

2. Not Handling Non-TextMessage Scenarios

JMS supports multiple message types. I initially assumed all messages would be TextMessage:

Wrong:
@Override
public Object fromMessage(Message message) throws JMSException {
TextMessage textMessage = (TextMessage) message; // ClassCastException!
// ...
}

This throws ClassCastException when receiving BytesMessage or ObjectMessage.

3. Using ObjectMapper Instead of JsonMapper

Jackson 2.13+ provides JsonMapper which is more efficient:

Less
private final ObjectMapper objectMapper = new ObjectMapper(); // Works but slower
More
private final JsonMapper jsonMapper = JsonMapper.builder()
.findAndAddModules()
.build();

4. Throwing Generic Exceptions

I initially threw RuntimeException which made debugging harder:

Wrong:
throw new RuntimeException("Failed to convert message");

Using MessageConversionException provides better error context:

Correct:
throw new MessageConversionException("Failed to convert message", cause);

Summary

In this post, I showed how to implement a custom JMS MessageConverter to serialize POJOs as JSON in Spring Boot. The key points are:

  1. SimpleMessageConverter cannot serialize POJOs directly
  2. Custom MessageConverter delegates to Jackson’s JsonMapper for JSON conversion
  3. The _type message property stores the fully qualified class name for reconstruction
  4. Spring Boot auto-wires MessageConverter beans into JmsTemplate and @JmsListener

The implementation handles serialization, deserialization, and type preservation. This pattern works with any JMS broker: ActiveMQ, IBM MQ, RabbitMQ, or Amazon SQS.

Final Words + More Resources

My intention with this article was to help others share my knowledge and experience. If you want to contact me, you can contact by email: Email me

Here are also the most important links from this article along with some further resources that will help you in this scope:

Oh, and if you found these resources useful, don’t forget to support me by starring the repo on GitHub!

Comments