A mechanism used to provide different versions of a resource from the same URI.
300 (Multiple Choices) and 406 for fallback formats.The browser sends a URL and several HTTP headers.
The server selects the best content to present to the client based on these headers.

The server uses Vary to indicate which headers are used - allowing optimal caching.
(If a particular header changes or is absent, it treats the request differently rather than applying a cache.)
While the most common approach, it has a few drawbacks:
The MIME type of media resources the agent wishes to process.
It can vary depending on the context (receiving a document entered in the address bar, downloading images & videos, etc.).
Typically, web browsers send something like
text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7.
html -> xml -> webp,avif ->*.*
Identifies the browser sending the request.
Some services reject requests if this User-Agent is not included.
EX) GITHUB API, Pexels
Then, what about in Spring? It can be handled very conveniently.
Even without developers writing code directly,
ContentNegotiationManager operates with various HttpMessageConverters to handle it.

Dependency injects Strategies and adds them, or adds them if they are resolvers.

?format=xml ) - deactivated.HeaderContentNegotiationStrategy is the strategy based on Accept headers we saw above.
@Override
public List<MediaType> resolveMediaTypes(NativeWebRequest request)
throws HttpMediaTypeNotAcceptableException {
...
}When ContentNegotiationStrategy returns media types it can receive based on the request,

RequestResponseBodyMethodProcessor
processes conversion based on the media type and returns it.
/**
* Writes the given return type to the given output message. * @param value the value to write to the output message
* @param returnType the type of the value
* @param inputMessage the input messages. Used to inspect the {@code Accept} header.
* @param outputMessage the output message to write to
* @throws IOException thrown in case of I/O errors
* @throws HttpMediaTypeNotAcceptableException thrown when the conditions indicated
* by the {@code Accept} header on the request cannot be met by the message converters
* @throws HttpMessageNotWritableException thrown if a given message cannot
* be written by a converter, or if the content-type chosen by the server * has no compatible converter. */@SuppressWarnings({"rawtypes", "unchecked", "NullAway"})
protected <T> void writeWithMessageConverters(@Nullable T value, MethodParameter returnType,
ServletServerHttpRequest inputMessage, ServletServerHttpResponse outputMessage)
throws IOException, HttpMediaTypeNotAcceptableException, HttpMessageNotWritableException {
...
if (selectedMediaType != null) {
selectedMediaType = selectedMediaType.removeQualityValue();
ResolvableType targetResolvableType = null;
for (HttpMessageConverter converter : this.messageConverters) {
...
switch (converterTypeToUse) {
case BASE -> converter.write(body, selectedMediaType, outputMessage);
case GENERIC -> ((GenericHttpMessageConverter) converter).write(body, targetType, selectedMediaType, outputMessage);
case SMART -> ((SmartHttpMessageConverter) converter).write(body, targetResolvableType, selectedMediaType, outputMessage, null);
}Selecting a converter based on the MediaType, it intelligently handles Content Negotiation by constructing the body.
But doesn't this seem a bit too automatic?
You can configure negotiations from the WebMvcConfigurer provided by Spring.
@Override
public void configureContentNegotiation(ContentNegotiationConfigurer configurer) {
// Use Accept Header strategy (default true)
configurer.ignoreAcceptHeader(false)
.defaultContentType(MediaType.APPLICATION_JSON);
}You can specify the default MediaType if no negotiation is found through default.
Actually, I studied this because of this content. Haha.
implementation 'com.fasterxml.jackson.dataformat:jackson-dataformat-xml'While handling this issue, there was a requirement to parse xml, so I added the dependency.

The API, which was supposed to output as JSON,

was outputting as XML.
@Autowired
List<HttpMessageConverter> messageConverters;
@PostConstruct
public void init() {
log.info("Application started : {}", messageConverters);
}You can print out the HttpMessageConverter anywhere like this,
[org.springframework.http.converter.StringHttpMessageConverter@13d26ed3, org.springframework.http.converter.xml.MappingJackson2XmlHttpMessageConverter@39f1bf06, org.springframework.http.converter.json.MappingJackson2HttpMessageConverter@2cea567b]and you can see that XmlHttpMessageConverter is added.
@Override
public void extendMessageConverters(List<HttpMessageConverter<?>> converters) {
converters.removeIf(converter -> converter instanceof MappingJackson2XmlHttpMessageConverter);
}Identify XmlConverter by using instanceof and remove it.
Then, you can see it returns JSON again as before!