HTTP Message Converters in Spring MVC

When building web applications with Spring MVC, there are times when you don’t want to generate HTML responses using view templates like Thymeleaf or JSP. Instead, you might need to work directly with JSON data in HTTP APIs, reading or writing it straight from the HTTP message body. This is where HTTP Message Converters come in handy. Let’s dive into how they work, starting with a quick recap of some fundamentals.

The Role of @ResponseBody

You might know the @ResponseBody annotation. Here’s how it works:

  • When you annotate a controller method with @ResponseBody, Spring skips the usual view resolution process (handled by viewResolver).
  • Instead, it writes the return value directly into the HTTP response body.
  • This is powered by HTTP Message Converters, which handle different types of data:
    • String: Processed by StringHttpMessageConverter.
    • Objects: Handled by MappingJackson2HttpMessageConverter (for JSON).
    • Byte arrays: Managed by ByteArrayHttpMessageConverter, and so on.

For responses, Spring selects the appropriate converter based on two factors:

  1. The HTTP Accept header from the client.
  2. The return type of the controller method.

We’ll explore this in more detail later, but for now, let’s focus on HTTP Message Converters themselves.

When Are HTTP Message Converters Used?

Spring MVC applies HTTP Message Converters in the following scenarios:

  • HTTP Requests: When using @RequestBody or HttpEntity (e.g., RequestEntity).
  • HTTP Responses: When using @ResponseBody or HttpEntity (e.g., ResponseEntity).

The HTTP Message Converter Interface

At its core, an HTTP Message Converter is defined by the HttpMessageConverter interface in Spring. Here’s a simplified look at its key methods:


package org.springframework.http.converter; public interface HttpMessageConverter<T> { boolean canRead(Class<?> clazz, MediaType mediaType); boolean canWrite(Class<?> clazz, MediaType mediaType); List<MediaType> getSupportedMediaTypes(); T read(Class<? extends T> clazz, HttpInputMessage inputMessage); void write(T t, MediaType contentType, HttpOutputMessage outputMessage); }
  • canRead() and canWrite(): These methods check if the converter supports a specific class type (e.g., String, byte[]) and media type (e.g., application/json, text/plain).
  • read(): Reads data from the HTTP request body and converts it into an object.
  • write(): Writes an object into the HTTP response body.

Default Message Converters in Spring Boot

Spring Boot comes with several built-in message converters, each with a specific priority. Here are a few key ones:

  1. ByteArrayHttpMessageConverter: Handles byte[] data.
    • Class: byte[]
    • Media Type: */*
    • Example: @RequestBody byte[] data or @ResponseBody return byte[] (produces application/octet-stream).
  2. StringHttpMessageConverter: Handles String data.
    • Class: String
    • Media Type: */*
    • Example: @RequestBody String data or @ResponseBody return "ok" (produces text/plain).
  3. MappingJackson2HttpMessageConverter: Handles JSON data.
    • Class: Objects or HashMap
    • Media Type: application/json
    • Example: @RequestBody HelloData data or @ResponseBody return helloData.

Spring Boot evaluates the target class type and media type to decide which converter to use. If one converter doesn’t match, it moves to the next in the priority list.

How HTTP Message Converters Work

Let’s break it down into two processes: reading request data and writing response data.

1. Reading HTTP Request Data

When a request arrives and a controller uses @RequestBody or HttpEntity:

  • Spring calls canRead() to check:
    • Does the converter support the target class (e.g., String, byte[], or a custom object like HelloData)?
    • Does it support the request’s Content-Type (e.g., application/json, text/plain)?
  • If canRead() returns true, Spring calls read() to convert the request body into an object.

For example:

@RequestMapping void hello(@RequestBody HelloData data) {}
  • If the request has Content-Type: application/json, MappingJackson2HttpMessageConverter kicks in to deserialize the JSON into a HelloData object.
2. Writing HTTP Response Data

When a controller returns a value with @ResponseBody or HttpEntity:

  • Spring calls canWrite() to check:
    • Does the converter support the return type (e.g., String, byte[], HelloData)?
    • Does it support the client’s Accept header (or the produces attribute in @RequestMapping)?
  • If canWrite() returns true, Spring calls write() to serialize the object into the response body.

For example:

@RequestMapping(produces = "application/json") @ResponseBody HelloData hello() { return new HelloData(); }
  • Here, MappingJackson2HttpMessageConverter serializes HelloData into JSON.

Examples in Action

  1. String Converter:
    @RequestMapping void hello(@RequestBody String data) {}
    • Works with Content-Type: application/json or text/plain.
  2. JSON Converter:
    @RequestMapping void hello(@RequestBody HelloData data) {}
    • Requires Content-Type: application/json.
  3. Mismatched Case:
    @RequestMapping void hello(@RequestBody HelloData data) {}
    • If the Content-Type is text/html, no converter will match, and Spring will throw an error.

Comments

Popular posts from this blog

@ModelAttribute vs @RequestBody in Validation

Side Project(a self-imposed 3-day "Hackathon" challenge)

Google: The King is Back