How to make spring boot default to application/json;charset=utf-8 instead of application/json;charset=iso-8859-1 - spring

I am updating spring-boot from 1.3.6 to 2.1.3 and while before responses had content type application/json;charset=UTF-8, now I am getting a charset of iso-8859-1.
I would like to have utf 8.
My controller looks like this:
#Controller
public class MyController {
#RequestMapping(value={"/myService/{serviceId}"}, method=RequestMethod.POST, consumes="application/json")
public ResponseEntity<Void> handlePostServiceId(final InputStream requestInputStream,
#PathVariable String serviceId,
final HttpServletRequest servletRequest,) {
<$businessLogic>
return new ResponseEntity<>(new HttpHeaders(), HttpStatus.ACCEPTED);
}
I can get it to return utf-8 if I include produces= MediaType.APPLICATION_JSON_UTF8_VALUE in my #RequestMapping but I would like to only have to set that once, rather than for every single API.
I also tried adding
#Override
public void configureContentNegotiation(
ContentNegotiationConfigurer configurer) {
configurer.defaultContentType(MediaType.APPLICATION_JSON_UTF8_VALUE);
}
to my WebMvcConfigurer as suggested here: https://stackoverflow.com/a/25275291/2855921 but that broke my availability api which consumes plain/text content type.
I also ensured that my request was UTF-8, so it is not just mirroring back the format I gave.
Any ideas on how I can set the charset to be UTF-8 for the entire project?

Add the below properties to the application.properties file:
For Spring Boot 1.x
# Charset of HTTP requests and responses. Added to the "Content-Type"
# header if not set explicitly.
spring.http.encoding.charset=UTF-8
# Enable http encoding support.
spring.http.encoding.enabled=true
# Force the encoding to the configured charset on HTTP requests and responses.
spring.http.encoding.force=true
Source: https://docs.spring.io/spring-boot/docs/1.5.22.RELEASE/reference/html/common-application-properties.html
For Spring Boot 2.x
server.servlet.encoding.charset=UTF-8
server.servlet.encoding.force-response=true
Source: https://docs.spring.io/spring-boot/docs/current/reference/html/appendix-application-properties.html#server.servlet.encoding.charset

If you're using default object mapper (Jackson) then the encoding can be forced with this simple configuration:
#Bean
public MappingJackson2HttpMessageConverter mappingJackson2HttpMessageConverter() {
MappingJackson2HttpMessageConverter jsonConverter = new MappingJackson2HttpMessageConverter();
jsonConverter.setDefaultCharset(StandardCharsets.UTF_8);
return jsonConverter;
}
Here the MappingJackson2HttpMessageConverter() constructor uses public Jackson2ObjectMapperBuilder class to set default parameters for an object mapper.
For another object mapper (Gson or Jsonb) you can look into AllEncompassingFormHttpMessageConverter() constructor.
Note also MediaType.APPLICATION_JSON_UTF8 was deprecated in favor of MediaType.APPLICATION_JSON since Spring 5.2.
So in tests I prefer to use contentTypeCompatibleWith(...) instead of contentType(..._UTF8):
MvcResult result = mockMvc.perform(get("/api/resource"))
.andDo(print())
.andExpect(status().isOk())
// .andExpect(content().contentType(MediaType.APPLICATION_JSON_UTF8))
.andExpect(content().contentTypeCompatibleWith(MediaType.APPLICATION_JSON))
.andReturn();
Links:
Commit where APPLICATION_JSON_UTF8 was replaced by APPLICATION_JSON in MappingJackson2HttpMessageConverter — https://github.com/spring-projects/spring-framework/commit/c38542739734c15e84a28ecc5f575127f25d310a
Chromium issue discussing "Content-Type: application/json" support —https://bugs.chromium.org/p/chromium/issues/detail?id=438464
No "charset" for "Content-Type: application/json" in RFC7159 — https://www.rfc-editor.org/rfc/rfc7159#section-11
More examples with MappingJackson2HttpMessageConverter:
Customizing HttpMessageConverters with Spring Boot and Spring MVC — https://dzone.com/articles/customizing
Http Message Converters with the Spring Framework — https://www.baeldung.com/spring-httpmessageconverter-rest

Related

How to do java unit test with protobuf for controller?

I have a spring boot rest controller with requestBody & responseBody both protobuf. like below :
#RequestMapping(value = "/position/open", produces = "application/x-protobuf")
#ResponseBody
public MsgProto.Response positionOpen(#RequestBody MsgProto.Request request)throws Exception {
log.info("start /position/open");
return orderPositionService.addOrder(request);
}
Now I want to do a unit test using mockMvc to test the controller, but it failed every time. I believe it is the code below which is wrong to fire an HTTP request with protobuf, any idea how to resolve it?
mockMvc.perform(post("/position/open").contentType("application/x-protobuf")
.content(ObjectsMock.mockMsgProtoRequest().toByteArray())).andDo(print())
.andExpect(status().isOk());
Exception :
Resolved Exception:
Type = org.springframework.web.HttpMediaTypeNotSupportedException
MockHttpServletResponse:
Status = 415
Error message = null
Headers = [Accept:"application/json, application/octet-stream,
application/xml, application/*+json, text/plain, text/xml, application/x-www-
form-urlencoded, application/*+xml, multipart/form-data, multipart/mixed, */*"]
I assume the ProtobufHttpMessageConverter is missing here. Spring MVC can't read/write any messages without this specific converter.
You can create it as the following:
#Bean
public ProtobufHttpMessageConverter protobufHttpMessageConverter() {
return new ProtobufHttpMessageConverter();
}
Next, make sure to add the HTTP Method to your method, as I assume (from reading your test) you want this to be a HTTP POST handler. You can also add the consumes attribute to state that this endpoint also consumes Protobuf.
#RequestMapping(method = RequestMethod.POST, consumes = "application/x-protobuf", value = "/position/open", produces = "application/x-protobuf")
#ResponseBody
public MsgProto.Response positionOpen(#RequestBody MsgProto.Request request)throws Exception {
log.info("start /position/open");
return orderPositionService.addOrder(request);
}
In addition to this, there is an article on the Spring blog available that covers your usecase and explains how to use Protobuf with Spring MVC.
You need to add Protobuf converter to MockMvc builder
MockMvcBuilders.standaloneSetup(controller)
.setMessageConverters(new ProtobufHttpMessageConverter())
.build()
This fixed the issue for me

Supporting application/json and application/x-www-form-urlencoded simultaneously from Spring's rest controller

Am writing a REST endpoint which needs to support both application/x-www-form-urlencoded and application/json as request body simultaneously. I have made below configuration,
#RequestMapping(method = RequestMethod.POST, produces = { MediaType.APPLICATION_JSON_VALUE }, consumes = {
MediaType.APPLICATION_FORM_URLENCODED_VALUE, MediaType.APPLICATION_JSON_VALUE }, path = Constants.ACCESS_TOKEN_V1_ENDPOINT)
public OAuth2Authorization createAccessTokenPost(
#RequestBody(required = false) MultiValueMap<String, String> paramMap) { ..
While it supports application/x-www-form-urlencoded or application/json individually (when I comment out one content type from consumes = {}), but it does not support both simultaneously. Any ideas ?
So RestControllers by default can handle application/json fairly easily and can create a request pojo from a #RequestBody annotated parameter, while application/x-www-form-urlencoded takes a little more work. A solution could be creating an extra RestController method that has the same mapping endpoint to handle the different kinds of requests that come in (application/json, application/x-www-form-urlencoded, etc). This is because application/x-www-form-urlencoded endpoints need to use the #RequestParam instead of the #RequestBody annotation (for application/json).
For instance if I wanted to host a POST endpoint for /emp that takes either application/json or application/x-www-form-urlencoded as Content-Types and uses a service to do something, I could create Overload methods like so
#Autowired
private EmpService empService;
#PostMapping(path = "/emp", consumes = {MediaType.APPLICATION_FORM_URLENCODED_VALUE})
public ResponseEntity createEmp(final #RequestHeader(value = "Authorization", required = false) String authorizationHeader,
final #RequestParam Map<String, String> map) {
//After receiving a FORM URLENCODED request, change it to your desired request pojo with ObjectMapper
final ObjectMapper mapper = new ObjectMapper();
final TokenRequest tokenRequest = mapper.convertValue(map, CreateEmpRequest.class);
return empService.create(authorizationHeader, createEmpRequest);
}
#PostMapping(path = "/emp", consumes = {MediaType.APPLICATION_JSON_VALUE})
public ResponseEntity createEmp(final #RequestHeader(value = "Authorization", required = false) String authorizationHeader,
final #RequestBody CreateEmpRequest createEmpRequest) {
//Receieved a JSON request, the #RequestBody Annotation can handle turning the body of the request into a request pojo without extra lines of code
return empService.create(authorizationHeader, createEmpRequest);
}
As per my findings, spring does not support content types "application/x-www-form-urlencoded", "application/json" and "application/xml" together.
Reason I figured: Spring processes JSON and XML types by parsing and injecting them into the java pojo marked with #RequestBody spring annotation. However, x-www-form-urlencoded must be injected into a MultiValueMap<> object marked with #RequestBody. Two different java types marked with #RequestBody will not be supported simultaneously, as spring may not know where to inject the payload.
A working solution:
"application/x-www-form-urlencoded" can be supported as it is in the API. That is, it can be injected into spring's MultiValueMap<> using an #RequestBody annotation.
To support JSON and XML on the same method, we can leverage servlet specification and spring's class built on top of them to extract the payload as stream.
Sample code:
import org.springframework.http.HttpInputMessage;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
import org.springframework.http.converter.xml.Jaxb2RootElementHttpMessageConverter;
import org.springframework.http.server.ServletServerHttpRequest;
import org.springframework.util.MultiValueMap;
// usual REST service class
#Autowired
private MappingJackson2HttpMessageConverter mappingJackson2HttpMessageConverter;
#Autowired
private Jaxb2RootElementHttpMessageConverter jaxb2RootElementHttpMessageConverter;
public ResponseEntity<Object> authorizationRequestPost(HttpServletResponse response, HttpServletRequest request,#RequestBody(required = false) MultiValueMap<String, String> parameters) {
// this MultiValueMap<String,String> will contain key value pairs of "application/x-www-form-urlencoded" parameters.
// payload object to be populated
Authorization authorization = null;
HttpInputMessage inputMessage = new ServletServerHttpRequest(request) {
#Override
public InputStream getBody() throws IOException {
return request.getInputStream();
}
};
if (request.getContentType().equals(MediaType.APPLICATION_JSON_VALUE)) {
authorization = (Authorization) mappingJackson2HttpMessageConverter.read(Authorization.class, inputMessage);
}
else if (request.getContentType().equals(MediaType.APPLICATION_XML_VALUE)) {
authorization = (Authorization)jaxb2RootElementHttpMessageConverter.read(Authorization.class, inputMessage);
}
else{
// extract values from MultiValueMap<String,String> and populate Authorization
}
// remaining method instructions
}
Point to note that any custom data type/markup/format can be supported using this approach. Spring's org.springframework.http.converter.HttpMessageConverter<> can be extended to write the parsing logic.
Another possible approach could be an AOP style solution which would execute the same logic: parse payload by extracting it from HttpServlet input stream and inject into the payload object.
A third approach will be to write a filter for executing the logic.
It's not possible to handle application/json and application/x-www-form-urlencoded requests simultaneously with a single Spring controller method.
Spring get application/x-www-form-urlencoded data by ServletRequest.getParameter(java.lang.String), the document said:
For HTTP servlets, parameters are contained in the query string or posted form data.
If the parameter data was sent in the request body, such as occurs with an HTTP POST request, then reading the body directly via getInputStream() or getReader() can interfere with the execution of this method.
So, if your method parameter is annotated with #RequestBody, Spring will read request body and parse it to the method parameter object. But application/x-www-form-urlencoded leads Spring to populate the parameter object by invoking ServletRequest.getParameter(java.lang.String).
Just to make it, the above answer doesn't work as even if you do not annotate MultiValueMap with #RequestBody it would always check for contentType==MediaType.APPLICATION_FORM_URLENCODED_VALUE which again in rest of the cases resolves to 415 Unsupported Media Type.

Response MIME type for Spring Boot actuator endpoints

I have updated a Spring Boot application from 1.4.x to 1.5.1 and the Spring Actuator endpoints return a different MIME type now:
For example, /health is now application/vnd.spring-boot.actuator.v1+json instead simply application/json.
How can I change this back?
The endpoints return a content type that honours what the client's request says it can accept. You will get an application/json response if the client send an Accept header that asks for it:
Accept: application/json
In response to the comment of https://stackoverflow.com/users/2952093/kap (my reputation is to low to create a comment): when using Firefox to check endpoints that return JSON I use the Add-on JSONView. In the settings there is an option to specify alternate JSON content types, just add application/vnd.spring-boot.actuator.v1+jsonand you'll see the returned JSON in pretty print inside your browser.
As you noticed the content type for actuators have changed in 1.5.x.
If you in put "application/json" in the "Accept:" header you should get the usual content-type.
But if you don't have any way of modifying the clients, this snippet returns health (without details) and original content-type (the 1.4.x way).
#RestController
#RequestMapping(value = "/health", produces = MediaType.APPLICATION_JSON_VALUE)
public class HealthController {
#Inject
HealthEndpoint healthEndpoint;
#RequestMapping(method = RequestMethod.GET)
public ResponseEntity<Health > health() throws IOException {
Health health = healthEndpoint.health();
Health nonSensitiveHealthResult = Health.status(health.getStatus()).build();
if (health.getStatus().equals(Status.UP)) {
return ResponseEntity.status(HttpStatus.OK).body(nonSensitiveHealthResult);
} else {
return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(nonSensitiveHealthResult);
}
}
}
Configuration (move away existing health)
endpoints.health.path: internal/health
Based on the code in https://github.com/spring-projects/spring-boot/issues/2449 (which also works fine but completely removes the new type) I came up with
#Component
public class ActuatorCustomizer implements EndpointHandlerMappingCustomizer {
static class Fix extends HandlerInterceptorAdapter {
#Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
throws Exception {
Object attribute = request.getAttribute(HandlerMapping.PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE);
if (attribute instanceof LinkedHashSet) {
#SuppressWarnings("unchecked")
LinkedHashSet<MediaType> lhs = (LinkedHashSet<MediaType>) attribute;
if (lhs.remove(ActuatorMediaTypes.APPLICATION_ACTUATOR_V1_JSON)) {
lhs.add(ActuatorMediaTypes.APPLICATION_ACTUATOR_V1_JSON);
}
}
return true;
}
}
#Override
public void customize(EndpointHandlerMapping mapping) {
mapping.setInterceptors(new Object[] {new Fix()});
}
}
which puts the new vendor-mediatype last so that it will use application/json for all actuator endpoints when nothing is specified.
Tested with spring-boot 1.5.3
Since SpringBoot 2.0.x the suggested solution in implementing the EndpointHandlerMappingCustomizer doesn't work any longer.
The good news is, the solution is simpler now.
The Bean EndpointMediaTypes needs to be provided. It is provided by the SpringBoot class WebEndpointAutoConfiguration by default.
Providing your own could look like this:
#Configuration
public class ActuatorEndpointConfig {
private static final List<String> MEDIA_TYPES = Arrays
.asList("application/json", ActuatorMediaType.V2_JSON);
#Bean
public EndpointMediaTypes endpointMediaTypes() {
return new EndpointMediaTypes(MEDIA_TYPES, MEDIA_TYPES);
}
}
To support application/vnd.spring-boot.actuator.v1+json in Firefox's built in JSON viewer, you can install this addon: json-content-type-override. It will convert content types that contain "json" to "application/json".
Update: Firefox 58+ has built-in support for these mime types, and no addon is needed anymore. See https://bugzilla.mozilla.org/show_bug.cgi?id=1388335

Spring Boot RequestMapping with non-standard produces value returning 406 error when returning JAXB annotated object

I'm creating a Spring Boot app to replace a legacy api application, so all the routes/headers/etc are already set in stone. In that legacy app we used custom Accept headers to include both the version and the content type. So our Accept header is like:
catalog.v1.xml or catalog.v2.json etc.
Here is my request mapping for the method that is handling the request. I'm trying to handle the v1.xml one now. Spring is finding the correct method and the whole method is executed and it returns my JAXB annotated object:
#RequestMapping(value = "/catalog", method = RequestMethod.GET, produces="application/catalog.v1.xml")
How do I make sure Spring finds this matching handler method based on my Accept header, but knows that the output should be XML and marshall my JAXB object accordingly?
You need to provide Spring MVC with an HttpMessageConverter for your custom media type. To do so, I'd take advantage of Spring Boot automatically adding any HttpMessageConverter beans to Spring MVC's default configuration by configuring a bean that knows how to convert application/catalog.v1.xml:
#Bean
public Jaxb2RootElementHttpMessageConverter catalogXmlConverter() {
Jaxb2RootElementHttpMessageConverter xmlConverter = new Jaxb2RootElementHttpMessageConverter();
xmlConverter.setSupportedMediaTypes(Arrays.asList(new MediaType("application", "catalog.v1.xml")));
return xmlConverter;
}
So once I realized I was changing the wrong configuration, and from deep debugging into Spring code, I realized I needed to replace or modify the message converter behavior. Here is my solution below. They don't make it super easy. If anyone has a simpler way of doing this, please let me know. This works.
public void extendMessageConverters(List<HttpMessageConverter<?>> converters)
{
for (HttpMessageConverter<?> converter : converters) {
if (converter.getClass() == MappingJackson2HttpMessageConverter.class){
MappingJackson2HttpMessageConverter jacksonConverter = (MappingJackson2HttpMessageConverter)converter;
MediaType jsonType = new MediaType("application", "catalog.v2.json");
MediaType jsonpType = new MediaType("application", "catalog.v2.jsonp");
List<MediaType> mediatTypes = new ArrayList<>(jacksonConverter.getSupportedMediaTypes());
mediatTypes.add(jsonpType);
mediatTypes.add(jsonType);
jacksonConverter.setSupportedMediaTypes(mediatTypes);
}
else if (converter.getClass() == Jaxb2RootElementHttpMessageConverter.class){
Jaxb2RootElementHttpMessageConverter xmlConverter = (Jaxb2RootElementHttpMessageConverter) converter;
MediaType xmlType = new MediaType("application", "catalog.v1.xml");
// Since the SupportMediaTypes list is unmodifiable, we have to create a new one based on it
// and replace it completely
List<MediaType> mediatTypes = new ArrayList<>(xmlConverter.getSupportedMediaTypes());
mediatTypes.add(xmlType);
xmlConverter.setSupportedMediaTypes(mediatTypes);
}
}
}

How to make a #RestController POST method ignore Content-Type header and only use request body?

I'm using latest Spring Boot (1.2.1) and whatever Spring MVC version comes with it.
I have a controller method with implicit JSON conversions for both incoming and outgoing data:
#RestController
public class LoginController {
#RequestMapping(value = "/login", method = POST, produces = "application/json")
ResponseEntity<LoginResponse> login(#RequestBody LoginRequest loginRequest) {
// ...
}
}
This works fine, but only if request Content-Type is set to application/json. In all other cases, it responds with 415, regardless of the request body:
{
"timestamp": 1423844498998,
"status": 415,
"error": "Unsupported Media Type",
"exception": "org.springframework.web.HttpMediaTypeNotSupportedException",
"message": "Content type 'text/plain;charset=UTF-8' not supported",
"path": "/login/"
}
Thing is, I'd like to make my API more lenient; I want Spring to only use the POST request body and completely ignore Content-Type header. (If request body is not valid JSON or cannot be parsed into LoginRequest instance, Spring already responds with 400 Bad Request which is fine.) Is this possible while continuing to use the implicit JSON conversions (via Jackson)?
I've tried consumes="*", and other variants like consumes = {"text/*", "application/*"} but it has no effect: the API keeps giving 415 if Content-Type is not JSON.
Edit
It looks like this behaviour is caused by MappingJackson2HttpMessageConverter whose documentation says:
By default, this converter supports application/json and
application/*+json. This can be overridden by setting the supportedMediaTypes property.
I'm still missing how exactly do I customise that, for example in a
custom Jackson2ObjectMapperBuilder...
I assume that you are using default MappingJackson2HttpMessageConverter provided by Spring.
If you would like to have the same behavior in all requests, one solution would be to write custom converter which will not look for Content-Type, in a header (instead will parse to JSON alwayse) and then configure Spring to use your custom one. Again this will affect all requests, so might not fit all needs.
public class CustomerJsonHttpMessageConverter extends AbstractHttpMessageConverter<Object> {
private ObjectMapper mapper = new ObjectMapper();
private static final Charset DEFAULT_CHARSET = Charset.forName("UTF-8");
public CustomerJsonHttpMessageConverter() {
super(new MediaType("application", "json", DEFAULT_CHARSET));
}
#Override
protected Object readInternal(Class<?> clazz, HttpInputMessage inputMessage) throws IOException,
HttpMessageNotReadableException {
return mapper.readValue(inputMessage.getBody(), clazz);
}
#Override
protected boolean supports(Class<?> clazz) {
return true;
}
#Override
protected void writeInternal(Object value, HttpOutputMessage outputMessage) throws IOException,
HttpMessageNotWritableException {
String json = mapper.writeValueAsString(value);
outputMessage.getBody().write(json.getBytes());
}
}
To have custom media type,
MappingJackson2HttpMessageConverter converter = new MappingJackson2HttpMessageConverter();
converter.setSupportedMediaTypes(
Arrays.asList(
new MediaType("text", "plain"),
new MediaType("text", "html")
));
For anyone else who is curious about this;
It is possible to customize the used MappingJackson2HttpMessageConverter by overridding WebMvcConfigurerAdapter.extendMessageConverters to allow for multiple mime types.
However, it does not work as expected because application/x-www-form-urlencoded is hardcoded in ServletServerHttpRequest.getBody to modify the body to be url encoded (even if the post data is JSON) before passing it to MappingJackson2HttpMessageConverter.
If you really needed this to work then I think the only way is to put a Filter that modifies the request content-type header before handling (not to imply this is a good idea, just if the situation arises where this is necessary).
Update: watch out if you use this
(This was probably a stupid idea anyway.)
This has the side effect that server sets response Content-Type to whatever the first value in the request's Accept header is! (E.g. text/plain instead of the correct application/json.)
After noticing that, I got rid of this customisation and settled went with Spring's default behaviour (respond with 415 error if request does not have correct Content-Type).
Original answer:
MappingJackson2HttpMessageConverter javadocs state that:
By default, this converter supports application/json and application/*+json. This can be overridden by setting the supportedMediaTypes property.
...which pointed me towards a pretty simple solution that seems to work. In main Application class:
#Bean
public MappingJackson2HttpMessageConverter mappingJackson2HttpMessageConverter() {
MappingJackson2HttpMessageConverter converter =
new MappingJackson2HttpMessageConverter(new CustomObjectMapper());
converter.setSupportedMediaTypes(Arrays.asList(MediaType.ALL));
return converter;
}
(CustomObjectMapper is related to other Jackson customisations I have; that contructor parameter is optional.)
This does affect all requests, but so far I don't see a problem with that in my app. If this became a problem, I'd probably just switch the #RequestBody parameter into String, and deserialise it manually.

Resources