Spring defaultContentType ignored - spring

I have a Spring 3.2.4 application with the following mvc setup:
#Configuration
public class WebConfig extends WebMvcConfigurationSupport {
#Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/assets/**").addResourceLocations("/assets/");
}
#Override
public void configureContentNegotiation(ContentNegotiationConfigurer configurer) {
configurer.defaultContentType(MediaType.APPLICATION_JSON);
configurer.mediaType("json", MediaType.APPLICATION_JSON);
configurer.mediaType("xml", MediaType.APPLICATION_XML);
}
when I access my controller's url like /rest/products.json I get JSON response and via /rest/products.xml I get XML as expected.
But when I accesss /rest/products I get XML, but I expected JSON as I set that as default content type.
This seems to be ignored by Spring.

The Spring documentation says:
This content type will be used when neither the request path
extension, nor a request parameter, nor the Accept header could help
determine the requested content type.
So there are at least three ways to tell Spring which format you are trying to receive. My guess would be that you are still telling Spring somehow that you want to get the data in XML, maybe in a requests parameter or - more likely as it is not that visible - in a header.
Try checking the headers of your request.

Related

Request Attribute not found after Spring Security added to Spring Boot app

I have a Spring Boot application that is running. As soon as I added Spring Security, the app generated an error.
I have a form that is backed by a bean. When I enable Spring Security, the bean for the form cannot be found. Before I added Spring Security, the bean and form worked.
The error that I receive after making a GET request to the form is
Neither BindingResult nor plain target object for bean name 'orderActive' available as request attribute
The form is using the ThymeLeaf package.
Spring Security Configuration
#Configuration
#EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth
.inMemoryAuthentication()
.withUser("buzz")
.password("{noop}infinity")
.authorities("ROLE_USER");
}
}
Controller Method
#GetMapping("/orders/current")
public String orderForm() {
return "orderForm";
}
Test Class Annotations
#SpringBootTest
#AutoConfigureMockMvc
class DesignTacoControllerTest {
Test Method
#WithMockUser("buzz")
#Test
public void testProcessDesignGet() throws Exception {
mockMvc.perform(get("/orders/current")
.requestAttr("orderActive", new Order()))
.andExpect(status().isOk());
}
orderForm
<form method="POST" th:action="#{/orders}" th:object="${orderActive}">
I have tried adding a RequestAttribute to the controller method.
#GetMapping("/orders/current")
public String orderForm(#RequestAttribute("orderActive") Order orderActive) {
return "orderForm";
}
When I debug, the order has the same ID as the one that was added in the test method. The next step is to render the view. When I continue, the error appears.
Somewhere between the controller method and the view, the request parameter disappears. It has something to do with security, since the code runs without security enabled. The order form is found, so the page is not forbidden. Does security disable the request attributes?
You say it worked before Security, but, do you have a class (DTO) OrderForm with the fields you need in your form? I don't see one. If you don't create one and then add it to the model (that's the Binding part):
#GetMapping("/orders/current")
public String orderForm(Model model) {
model.addAttribute("orderForm", new OrderForm())
return "orderForm";
}

SpringBoot controller returns XML although content-type set to JSON explicitly

I have 2 spring boot REST-services which interact as producer and consumer.
One is producer of JSON content and the other is the consumer.
In the consumer service I use Spring's RestTemplate to invoke the producer's endpoint and get the result.
Code
Producer's relevant code for controller's POST endpoint is as follows:
#RequestMapping(value = "/cars", method = RequestMethod.POST)
public ResponseEntity<Cars> getCars(/* methods params */) {
// some code here
HttpHeaders respHeader = new HttpHeaders();
respHeader.set("Content-Type", "application/json");
ResponseEntity<Cars> resp = new ResponseEntity<Cars>(cars, respHeader, HttpStatus.OK);
return resp;
}
Notice in the producer code above:
I don't have produces="application/json" specified explicitly in the RequestMapping annotation.
However, I set content-type as "application/json" in the response headers.
Issue
But consumer gets the response in XML instead of JSON.
Jackson is there in the class path.
Questions
What explains returning XML and not JSON even though content-type explicitly set in response-headers to "application/json"?
Assume the response-header doesn't have any effect, what is the default - is it XML instead of JSON?
You can configure the default content-type for any response by customizing Spring's WebMvcConfigurer.
To do so add following code to a given configuration class that is annotated with #Configuration:
#Bean
public WebMvcConfigurer customConfigurer() {
return new WebMvcConfigurer() {
#Override
public void configureContentNegotiation(ContentNegotiationConfigurer configurer) {
configurer.defaultContentType(MediaType.APPLICATION_JSON);
}
};
}
You could set a breakpoint in Spring's ContentTypeDelegatingMessageConverter and debug to see the resolved message-converter for your response.
Content Negotiation
The concept and process between a web-client and -server determining the content-type of a response is called Content-Negotiation.
Since Spring MVC the registered WebMvcConfigurer and HttpMessageConverters are responsible for the representation and content-type of the response.
In SpringBoot those are autoconfigured.
For any #RestController or a controller-endpoint having #ResponseBody the default content-type of the response is XML, if Jackson is available in the classpath and auto-configured successfully, the default content-type should be JSON.
See also:
tutorial on Baeldung: Spring MVC Content Negotiation
How to make spring boot default to application/json;charset=utf-8 instead of application/json;charset=iso-8859-1
It appears that the order of HttpMessageConverters matters. I encountered the same problem with default xml responses when there is a transitive xml dependency. Adding MappingJackson2HttpMessageConverter to the top of converters list solved it for me.
For some reason configurer.defaultContentType(MediaType.APPLICATION_JSON) this suggestion from previous answers breaks apis that produce media types other than json (when requests does not contain Accept header). These apis started returning http status 406 with json as the defaultContentType
My Spring Boot version is 2.7.1
Here is the configuration class:
#Configuration
#RequiredArgsConstructor
public class WebMvcConfiguration implements WebMvcConfigurer {
private final ObjectMapper objectMapper;
#Override
public void extendMessageConverters(#NotNull List<HttpMessageConverter<?>> converters) {
converters.add(0, new MappingJackson2HttpMessageConverter(objectMapper));
}
}
When there is no default content type defined, xml becomes the default since spring iterates over converters to decide produceable content types. And since xml converter is added before json converter it uses xml. Adding json converter ahead of xml converter fixes this.

Spring boot - setting default Content-type header if it's not present in request

I'm having the following problem: suppose I sometimes receive POST requests with no Content-type header set. In this case I want to assume that Content-type=application/json by default.
Can I achieve this somehow using spring boot features and not using filters?
Thanks
As of Spring Boot 2.x, you need to create a class that extends the WebMvcConfigurer interface, e.g.:
#Configuration
class WebMvcConfiguration implements WebMvcConfigurer {
#Override
public void configureContentNegotiation( ContentNegotiationConfigurer configurer )
{
configurer.defaultContentType( MediaType.APPLICATION_JSON );
}
}
Under 1.x, you could do the same thing with WebMvcConfigurerAdapter, which is now deprecated.
This will affect both request and response bodies, so if you do not have a "produces" parameter explicitly set, and you wanted something other than application/json, it's going to get coerced to application/json.

Spring Annotation-Based Interceptor

can any of you please post a snippet for the following. I've looked in a couple of places but they all seem to be xml based. I'd like to have this code in java configuration style only.
I am trying to accomplish the following..
1) I need to intercept a specific url, for example "http://localhost:8080/test" and only a url that starts with "/test/*". This is not a spring security question, this is just for an endpoint I like to intercept.
2) I need to intercept the HttpServletRequest object of that request and add a specific header to that request. For example, "authorization", "bearer xxxx".
3) When the /test endpoint finally hits, I should be able to see the authorization header inside my HttpServletRequest object.
Thank you guys.
you can do this like this:
(Whithin "YourOwnInterceptor" you can add the value to the header...)
#Configuration
public class MvcConfig extends WebMvcConfigurerAdapter {
#Bean
public YourOwnInterceptor yourOwnInterceptor() {
return new YourOwnInterceptor();
}
#Override
public void addInterceptors(InterceptorRegistry registry) {
super.addInterceptors(registry);
registry.addInterceptor(yourOwnInterceptor()).
addPathPatterns("/<your-url-to-intercept>/**");
}
}

Spring HATEOAS (w Spring Boot) JAXB marshal error when returning a Resources<T> or PagedResources<T> result

I've got something like this in my controller:
#RequestMapping
#ResponseBody
public HttpEntity<PagedResources<PromotionResource>> promotions(
#PageableDefault(size = RestAPIConfig.DEFAULT_PAGE_SIZE, page = 0) Pageable pageable,
PagedResourcesAssembler<Promotion> assembler
){
PagedResources<PromotionResource> r = assembler.toResource(this.promoService.find(pageable), this.promoAssembler);
return new ResponseEntity<PagedResources<PromotionResource>>(r, HttpStatus.OK);
}
If i navigate to the URL mapped to that controller method i get a 500 error with a root cause of:
com.sun.istack.internal.SAXException2: unable to marshal type "commerce.api.rest.resources.PromotionResource " as an element because it is missing an #XmlRootElement annotation
If i throw a #XmlRootElement annotation on my resource it becomes this error:
com.sun.istack.internal.SAXException2: unable to marshal type "commerce.api.rest.resources.PromotionResource " as an element because it is not known to this context.
Everything is fine if the accept header is application/json or application/hal+json. The problem is caused only when the client (in this case chrome) is looking for application/xml (which makes sense as HATEOAS is following the clients requests. I'm using spring boot's #EnableAutoConfiguration which is adding the XML message converter to the list and thus enabling XML content types.
I'm guessing i have at least 2 options:
1. fix the jaxb error
2. remove xml as a supported content type
not sure how to do either, or maybe there's another option.
If you don't actually want to produce XML try using the produces attribute of the #RequestMapping annotation. Something like: #RequestMapping(produces=MediaType.APPLICATION_JSON_VALUE)
Alternatively you could exclude jaxb from you classpath or look at adding your own org.springframework.boot.autoconfigure.web.HttpMessageConverters bean to take complete control of the registered HttpMessageConverter's. See WebMvcConfigurationSupport.addDefaultHttpMessageConverters to see what Spring will add by default.
Not sure this is a good technique, and it looks like in 1.1.6 there's a different approach. Here's what i did:
#Configuration
public class WebMVCConfig extends WebMvcAutoConfiguration.WebMvcAutoConfigurationAdapter {
#Override
public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
//Remove the Jaxb2 that is automatically added because some other dependency brings it into the classpath
List<HttpMessageConverter<?>> baseConverters = new ArrayList<HttpMessageConverter<?>>();
super.configureMessageConverters(baseConverters);
for(HttpMessageConverter<?> c : baseConverters){
if(!(c instanceof Jaxb2RootElementHttpMessageConverter)){
converters.add(c);
}
}
}
}
if you don't want to support XML converter, you can extend spring WebMvcConfigurer to exclude XML message converters.
#Configuration
public class WebMVCConfig extends WebMvcAutoConfiguration.WebMvcAutoConfigurationAdapter {
#Override
public void extendMessageConverters(List<HttpMessageConverter<?>> converters) {
converters.removeIf(c -> c instanceof AbstractXmlHttpMessageConverter<?>);
}
}

Resources