Skip to content

Spring Boot Content-Type Response Header#

In web application development, it is imperative to possess the capability to manage and define the Content-Type response header. The Content-Type header informs the client about the format of the data being sent in the response body. In this post, we'll explore various approaches to manage the Content-Type response header in a Spring Boot application. We'll cover examples of using @RequestMapping, ResponseEntity and ContentNegotiationConfigurer to control the Content-Type header effectively.

Using RequestMapping#

  • The @RequestMapping annotation is commonly used to map HTTP requests to controller methods in Spring Boot applications. By specifying the produces attribute, you can control the Content-Type response headers for a specific endpoint. Let's take an example as below.
CustomerController.java
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
package com.springboot.project.config.content.type.response.app.controller;

import com.springboot.project.config.content.type.response.app.model.CustomerRequest;
import com.springboot.project.config.content.type.response.app.model.CustomerResponse;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;

import java.util.ArrayList;
import java.util.List;
import java.util.UUID;

@RestController
public class CustomerController {

    private List<CustomerResponse> customers = new ArrayList<>();

    @RequestMapping(method = RequestMethod.POST, path = "/v1/customers", consumes = MediaType.APPLICATION_JSON_VALUE
            , produces = {MediaType.APPLICATION_JSON_VALUE, MediaType.APPLICATION_XML_VALUE}
    )
    public ResponseEntity<CustomerResponse> createCustomer(@RequestBody CustomerRequest customerRequest) {
        CustomerResponse customerResponse = this.toCustomerResponse(customerRequest);
        customerResponse.setId(UUID.randomUUID());
        this.customers.add(customerResponse);
        return new ResponseEntity<>(customerResponse, HttpStatus.CREATED);
    }

    @RequestMapping(method = RequestMethod.GET, path = "/v1/customers"
            ,produces = {MediaType.APPLICATION_XML_VALUE, MediaType.APPLICATION_JSON_VALUE}
    )
    public ResponseEntity<List<CustomerResponse>> getCustomers() {
        return new ResponseEntity<>(this.customers, HttpStatus.OK);
    }

    private CustomerResponse toCustomerResponse(CustomerRequest customerRequest) {
        CustomerResponse customerResponse = new CustomerResponse();
        customerResponse.setAddress(customerRequest.getAddress());
        customerResponse.setDob(customerRequest.getDob());
        customerResponse.setFullName(customerRequest.getFullName());
        customerResponse.setPhone(customerRequest.getPhone());
        customerResponse.setGender(customerRequest.getGender());
        customerResponse.setEmail(customerRequest.getEmail());
        return customerResponse;
    }

}
  • As you can see, in apis above, we are supporting produces a response with a application/json Content-Type or application/xml Content-Type base on the Accept header of the request, if there is no Accept header in the request then the first value Content-Type will be choose to response as default, in this case it is application/json.

  • Now, let's start the Spring Boot application and use postman to call 2 apis without Accept header. You will receive results as below.

 #zoom

  • For the first api, we configure MediaType.APPLICATION_JSON_VALUE as the first value, so we will receive the response with application/json as default.

 #zoom

  • Then for the second api, we configure MediaType.APPLICATION_XML_VALUE as the first value, so we will receive the response with application/xml as default.

Using ResponseEntity#

  • The ResponseEntity class gives us fine-grained control over the response, including the Content-Type header. This is particularly useful when you need to customize not only the Content-Type but also other response aspects. Let's take an example as below.
CustomerController.java
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
package com.springboot.project.config.content.type.response.app.controller;

import com.springboot.project.config.content.type.response.app.model.CustomerRequest;
import com.springboot.project.config.content.type.response.app.model.CustomerResponse;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;

import javax.validation.constraints.NotNull;
import javax.websocket.server.PathParam;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import java.util.UUID;

@RestController
public class CustomerController {

    private List<CustomerResponse> customers = new ArrayList<>();

    @RequestMapping(method = RequestMethod.POST, path = "/v1/customers", consumes = MediaType.APPLICATION_JSON_VALUE
            , produces = {MediaType.APPLICATION_JSON_VALUE, MediaType.APPLICATION_XML_VALUE}
    )
    public ResponseEntity<CustomerResponse> createCustomer(@RequestBody CustomerRequest customerRequest) {
        CustomerResponse customerResponse = this.toCustomerResponse(customerRequest);
        customerResponse.setId(UUID.randomUUID());
        this.customers.add(customerResponse);
        return new ResponseEntity<>(customerResponse, HttpStatus.CREATED);
    }

    @RequestMapping(method = RequestMethod.GET, path = "/v1/customers"
            ,produces = {MediaType.APPLICATION_XML_VALUE, MediaType.APPLICATION_JSON_VALUE}
    )
    public ResponseEntity<List<CustomerResponse>> getCustomers() {
        return new ResponseEntity<>(this.customers, HttpStatus.OK);
    }

    @RequestMapping(method = RequestMethod.GET, path = "/v1/customers/{id}")
    public ResponseEntity<CustomerResponse> getCustomerById(@NotNull @PathVariable(value = "id") UUID customerId) {
        Optional<CustomerResponse> customerResponse = this.customers.stream()
                .filter(customer -> customer.getId().equals(customerId))
                .findFirst();
        HttpHeaders headers = new HttpHeaders();
        headers.setContentType(MediaType.APPLICATION_XML);
        return customerResponse.map(response -> new ResponseEntity<>(response, headers, HttpStatus.OK))
                .orElseGet(() -> new ResponseEntity<>(HttpStatus.NOT_FOUND));
    }

    private CustomerResponse toCustomerResponse(CustomerRequest customerRequest) {
        CustomerResponse customerResponse = new CustomerResponse();
        customerResponse.setAddress(customerRequest.getAddress());
        customerResponse.setDob(customerRequest.getDob());
        customerResponse.setFullName(customerRequest.getFullName());
        customerResponse.setPhone(customerRequest.getPhone());
        customerResponse.setGender(customerRequest.getGender());
        customerResponse.setEmail(customerRequest.getEmail());
        return customerResponse;
    }

}
  • So if you into the api /v1/customers/{id} then you can see we will create an HttpHeaders and then we can set any Content-Type that we want. In this example we will set application/xml.

 #zoom

  • Now, although you set the Accept is application/json in the request header, but we will always receive the Content-Type as application/xml.

 #zoom

Using ContentNegotiationConfigurer#

  • The ContentNegotiationConfigurer provides a more centralized approach to configure content negotiation for our Spring Boot application. This allows us to define global rules for handling Content-Type headers. So this usually used for setting the default response Content-Type header if we don't use RequestMapping or ResponseEntity for setting Content-Type. So let's create WebMvcConfig.java as below.
WebMvcConfig.java
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
package com.springboot.project.config.content.type.response.app.config;

import org.springframework.context.annotation.Configuration;
import org.springframework.http.MediaType;
import org.springframework.web.servlet.config.annotation.ContentNegotiationConfigurer;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

@Configuration
public class WebMvcConfig implements WebMvcConfigurer {

    @Override
    public void configureContentNegotiation(ContentNegotiationConfigurer configurer) {
        configurer.defaultContentType(MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON);
    }

}
  • With this configuration, the default Content-Type for responses in the application will be application/xml. We can further customize this behavior based on request parameters or headers.

  • Now, let's remove all setting Content-Type with RequestMapping or ResponseEntity in the CustomerController.java as below.

CustomerController.java
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
package com.springboot.project.config.content.type.response.app.controller;

import com.springboot.project.config.content.type.response.app.model.CustomerRequest;
import com.springboot.project.config.content.type.response.app.model.CustomerResponse;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;

import javax.validation.constraints.NotNull;
import javax.websocket.server.PathParam;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import java.util.UUID;

@RestController
public class CustomerController {

    private List<CustomerResponse> customers = new ArrayList<>();

    @RequestMapping(method = RequestMethod.POST, path = "/v1/customers")
    public ResponseEntity<CustomerResponse> createCustomer(@RequestBody CustomerRequest customerRequest) {
        CustomerResponse customerResponse = this.toCustomerResponse(customerRequest);
        customerResponse.setId(UUID.randomUUID());
        this.customers.add(customerResponse);
        return new ResponseEntity<>(customerResponse, HttpStatus.CREATED);
    }

    @RequestMapping(method = RequestMethod.GET, path = "/v1/customers")
    public ResponseEntity<List<CustomerResponse>> getCustomers() {
        return new ResponseEntity<>(this.customers, HttpStatus.OK);
    }

    @RequestMapping(method = RequestMethod.GET, path = "/v1/customers/{id}")
    public ResponseEntity<CustomerResponse> getCustomerById(@NotNull @PathVariable(value = "id") UUID customerId) {
        Optional<CustomerResponse> customerResponse = this.customers.stream()
                .filter(customer -> customer.getId().equals(customerId))
                .findFirst();
        HttpHeaders headers = new HttpHeaders();
        headers.setContentType(MediaType.APPLICATION_XML);
        return customerResponse.map(response -> new ResponseEntity<>(response, headers, HttpStatus.OK))
                .orElseGet(() -> new ResponseEntity<>(HttpStatus.NOT_FOUND));
    }

    private CustomerResponse toCustomerResponse(CustomerRequest customerRequest) {
        CustomerResponse customerResponse = new CustomerResponse();
        customerResponse.setAddress(customerRequest.getAddress());
        customerResponse.setDob(customerRequest.getDob());
        customerResponse.setFullName(customerRequest.getFullName());
        customerResponse.setPhone(customerRequest.getPhone());
        customerResponse.setGender(customerRequest.getGender());
        customerResponse.setEmail(customerRequest.getEmail());
        return customerResponse;
    }


}
  • Now, let's use Postman to test, you will see all response Content-Type is application/xml.

 #zoom

 #zoom

 #zoom

  • Then now, if we set the Accept header in the request or set Content-Type with RequestMapping or ResponseEntity in the CustomerController.java then we can see all these setting will have higher priority than using ContentNegotiationConfigurer.

 #zoom

Content-Type Setting Priority Order#

  • Controller Method Annotations: The @GetMapping, @PostMapping, @RequestMapping and other mapping annotations with produces attribute have the highest priority.
  • Explicitly Set Content-Type in ResponseEntity: This method has higher precedence over global settings but lower than the produces attribute in controller annotations.
  • ContentNegotiationConfigurer: The global content negotiation settings provide default values and fallbacks when no specific Content-Type is specified at the controller level.

References#