MessageConverterBeans Or: How I learned to stop converting manually and trust in Spring
We have all been there. We picked that surprisingly simple user story that wanted us to directly return a String given as a PathParameter
. The story is implemented and all our new endpoints return the String-parameter they get in the request when suddenly a new criterion we have never noticed before materializes in the story’s DoD. The String should be returned as a PNG
containing the String as a QR-Code.
With the review coming closer some really good advice is needed. Reimplementing every single endpoint to return ResponseEntities
and write byte-Arrays instead of simple Strings takes way too long and is way too much work.
What if… there was a simple yet almost ingenious solution to convert Http-Messages
the way we want them. Maybe with just a single Bean available in the context which will then be utilized the very moment we need a PNG
containing a QR-Code. A Bean converting a HttpMessage
, something like a HttpMessageConverterBean.
Luckily, Spring has us covered.
Spring’s HttpMessageConverters
do pretty much what their name implies. Whenever a HttpRequest
needs to be converted to an object (and vice versa) Spring selects a matching HttpMessageConverter
to do this job.
A few of these MessageConverters are enabled by default and perform their jobs without us even noticing. Have you ever wondered why our DTOs are returned as a JSON-structure without ever using a JSON-serializer explicitly or why a JSON-object sent to an endpoint shows up as an object of just that class we expect?
That’s because a matching HttpMessageConverter
— namely the MappingJackson2HttpMessageConverter
— is available on the classpath as it comes as a transitive dependency of the Spring Boot Starter.
The Jackson converter of course comes with some magic to handle all kinds of DTOs without the need to explicitly implement a matching MessageConverter. Still, the idea is all the same.
But how does this help us with the review coming closer and closer?
Obviously, all we have to do is implement our own MessageConverter
which converts a String to a PNG image containing the QR-Code, place a matching Bean in the context, and call our endpoints in a way so the converter is used.
The first step is to implement a QrGeneratorService
which creates a BufferedImage
containing the QR-Code. For this task we will use the ZXing (“zebra crossing”) library. ZXing unfortunately is in Maintenance Mode only, but it still is a really good library to create and detect all sorts of 1D and 2D-Codes.
All code mentioned here and a complete demo application is available on github
The QrGeneratorService
is a straight-forward Service class containing only one method accepting a String and the edge length of the square QR-Code which then creates a BufferedImage
containing said QR-Code.
O nce the creation of theBufferedImage
is implemented, our next step is to convert the String
return value from our endpoint and return a PNG
instead. Therefore we have to implement the MessageConverter
itself. The HttpMessageConverter
interface is quite simple with only five well-named methods to be implemented:
Since we don’t want to read QR-Codes but only write them, our implementation is reduced to three of these methods: canWrite(...)
, write(...)
, and getSupportedMediaTypes(...)
.
While the canWrite(...)
and getSupportedMediaTypes(...)
methods are only used to determine what the HttpMessasgeConverter
supports, the write(...)
method is where the magic happens. Here the content is converted and written to the HttpOutputMessage.
We do not convert incoming messages in our converter therefore
canRead(...)
always returnsfalse
and thusread(...)
won’t even be called.
The write(...)
method is called with three parameters: The String
to be converted, the MediaType
requested and the HttpOutputMessage
we write the converted result to.
We then first create a BufferedImage with the QR-Code and set the contentType
of the body of the HttpOutpuMessage:
The second step is to actually write the PNG
to the outputMessage’s body
:
That’s all — at least everything concerning the HTTP part. Functionality to create the PNG
is encapsulated in the writeBufferedImageAsContentTypeToOutputStream(...)
method. If you want to have a look at the details on how to create a PNG
from a BufferedImage
you can of course find that code on github.
Now that we have completed the conversion part our next task is to tell Spring to actually use it.
This means: We have to put the Bean
into the context. To place the Bean in the application’s context we create a Configuration
class containing the Bean
definition:
And that’s it. To ensure our client gets the information in the format it wants we can go two ways:
One way is to extend the GetMapping
for that endpoint and explicitly state that this endpoint produces a PNG
image. This means that whoever calls this client will always receive a PNG
containing the QR-Code.
With this configuration the controller returns a PNG even if the Accept
header is not set:
Another way would be to let the client decide whether they want a PNG
or just the plain text. In this case we don’t simply configure the MediaType
the endpoint produces, but look at what the client requests in the Accept
header first. When a client requests an image/png
but our controller-method only returns a String, Spring will use our MessageConverter
to convert the String
to a PNG
.
If the client requests a format Spring does not have a converter for status
406 Not Acceptable
is returned.
Without the produces
parameter set in the GetMapping
the application returns a PNG
only if an image/png
is requested. If the Accept
header contains text/plain
the actual plain text is returned instead:
Both ways have their pros and cons. I personally find it a very intriguing idea that we can let the client decide which representation it wants for the requested data and that we can handle the conversion without any explicit implementation in the Controller itself.
Assuming we wanted to not only provide the data as an QR-Code but also as some sort of barcode we could define a custom MediaType
in the context of this application and register a matching converter. That’s all. curl -H “Accept: image/x-qr-png”
might then return a QR-Code, curl -H “Accept: image/x-barcode-png”
might return a barcode. Both without the need of adding new endpoints. All we would have to do is add another ConverterBean
.
So, what did we learn?
a) Read your User-Stories properly ;)
b) Spring’s HttpMessageConverters
provide an easy-to-use and intuitively understandable way to convert Objects into all kind of formats. The conversion itself is encapsulated in message converters and Spring automatically picks the right converter based on the object to be converted and the MediaType
to be produced. Many conversions are actually made and used without us even noticing.
When the default converters don’t suit our needs we can easily implement our own converter and it integrates just as the default ones.
From a testing and reusability point of view the segregated development of the converter is just perfect. With ConditionalBeans
we can even configure the available converters at start time with just a change in configuration.
I hope you enjoyed this article about a mechanism that’s certainly used in almost every Spring-based backend and I hope I could help you understand, how to use HttpMessageConverters
to improve your API
. What was your most memorable moment when a criterion suddenly materialized?
Thanks for reading! If you have any comments, suggestions, or questions please leave a comment, drop me a message on medium, or DM me on twitter. You might also be interested in the other posts published in the Digital Frontiers blog, announced on our Twitter account.