How to Return Custom Types in HTTP Responses using Spring Web
Time to read: 3 minutes
Spring Boot is the most popular framework for Java applications, and with the Web module enabled is a great way to quickly start building a web app. Both Boot and Web have a lot of preconfigured behaviours to make common tasks easy, and extension points for when you need something custom. In this post we'll look at one such extension point, so that our HTTP endpoint methods need deal only in the types that are meaningful for us, while still converting to HTTP responses correctly.
With Spring Web if you return Java objects from methods annotated with @GetMapping
or @PostMapping
(or the catch-all @RequestMapping
), Spring can turn that object into a JSON response automatically.
For a lot of cases this is enough - JSON over HTTP is the lingua franca of web APIs. It's not the only game in town though - you might find cases where you want to create a response from a custom Java type and have control over how the response body and headers are generated. This can be the case when working with Twilio as responses to Twilio webhook requests need to contain valid TwiML with an application/xml
content type. Let's look at a short method and see 3 different ways to create the same response.
Hand-written Responses
It's quite possible to return a String from your HTTP-mapped method (which needs to be in a @RestController
-annotated class):
With multi-line strings (added in Java 15) this doesn't even look too bad, but if you need to build your TwiML dynamically it can get tricky to make sure you're creating valid TwiML, and is laborious to maintain.
Object Conversion to String
Our Java Helper Library provides a few classes to build response objects programmatically and generate valid TwiML at the end. Sticking with the same example as before:
Here we have created a VoiceResponse
and called .toXml()
to convert it to a String. This is helpful but a couple of problems remain: we have to remember the produces = "application/xml"
line on each method, and we have to call .toXml()
on the VoiceResponse
object which is another thing to remember every time. A more subtle problem is that this method is hard to unit test. By forcing the VoiceResponse
into a String our tests will need to rely on fragile string-matching, or use an XML parser to turn the String back into a VoiceResponse
. This is untidy, and Spring Web is hugely customizable to enable us to avoid this kind of mess.
Can we have Automatic Response Generation?
Ideally we would write our method as minimally as possible, like this:
If you add this code Spring will assume you want the VoiceResponse
turned into JSON and will do that without hesitation. It's great when you need it, but does not spark joy in this moment.
Ideally we could tell Spring that any time we return a VoiceResponse
it needs to serialize the object for HTTP by calling the .toXml()
method and set application/xml
in the headers, and we should only need to say that once, not on every single method.
Using Spring's HttpMessageConverter
This is precisely what we can do with a MessageConverter
:
Our new converter is annotated as a @Component
, and extends AbstractHttpMessageConverter<TwiML>
. This means that Spring will find the class when scanning the project, and use the methods in this class whenever it needs to convert a matching object to an HTTP response. VoiceResponse
is one of the subclasses of TwiML so by using TwiML.class.isAssignableFrom
in the booleans supports
method we are claiming VoiceResponse
and all the other subclasses of TwiML as ours to convert. The actual conversion happens in the writeInternal
method, which is a one-liner. The content-type header we need is specified in the constructor.
HttpMessageConverters
can also read HTTP requests into Java objects but we have no need for that so return false
for all requests in canRead
and leave the readInternal
method as empty as possible (it won't be called unless canRead
returns true).
That's it - add the TwiMLMessageConverter
to your project and all your TwiML responses will be automatically converted to XML and have the right header set. Note that this is best used purely for creating correctly formatted responses, although you could do any kind of manipulation to the object before you write the response, this converter is not the place for extra database calls or other business logic.
Are you building with Twilio and Java? I'd love to hear from you at mgilliard@twilio.com
Related Posts
Related Resources
Twilio Docs
From APIs to SDKs to sample apps
API reference documentation, SDKs, helper libraries, quickstarts, and tutorials for your language and platform.
Resource Center
The latest ebooks, industry reports, and webinars
Learn from customer engagement experts to improve your own communication.
Ahoy
Twilio's developer community hub
Best practices, code samples, and inspiration to build communications and digital engagement experiences.