In project development, the same resource usually needs to be provided to different clients in multiple representations. For example, a browser may want to get HTML pages, while a mobile application may require JSON data. This mechanism of dynamically selecting the response format according to client needs is content negotiation.
Content negotiation can realize the need to serve multiple clients on the same API endpoint, greatly improving the flexibility and reusability of Web services. As the mainstream Java application development framework, SpringBoot provides powerful and flexible content negotiation support, allowing developers to easily implement resource expressions in multiple forms.
Content negotiation basis
What is content negotiation
Content negotiation is an important concept in the HTTP protocol, allowing the same resource URL to provide representations in different formats according to the client's preferences. This process is usually done by the server and the client: the client informs the server of its expected content type, and the server selects the most appropriate form of expression based on its own capabilities to return.
Content negotiation mainly relies on media type (Media Type), also known as MIME type, such asapplication/json
、application/xml
、text/html
wait.
Content Negotiation Structure in SpringBoot
SpringBoot is based on Spring MVC's content negotiation mechanism, which is implemented through the following components:
- ContentNegotiationManager: Responsible for coordinating the content negotiation process
- ContentNegotiationStrategy: Define how to determine the media type requested by the client
- HttpMessageConverter: Responsible for converting between Java objects and HTTP request/responsive bodies
SpringBoot supports multiple content negotiation policies by default, which can be configured and combined according to needs.
Policy 1: Content negotiation based on request header
Principle analysis
Request header-based content negotiation is one of the most consistent ways to comply with the HTTP specification, which checks theAccept
Header to determine the response format expected by the client. For example, when the client sendsAccept: application/json
When header, the server will return data in JSON format first.
This strategyHeaderContentNegotiationStrategy
Implementation is SpringBoot's default content negotiation strategy.
Configuration method
In SpringBoot, content negotiation based on request headers is enabled by default, without additional configuration. If explicit configuration is required, you canor
Added in:
spring: mvc: contentnegotiation: favor-parameter: false favor-path-extension: false
Or via Java configuration:
@Configuration public class WebConfig implements WebMvcConfigurer { @Override public void configureContentNegotiation(ContentNegotiationConfigurer configurer) { configurer .defaultContentType(MediaType.APPLICATION_JSON) .favorParameter(false) .favorPathExtension(false) .ignoreAcceptHeader(false); // Make sure that Accept headers are not ignored } }
Practical examples
First, create a basic REST controller:
@RestController @RequestMapping("/api/products") public class ProductController { private final ProductService productService; public ProductController(ProductService productService) { = productService; } @GetMapping("/{id}") public Product getProduct(@PathVariable Long id) { return (id); } @GetMapping public List<Product> getAllProducts() { return (); } }
The client can passAccept
Header requests data in different formats:
// Request JSON formatGET /api/products HTTP/1.1 Accept: application/json // Request XML formatGET /api/products HTTP/1.1 Accept: application/xml // Request HTML formatGET /api/products HTTP/1.1 Accept: text/html
To support XML responses, you need to add relevant dependencies:
<dependency> <groupId></groupId> <artifactId>jackson-dataformat-xml</artifactId> </dependency>
Pros and cons analysis
advantage
- Comply with HTTP specifications and is a recommended practice for RESTful API
- No need to modify the URL, keep the URL simple
- Applicable to all HTTP clients
- Cache-friendly
shortcoming
- Need to be correctly set by the client
Accept
head - It is not convenient to directly test different formats in the browser
- Some proxy servers may modify or remove HTTP headers
Applicable scenarios
- RESTful API Design
- API interface for programmatic clients
- When multiple clients need different representations of the same data
Strategy 2: Content negotiation based on URL path extension
Principle analysis
Content negotiation based on URL path extension determines the response format expected by the client through the file extension at the end of the URL. For example,/api/
Request JSON format, and/api/
Request XML format.
This strategyPathExtensionContentNegotiationStrategy
To implement, it is important to note that starting from Spring 5.3, this policy has been disabled by default for security reasons.
Configuration method
existor
Enable in:
spring: mvc: contentnegotiation: favor-path-extension: true # Clearly specify the mapping relationship between path extension and media type media-types: json: application/json xml: application/xml html: text/html
Or via Java configuration:
@Configuration public class WebConfig implements WebMvcConfigurer { @Override public void configureContentNegotiation(ContentNegotiationConfigurer configurer) { configurer .favorPathExtension(true) .ignoreAcceptHeader(false) .defaultContentType(MediaType.APPLICATION_JSON) .mediaType("json", MediaType.APPLICATION_JSON) .mediaType("xml", MediaType.APPLICATION_XML) .mediaType("html", MediaType.TEXT_HTML); } }
Safety precautions
Since path expansion policy may lead to path traversal attacks, it is disabled by default after Spring 5.3. If necessary, it is recommended:
useUrlPathHelper
Security configuration:
@Configuration public class WebConfig implements WebMvcConfigurer { @Override public void configurePathMatch(PathMatchConfigurer configurer) { UrlPathHelper urlPathHelper = new UrlPathHelper(); (false); (urlPathHelper); } }
Clearly define supported media types to avoid automatic detection
Practical examples
There is no need to modify the controller. After configuring the extension policy, the client can access it through the URL extension:
// Request JSON formatGET /api/ // Request XML formatGET /api/
To better support path extensions, you can use URL rewrite filters:
@Component public class UrlRewriteFilter extends OncePerRequestFilter { @Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { HttpServletRequest wrappedRequest = new HttpServletRequestWrapper(request) { @Override public String getRequestURI() { String uri = (); return urlRewrite(uri); } }; (wrappedRequest, response); } private String urlRewrite(String url) { // Implement URL rewriting logic, such as adding missing file extensions return url; } }
Pros and cons analysis
advantage
- Easy to test different formats in your browser
- No special HTTP header required
- The URL intuitively indicates the desired response format
shortcoming
- Not in line with RESTful API design principles (the same resource has multiple URIs)
- There is a security risk (path traversal attack)
- After Spring 5.3, it is disabled by default and requires additional configuration
- May conflict with certain web frameworks or routing systems
Applicable scenarios
- Quickly switch different response formats in development and testing environment
- Traditional web applications need to provide multiple formats at the same time
- Need to support clients that cannot easily modify HTTP headers
Strategy 3: Content negotiation based on request parameters
Principle analysis
Based on the content negotiation of request parameters, the response format expected by the client is determined by querying parameters through URLs. For example,/api/products?format=json
Request JSON format, and/api/products?format=xml
Request XML format.
This strategyParameterContentNegotiationStrategy
Implementation, needs to be explicitly enabled.
Configuration method
existor
Configuration in:
spring: mvc: contentnegotiation: favor-parameter: true parameter-name: format # Default is "format", customizable media-types: json: application/json xml: application/xml html: text/html
Or via Java configuration:
@Configuration public class WebConfig implements WebMvcConfigurer { @Override public void configureContentNegotiation(ContentNegotiationConfigurer configurer) { configurer .favorParameter(true) .parameterName("format") .ignoreAcceptHeader(false) .defaultContentType(MediaType.APPLICATION_JSON) .mediaType("json", MediaType.APPLICATION_JSON) .mediaType("xml", MediaType.APPLICATION_XML) .mediaType("html", MediaType.TEXT_HTML); } }
Practical examples
Using the previous controller, the client accesses different formats by adding query parameters:
// Request JSON formatGET /api/products?format=json // Request XML formatGET /api/products?format=xml
Pros and cons analysis
advantage
- Easy to test different formats in the browser
- No modification to the resource's basic URL path
- More secure than path extension
- Simple configuration, easy to understand
shortcoming
- Not fully compliant with RESTful API design principles
- Increases the complexity of the URL
- May be confused with other query parameters in the application
- Not cache friendly (same URL returns different content)
Applicable scenarios
- API documentation or test page for developers
- You need to directly test different response formats in the browser
- Public APIs require simple format switching mechanisms
- Inconvenient to set up HTTP header environment
Combination strategy to achieve advanced content negotiation
Policy combination configuration
In practical applications, multiple strategies are often combined to provide maximum flexibility:
@Configuration public class WebConfig implements WebMvcConfigurer { @Override public void configureContentNegotiation(ContentNegotiationConfigurer configurer) { configurer .favorParameter(true) .parameterName("format") .ignoreAcceptHeader(false) // Don't ignore Accept header .defaultContentType(MediaType.APPLICATION_JSON) .mediaType("json", MediaType.APPLICATION_JSON) .mediaType("xml", MediaType.APPLICATION_XML) .mediaType("html", MediaType.TEXT_HTML); } }
This configuration enables content negotiation based on parameters and request headers, and uses parameters first, and if there are no parameters, use Accept headers.
Customize content negotiation strategy
For more complex requirements, customizableContentNegotiationStrategy
:
public class CustomContentNegotiationStrategy implements ContentNegotiationStrategy { @Override public List<MediaType> resolveMediaTypes(NativeWebRequest request) throws HttpMediaTypeNotAcceptableException { String userAgent = ("User-Agent"); // Content negotiation based on User-Agent if (userAgent != null) { if (("Mozilla")) { return (MediaType.TEXT_HTML); } else if (("Android") || ("iPhone")) { return (MediaType.APPLICATION_JSON); } } // Return JSON by default return (MediaType.APPLICATION_JSON); } }
Register a custom policy:
@Configuration public class WebConfig implements WebMvcConfigurer { @Override public void configureContentNegotiation(ContentNegotiationConfigurer configurer) { (( new CustomContentNegotiationStrategy(), new HeaderContentNegotiationStrategy() )); } }
Response optimization practice
Provide optimized output for different manifestations:
@RestController @RequestMapping("/api/products") public class ProductController { private final ProductService productService; // General JSON/XML response @GetMapping("/{id}") public ProductDto getProduct(@PathVariable Long id) { Product product = (id); return new ProductDto(product); // Convert to DTO to avoid physical class exposure } // Special treatment for HTML @GetMapping(value = "/{id}", produces = MediaType.TEXT_HTML_VALUE) public ModelAndView getProductHtml(@PathVariable Long id) { Product product = (id); ModelAndView mav = new ModelAndView("product-detail"); ("product", product); ("relatedProducts", (product)); return mav; } // Simplified response to mobile clients @GetMapping(value = "/{id}", produces = "application/+json") public ProductMobileDto getProductForMobile(@PathVariable Long id) { Product product = (id); return new ProductMobileDto(product); // Contains streamlined information required by mobile terminal } }
in conclusion
SpringBoot provides a flexible and powerful content negotiation mechanism to meet the needs of various application scenarios. In actual development, appropriate policies or combination policies should be selected according to specific needs, while paying attention to security, performance and API design best practices.
This is the article about SpringBoot customization of content negotiation. For more relevant SpringBoot content negotiation content, please search for my previous articles or continue browsing the related articles below. I hope everyone will support me in the future!