Spring Boot HTTP Message Conversion & Content Negotiation Deep Dive
This article explains Spring Boot's HttpMessageConverter and Content Negotiation mechanisms, covering default converters, source code flow, customization via WebMvcConfigurer, and troubleshooting 415/406 errors.
1. What Message Converters Do in the Request Pipeline
When a controller method uses @RequestBody or @ResponseBody, Spring needs to convert between HTTP message bodies and Java objects. Two mechanisms work together:
HttpMessageConverter : Handles reading (deserialization) and writing (serialization) for specific media types and Java types.
Content Negotiation : Decides which media type to use for the response based on the request.
The converter does not decide the format; it only knows whether it can handle a given media type and Java type. The format decision is made by content negotiation.
2. HttpMessageConverter Interface and Default Converters
2.1 Core Interface
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) throws IOException, HttpMessageNotReadableException;
void write(T t, MediaType contentType, HttpOutputMessage outputMessage) throws IOException, HttpMessageNotWritableException;
}The adapter checks canRead / canWrite first, then calls read / write.
2.2 Spring Boot Default Converters (registered based on classpath dependencies)
ByteArrayHttpMessageConverter– byte arrays,
application/octet-streamStringHttpMessageConverter– strings, text/plain,
*/*ResourceHttpMessageConverter– file resources,
application/octet-streamMappingJackson2HttpMessageConverter– JSON (Jackson),
application/jsonMappingJackson2XmlHttpMessageConverter– XML (Jackson XML),
application/xmlFormHttpMessageConverter– form data,
application/x-www-form-urlencodedAllEncompassingFormHttpMessageConverter– form + multipart, multipart/form-data Order matters: the adapter iterates the list and uses the first converter whose canWrite returns true.
2.3 JSON Converter and ObjectMapper
MappingJackson2HttpMessageConverterholds an ObjectMapper. Spring Boot auto-configuration injects the globally configured ObjectMapper (via spring.jackson.* properties) into the converter. Thus date formats, null handling, and serialization rules are controlled through that shared ObjectMapper.
2.4 Converter List Source
RequestMappingHandlerAdapterholds the converter list, provided by MVC configuration. WebMvcAutoConfiguration registers defaults based on classpath; custom converters added via WebMvcConfigurer are merged into the same list.
3. Content Negotiation: How the Response Type Is Determined
3.1 Problem
The same return object can be serialized as JSON or XML. The server must pick a media type both sides accept and set the Content-Type header.
3.2 Three Negotiation Strategies
ContentNegotiationManageraggregates multiple ContentNegotiationStrategy implementations, tried in order:
HeaderContentNegotiationStrategy – uses Accept header (default). Parses q-values for priority.
ParameterContentNegotiationStrategy – uses a request parameter (e.g., ?format=json). Usually higher priority than Accept header.
PathExtensionContentNegotiationStrategy – uses URL suffix (e.g., /order.json). Deprecated/weakened since Spring Boot 2.x (PathPatternParser disables suffix matching by default).
3.3 Negotiation Flow
1. Collect producible media types from the handler method (produces / converter support)
2. Invoke each strategy to get client-preferred types
3. Intersect: server producible ∩ client acceptable
4. Select best MediaType, pass to converter.write
5. No match → 406 Not Acceptable3.4 Accept Header (Default)
Accept: application/json;q=0.9, application/xml;q=0.8Server prefers JSON; falls back to XML if JSON not supported.
3.5 Request Parameter
Enable via config:
spring:
mvc:
contentnegotiation:
favor-parameter: true
parameter-name: format
favor-path-extension: falseThen GET /order/1?format=xml selects XML converter.
3.6 Suffix Strategy Evolution
Early versions allowed /order/1.json. Since Spring Boot 2.x, PathPatternParser disables suffix matching by default; PathExtensionContentNegotiationStrategy is discouraged. Prefer Accept header or parameter.
3.7 produces and consumes Direct Locking
@PostMapping(consumes = MediaType.APPLICATION_JSON_VALUE)
@GetMapping(produces = MediaType.APPLICATION_JSON_VALUE)consumes: restricts acceptable request media types; mismatch → 415 Unsupported Media Type . produces: restricts producible response types; negotiation failure → 406 Not Acceptable .
4. Source Code Flow: From @RequestBody to write
4.1 Argument Resolution: RequestResponseBodyMethodProcessor
@RequestBodyis resolved by RequestResponseBodyMethodProcessor (extends AbstractMessageConverterMethodArgumentResolver). Its resolveArgument calls readWithMessageConverters:
protected <T> Object readWithMessageConverters(..., MethodParameter parameter, Type targetType) {
MediaType contentType = inputMessage.getHeaders().getContentType();
for (HttpMessageConverter<?> converter : this.messageConverters) {
if (converter.canRead(targetClass, contentType)) {
return ((HttpMessageConverter<T>) converter).read(targetClass, inputMessage);
}
}
throw new HttpMediaTypeNotSupportedException(contentType, ...);
}Iterates converters, picks first matching canRead, calls read. Throws 415 if none.
4.2 Return Value Handling: Same Processor
The same class implements HandlerMethodReturnValueHandler for @ResponseBody:
public void handleReturnValue(Object returnValue, MethodParameter returnType, ...) {
List<MediaType> producibleTypes = getProducibleMediaTypes(...);
MediaType selected = contentNegotiationManager.resolveMediaTypes(..., producibleTypes).get(0);
for (HttpMessageConverter<?> converter : this.messageConverters) {
if (converter.canWrite(returnClass, selectedMediaType)) {
converter.write(returnValue, selectedMediaType, outputMessage);
return;
}
}
throw new HttpMediaTypeNotAcceptableException(producibleTypes);
}Steps: 1) content negotiation to pick MediaType, 2) find first converter with canWrite, 3) write and set Content-Type. Throws 406 if none.
4.3 Key Source Classes
HttpMessageConverter– top-level interface MappingJackson2HttpMessageConverter – JSON read/write StringHttpMessageConverter – string read/write RequestResponseBodyMethodProcessor – resolves @RequestBody, handles
@ResponseBodyContentNegotiationManager– aggregates strategies HeaderContentNegotiationStrategy – Accept header based ParameterContentNegotiationStrategy – parameter based WebMvcAutoConfiguration – Spring Boot default wiring
5. Customizing Converters and Negotiation
5.1 Append Custom Converter
Implement WebMvcConfigurer, override extendMessageConverters to add on top of defaults:
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Override
public void extendMessageConverters(List<HttpMessageConverter<?>> converters) {
converters.add(0, new ProtobufHttpMessageConverter());
}
}5.2 Replace Entire Converter List
Override configureMessageConverters – replaces defaults; you must manually add all needed converters (Jackson, String, etc.). Prefer extend for most cases.
@Override
public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
converters.add(new MappingJackson2HttpMessageConverter(objectMapper));
converters.add(new StringHttpMessageConverter(StandardCharsets.UTF_8));
// add others manually
}5.3 Customize JSON Serialization
Define an ObjectMapper bean; Spring Boot injects it into Jackson converter. Also configurable via spring.jackson.* properties.
@Bean
public ObjectMapper objectMapper() {
ObjectMapper mapper = new ObjectMapper();
mapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
mapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
return mapper;
}5.4 Customize Content Negotiation
@Override
public void configureContentNegotiation(ContentNegotiationConfigurer configurer) {
configurer
.favorParameter(true)
.parameterName("format")
.defaultContentType(MediaType.APPLICATION_JSON)
.mediaType("json", MediaType.APPLICATION_JSON)
.mediaType("xml", MediaType.APPLICATION_XML);
}5.5 Complete Custom Converter Example
Example for a custom YAML format:
public class YamlMessageConverter extends AbstractHttpMessageConverter<YamlData> {
public YamlMessageConverter() {
super(MediaType.parseMediaType("application/x-yaml"));
}
@Override
protected boolean supports(Class<?> clazz) {
return YamlData.class.isAssignableFrom(clazz);
}
@Override
protected YamlData readInternal(Class<? extends YamlData> clazz, HttpInputMessage inputMessage) throws IOException {
return YamlParser.parse(inputMessage.getBody(), clazz);
}
@Override
protected void writeInternal(YamlData data, HttpOutputMessage outputMessage) throws IOException {
outputMessage.getBody().write(YamlFormatter.format(data).getBytes(StandardCharsets.UTF_8));
}
}Register it; when negotiation yields application/x-yaml, this converter handles it.
Understanding HTTP message converters, content negotiation, and the Spring MVC request pipeline is essential for backend engineering and senior interviews. Mastering these mechanisms enables precise troubleshooting of encoding issues, 415/406 errors, and format negotiation failures, and informs REST API design, serialization customization, and multi-format endpoints.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
