Backend Development 6 min read

How Spring Boot Chooses Response Formats and Customizes JSON, XML, and YAML Output

This article explains Spring Boot's default JSON response, the internal HttpMessageConverter selection process, and how to configure XML and custom YAML responses by adding Jackson dependencies and implementing a custom HttpMessageConverter.

Spring Full-Stack Practical Cases
Spring Full-Stack Practical Cases
Spring Full-Stack Practical Cases
How Spring Boot Chooses Response Formats and Customizes JSON, XML, and YAML Output

Environment: Spring Boot 3.0.5

Message Format Conversion Principle

By default a @RestController returns JSON. Example controller:

<code>@RestController
@RequestMapping("/rmf")
public class ResponseMessageFormatController {
    @GetMapping("/index")
    public Users index() {
        return new Users(1, "张飒", 66, "男");
    }
}
</code>

When no Accept header is sent, Spring treats the request as accepting any media type ( */* ), iterates over all HttpMessageConverter s, and selects the first concrete MediaType that is not a wildcard. application/json is first, so JSON is returned via MappingJackson2HttpMessageConverter .

Core source code that performs the selection:

<code>public abstract class AbstractMessageConverterMethodProcessor {
    protected <T> void writeWithMessageConverters(...) {
        List<MediaType> acceptableTypes;
        try {
            // get Accept header, default */*
            acceptableTypes = getAcceptableMediaTypes(request);
        }
        List<MediaType> producibleTypes = getProducibleMediaTypes(request, valueType, targetType);
        List<MediaType> compatibleMediaTypes = new ArrayList<>();
        determineCompatibleMediaTypes(acceptableTypes, producibleTypes, compatibleMediaTypes);
        for (MediaType mediaType : compatibleMediaTypes) {
            if (mediaType.isConcrete()) {
                selectedMediaType = mediaType;
                break;
            } else if (mediaType.isPresentIn(ALL_APPLICATION_MEDIA_TYPES)) {
                selectedMediaType = MediaType.APPLICATION_OCTET_STREAM;
                break;
            }
        }
        if (selectedMediaType != null) {
            selectedMediaType = selectedMediaType.removeQualityValue();
            for (HttpMessageConverter<?> converter : this.messageConverters) {
                GenericHttpMessageConverter genericConverter = (converter instanceof GenericHttpMessageConverter ? (GenericHttpMessageConverter) converter : null);
                if (genericConverter != null ? ((GenericHttpMessageConverter) converter).canWrite(targetType, valueType, selectedMediaType) : converter.canWrite(valueType, selectedMediaType)) {
                    body = getAdvice().beforeBodyWrite(body, returnType, selectedMediaType, (Class<? extends HttpMessageConverter<?>>) converter.getClass(), inputMessage, outputMessage);
                    ((HttpMessageConverter) converter).write(body, selectedMediaType, outputMessage);
                }
            }
        }
    }
}
</code>

Returning XML format

To enable XML, add the Jackson XML dependency:

<code>&lt;dependency&gt;
    &lt;groupId&gt;com.fasterxml.jackson.dataformat&lt;/groupId&gt;
    &lt;artifactId&gt;jackson-dataformat-xml&lt;/artifactId&gt;
&lt;/dependency&gt;
</code>

After adding the dependency, setting Accept: application/xml makes Spring automatically use MappingJackson2XmlHttpMessageConverter to produce XML.

JSON response
JSON response
XML response
XML response

Custom Message Format (YAML)

To support YAML, add the Jackson YAML dependency:

<code>&lt;dependency&gt;
    &lt;groupId&gt;com.fasterxml.jackson.dataformat&lt;/groupId&gt;
    &lt;artifactId&gt;jackson-dataformat-yaml&lt;/artifactId&gt;
&lt;/dependency&gt;
</code>

Create a custom HttpMessageConverter for the application/yaml media type:

<code>@Component
public class YamlHttpMessageConverter extends AbstractHttpMessageConverter<Object> {

    public YamlHttpMessageConverter() {
        super(new MediaType("application", "yaml"));
    }

    @Override
    protected boolean supports(Class<?> clazz) {
        return true;
    }

    @Override
    protected Object readInternal(Class<? extends Object> clazz, HttpInputMessage inputMessage)
            throws IOException, HttpMessageNotReadableException {
        return null;
    }

    @Override
    protected void writeInternal(Object t, HttpOutputMessage outputMessage)
            throws IOException, HttpMessageNotWritableException {
        try (OutputStream os = outputMessage.getBody()) {
            YAMLFactory factory = new YAMLFactory();
            // Remove leading "---"
            factory.configure(Feature.WRITE_DOC_START_MARKER, false);
            ObjectMapper mapper = new ObjectMapper(factory);
            os.write(mapper.writeValueAsString(t).getBytes(StandardCharsets.UTF_8));
        }
    }
}
</code>

Testing with Postman and Accept: application/yaml returns the correct YAML representation.

YAML response
YAML response
backend developmentJSONSpring BootXMLyamlHttpMessageConverter
Spring Full-Stack Practical Cases
Written by

Spring Full-Stack Practical Cases

Full-stack Java development with Vue 2/3 front-end suite; hands-on examples and source code analysis for Spring, Spring Boot 2/3, and Spring Cloud.

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.