Content Negotiation
IntermediateSpring MVC selects the response format (JSON, XML, etc.) based on Accept headers or URL suffixes, driven by registered HttpMessageConverters.
Overview
Content negotiation is the process by which Spring MVC selects the response media type (JSON, XML, CSV, etc.) based on the client's Accept header or URL extension. Each media type is handled by an HttpMessageConverter — Jackson handles JSON (application/json), JAXB2 handles XML (application/xml), and custom converters can handle CSV, protobuf, or any other format. Spring Boot auto-registers Jackson and JAXB2 converters when their dependencies are on the classpath. Produces/consumes annotations on @RequestMapping narrow which formats a specific endpoint accepts or emits. Understanding message converter resolution order prevents common bugs where endpoints return unexpected formats.
HttpMessageConverters and Accept header negotiation
The Accept header tells the server which media type the client prefers. Spring iterates registered HttpMessageConverters in order, finds the first that can write the return type for the requested media type, and uses it. If no converter matches, it returns 406 Not Acceptable. Jackson's MappingJackson2HttpMessageConverter handles application/json; Jaxb2RootElementHttpMessageConverter handles application/xml.
// Client sends: Accept: application/xml
// Spring finds Jaxb2 converter → returns XML
@RestController
@RequestMapping("/orders")
public class OrderController {
// Endpoint produces both JSON and XML — client chooses via Accept header
@GetMapping(value = "/{id}",
produces = {MediaType.APPLICATION_JSON_VALUE,
MediaType.APPLICATION_XML_VALUE})
public Order getOrder(@PathVariable Long id) {
return orderService.findById(id).orElseThrow();
// GET /orders/1 Accept: application/json → {"id":1,...}
// GET /orders/1 Accept: application/xml → <Order><id>1</id>...</Order>
}
// Restrict endpoint to only accept JSON request body
@PostMapping(value = "/",
consumes = MediaType.APPLICATION_JSON_VALUE,
produces = MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity<Order> createOrder(@RequestBody OrderRequest req) {
// Will return 415 Unsupported Media Type if client sends XML
return ResponseEntity.ok(orderService.create(req));
}
}
// XML support requires JAXB annotations on the entity
@XmlRootElement
@XmlAccessorType(XmlAccessType.FIELD)
public class Order { ... }Registering a custom HttpMessageConverter
Implement HttpMessageConverter<T> or extend AbstractHttpMessageConverter<T> to support a custom media type (e.g., text/csv, application/x-protobuf). Register it in WebMvcConfigurer.configureMessageConverters() or as a @Bean. Spring Boot's auto-configuration already registers Jackson and JAXB2; custom converters are prepended to the list.
// Custom CSV converter for list endpoints
@Component
public class CsvHttpMessageConverter extends AbstractHttpMessageConverter<List<?>> {
public CsvHttpMessageConverter() {
super(new MediaType("text", "csv"));
}
@Override
protected boolean supports(Class<?> clazz) {
return List.class.isAssignableFrom(clazz);
}
@Override
protected List<?> readInternal(Class<? extends List<?>> clazz,
HttpInputMessage inputMessage) {
throw new UnsupportedOperationException("Read not supported");
}
@Override
protected void writeInternal(List<?> list,
HttpOutputMessage outputMessage) throws IOException {
PrintWriter writer = new PrintWriter(outputMessage.getBody());
// Write CSV header + rows using reflection or explicit mapping
list.forEach(item -> writer.println(toCsvLine(item)));
writer.flush();
}
}
// Register if not auto-detected via @Component
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Override
public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
converters.add(0, new CsvHttpMessageConverter()); // prepend
}
}
// Now: GET /orders Accept: text/csv → returns CSV
// Now: GET /orders Accept: application/json → returns JSONJackson customisation and @JsonView
Jackson is the default JSON converter. Customise it via ObjectMapper or Jackson2ObjectMapperBuilderCustomizer (auto-configured by Spring Boot). @JsonView defines "views" — different subsets of fields for different endpoints (summary vs detail). This avoids having separate DTO classes for list vs detail responses.
// Jackson global customisation (Spring Boot 3)
@Bean
public Jackson2ObjectMapperBuilderCustomizer jsonCustomizer() {
return builder -> builder
.featuresToDisable(
SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, // ISO-8601 dates
DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES)
.featuresToEnable(MapperFeature.DEFAULT_VIEW_INCLUSION)
.simpleDateFormat("yyyy-MM-dd'T'HH:mm:ssZ");
}
// @JsonView — field visibility per view
public class Views {
public interface Summary {}
public interface Detail extends Summary {} // Detail includes Summary fields
}
public class Order {
@JsonView(Views.Summary.class)
private Long id;
@JsonView(Views.Summary.class)
private String status;
@JsonView(Views.Detail.class)
private List<OrderItem> items; // only in Detail view
@JsonView(Views.Detail.class)
private String shippingAddress; // only in Detail view
}
// Controller selects view
@GetMapping
@JsonView(Views.Summary.class) // list endpoint: id + status only
public List<Order> listOrders() { ... }
@GetMapping("/{id}")
@JsonView(Views.Detail.class) // detail endpoint: all fields
public Order getOrder(@PathVariable Long id) { ... }Key Points to Remember
- 1Spring picks the HttpMessageConverter based on the Accept header (response) or Content-Type header (request body)
- 2produces = MediaType.APPLICATION_JSON_VALUE on a method restricts the endpoint to JSON output only — returns 406 otherwise
- 3consumes = MediaType.APPLICATION_JSON_VALUE restricts accepted request body type — returns 415 for other types
- 4JAXB2 XML support requires @XmlRootElement on the entity and jackson-dataformat-xml or JAXB2 on the classpath
- 5@JsonView selects field subsets per endpoint, avoiding separate DTO classes for summary vs detail views
- 6Custom HttpMessageConverters can support any media type (CSV, protobuf, YAML) by extending AbstractHttpMessageConverter
Interview Questions
Sign in to ask AriaHow does Spring MVC decide which HttpMessageConverter to use for a response?
What HTTP status code does Spring return when no converter supports the requested Accept type?
How would you add CSV export support to an existing Spring Boot REST endpoint?
What is @JsonView and how does it reduce the need for separate DTO classes per endpoint?
How would you globally configure Jackson to serialize dates as ISO-8601 strings instead of timestamps?
Ask Aria about Content Negotiation
Your personal AI tutor — ask anything about this concept
Revision Status
Personal Notes
Sign in to save personal notes for this topic.
Discussion
Sign in to join the discussion.