The 30 second answer
An HTTP API returns a response with a status, headers and sometimes a body, not necessarily JSON. The client uses Accept to describe the representations it can receive. Content-Type describes the body actually sent. Spring MVC combines the client's preferences, the endpoint's declared output types and available message converters. To return XML, I serialize the response model with an XML converter rather than just changing the content-type header on JSON bytes.
Start with the response contract. An HTTP API returns status/control metadata, headers and, when permitted by the method/status, content. JSON and XML are representation formats, not definitions of an API. HEAD responses have no content; neither do 204 and 304 responses. An endpoint may return a file, text, an image, a stream or an empty response. A Java controller's return object is an implementation detail until the framework converts it to a wire representation. ResponseEntity can control status, headers and body; @ResponseBody/@RestController return values are written through an HttpMessageConverter.
Negotiation versus representation metadata. Accept is a request preference for response media types, optionally with quality values. Content-Type describes the representation in the message that carries it. For a POST, request Content-Type: application/json can coexist with Accept: application/xml: the client sends JSON and wants XML back. Accept-Encoding/gzip is about content coding, a different dimension from choosing JSON versus XML. For a representation that varies by Accept, use the appropriate Vary: Accept response metadata so shared caches do not mix variants.
POST /reports
Content-Type: application/json
Accept: application/xml, application/json;q=0.5
{"name":"daily"}If this endpoint supports XML and JSON, a compatible XML response is preferred by the stated quality values. The actual response must carry Content-Type matching the bytes it sends. RFC 9110 allows a server to send 406 or disregard unacceptable preferences in defined circumstances; do not describe 406 as the only protocol-legal outcome. A Spring MVC endpoint with no compatible declared representation/converter commonly results in HttpMediaTypeNotAcceptableException/406. Unsupported request media type is a different problem, commonly HttpMediaTypeNotSupportedException/415. Invalid JSON syntax is not the same as an unsupported media type.
Spring MVC mechanism. Request mapping can narrow incoming representations with consumes and outgoing ones with produces. A return-value handler takes the model/ResponseEntity body, selects a compatible media type and asks a converter that can write that Java type/media type. Listing application/xml in produces does not magically install an XML serializer. Returning a pre-serialized JSON String can also bypass the intended object-to-XML path; return a DTO, not a JSON string disguised as an object.
JSON-to-XML scenario. If you own the endpoint, expose the same response model in both formats and let negotiation select its serializer. Do not generate JSON, replace braces with tags, or merely rename the Content-Type. Ensure an XML converter and its dependencies are installed, and define XML roots, collection wrappers, null handling and field names as part of the contract. JSON object/array structure is not a complete XML schema. If adapting a third-party JSON response, parse it into a bounded, validated model then serialize to the target XML contract; preserve meaningful status/errors rather than always wrapping everything in HTTP 200.
For example, in a Spring Framework 6.2/Jackson 2 application, the official MVC configuration supports MappingJackson2HttpMessageConverter and MappingJackson2XmlHttpMessageConverter with Jackson's XML support. This is a version-scoped example, not an instruction to copy those class names into every later major version. Prefer framework-managed converters in Spring Boot; replacing converter configuration carelessly can remove defaults. The mapping annotation itself can express both representations:
@GetMapping(path = "/reports/{id}",
produces = {"application/json", "application/xml"})
public ReportDto report(@PathVariable long id) {
return reportService.find(id); // DTO + installed JSON/XML converters
}This is a controller fragment, not a complete application. ReportDto, service and XML field/root mapping belong to the application's contract. If only JSON is supported, declare only JSON; avoid claiming XML support that fails during serialization.
Verification and diagnosis. Request the same resource separately with Accept: application/json and Accept: application/xml; check status, Content-Type and parsed field values, not raw string equality. Test quality-value preferences, wildcard Accept, unsupported response type, unsupported request Content-Type, malformed JSON, null/list XML shapes and negotiated errors. A 406 points to mapping/output compatibility; a 415 points to incoming content type/converter compatibility. If JSON bytes arrive with an XML header, inspect manual serialization or a gateway rewrite. If clients intermittently receive another format, inspect Vary and cache keys. Keep these tests distinct from API versioning: format selection and semantic contract evolution solve different problems.