Spring 5 consume multiple MediaType - java

I'm using Spring boot 1.5.8.RELEASE
I have an endpoint and would like to consume multiple MediaType.
In particular application/x-www-form-urlencoded and application/json type.
At the moment I have the below code. It works for application/x-www-form-urlencoded, but doesn't work for application/json.
#RequestMapping(path = "/abc", method = RequestMethod.POST,
consumes = {MediaType.APPLICATION_JSON_VALUE, MediaType.APPLICATION_FORM_URLENCODED_VALUE},
produces = MediaType.APPLICATION_JSON_VALUE)
#ResponseStatus(value = HttpStatus.OK)
public ResponseEntity<MyResponse> validate(#Valid #ModelAttribute MyDetails details) {
return something();
}
I tried to put a content negotiator in my configuration, but still doesn't work.
#Override
public void configureContentNegotiation(ContentNegotiationConfigurer configurer) {
configurer.favorPathExtension(false)
.defaultContentType(MediaType.APPLICATION_JSON)
.mediaType("x-www-form-urlencoded", MediaType.APPLICATION_FORM_URLENCODED);
}
How can I allow the endpoint to accept these 2 media types?

exchange {} to [] for consumes
Try it:
consumes = [MediaType.APPLICATION_JSON_VALUE, MediaType.APPLICATION_FORM_URLENCODED_VALUE],
reference: Spring RequestMapping - Baeldung

Related

How to return json/xml from Spring Boot #Controller?

I'm trying to return JSON/XML from a function of my Controller. First I was using #RestController and it worked good, but now I need to change to #Controller, because I will use also some other functions and pass there some objects for my view.
#Controller
#RequestMapping("/game")
public class ViewController {
#RequestMapping(value = "/statistic", method = RequestMethod.GET,
produces = {MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML})
public GamerData[] getStatistic() {
RestTemplate restTemplate = new RestTemplate();
try {
String uri_get_statistic = "http://localhost:8081/statistic/";
ResponseEntity<GamerData[]> response = restTemplate.getForEntity(uri_get_statistic, GamerData[].class);
GamerData[] statisticData = response.getBody();
return statisticData;
} catch(Exception e) {
e.printStackTrace();
return null;
}
}
}
After I've changed to Controller I get error 404 not found. With RestController I've got json. (GamerData is just a class with 2 simple fields (int and String), setters, getters, consructor).
UPDATE:
I've added #ResponseBody to my function, but now I have Internal Server error
Resolved [org.springframework.http.converter.HttpMessageNotWritableException: No converter for [class [Lgame.mainservice.mvc.GamerData;] with preset Content-Type 'null']
try to add #ResponseBody like
public #ResponseBody GamerData[] getStatistic....
#RequestMapping(value = "/statistic", method = RequestMethod.GET,
produces = {MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML})
#ResponseBody // add this one
public GamerData[] getStatistic() {}
Thank you all for answers, I've added #ResponceBody annotation to my function and removed "produces" part from #RequestMapping annotation and my function works great:
#RequestMapping(value = "/statistic", method = RequestMethod.GET)
#ResponseBody
public GamerData[] getStatistic() {
//function code
}

How to set priority to Spring-Boot request mapping methods

I have a Spring-Boot (v2.0.2) application with a RestController with 2 methods which only differ by the Accept header. A simplified version of the code is this:
#RestController
#RequestMapping("/myapp")
public class FooController {
#GetMapping(value = "/foo/{id}", headers = "Accept=application/json", produces = "application/json;charset=UTF-8")
public ResponseEntity<String> fooJson(#PathVariable id) {
return foo(pageId, true);
}
#GetMapping(value = "/foo/{id}", headers = "Accept=application/ld+json", produces = "application/ld+json;charset=UTF-8")
public ResponseEntity<String> fooJsonLd(#PathVariable id) {
return foo(pageId, false);
}
private ResponseEntity<String> foo(String id, boolean isJson) {
String result = generateBasicResponse(id);
if (isJson) {
return result
}
return addJsonLdContext(result);
}
This works fine. If we sent a request with accept header such as application/json;q=0.5,application/ld+json;q=0.6 for example it will return a json-ld response as it should.
My problem is that if we sent a request with no accept header, an empty accept header or a wildcard */* then it will by default always return a json response whereas I want the default response to be json-ld.
I've tried various things to make the json-ld request mapping take priority over the json one:
Reversing the order in which the mappings are declared.
Adding an #Order annotation to both methods (with value 1 for json-ld and value 2 for the json method)
Creating different classes and putting the #Order annotation at class-level
Adding Accept=*/* as a second accept header to the json-ld mapping does work in giving it preference but has the unwanted side-affect that all accept headers are accepted, even unsupported types as application/xml for example.
The only solution I can think of is creating one request-mapping method that accepts both headers and then processing the accept header ourselves, but I don't really like that solution. Is there a better, easier way to give preference to json-ld?
After some more searching this question on configuring custom MediaTypes pointed me in the right direction.
The WebMvcConfigurerAdapter (Spring 3 or 4) or WebMvcConfigurer (Spring 5) allows you to set a default mediatype like this:
public static final String MEDIA_TYPE_JSONLD = "application/ld+json";
#EnableWebMvc
public class WebConfig implements WebMvcConfigurer {
#Override
public void configureContentNegotiation(ContentNegotiationConfigurer configurer) {
configurer.defaultContentType(MediaType.valueOf(MEDIA_TYPE_JSONLD));
}
}
This works great for requests with no or an empty accept header, as well as accept: */*. However when you combine an unsupported type with the wildcard, for example accept: */*,text/plain it will return json instead of json-ld!? I suspect this is a bug in Spring.
I solved the issue using the consumes in the #GetMapping annotation. According to the official documentation:
The format is a single media type or a sequence of media types, with a request only mapped if the Content-Type matches one of these media types. Expressions can be negated by using the "!" operator, as in "!text/plain", which matches all requests with a Content-Type other than "text/plain".
In the solution bellow, note that I've added the consumes array to the normal json request mapping, making the client only be able to use the json endpoint if it have the correct Content-Type. Other requests go to the ld+json endpoint.
#GetMapping(value = "/json", headers = "Accept=application/json", consumes = {"application/json"})
#ResponseBody
public String testJson() {
return "{\"type\":\"json\"}";
}
#GetMapping(value = "/json", headers = "Accept=application/ld+json")
#ResponseBody
public String textLDJson() {
return "{\"type\":\"ld\"}";
}

SpringBoot and #CrossOrigin annotation with POST only

I have the following class
#RestController
#RequestMapping("/bets")
#CrossOrigin
public class BetRestController {
#Autowired
private BetController betController;
#ResponseBody
#RequestMapping(method=RequestMethod.POST, consumes = MediaType.APPLICATION_JSON_VALUE)
public BetResource betOnGame(#RequestBody #Valid BetResource betResource) {
return BetTranslator.from(betController.betOnGame(BetTranslator.from(betResource)));
}
#ResponseBody
#RequestMapping(method = RequestMethod.GET)
public List<BetResource> getAllBets() {
return betController.getAllBets().stream().map(BetTranslator::from).collect(Collectors.toList());
}
}
The problem is that when I try to access the POST method I am getting:
XMLHttpRequest cannot load http://localhost:8080/bets. Origin http://localhost:3000 is not allowed by Access-Control-Allow-Origin.
but GET method works.
What I have wrong in my configuration or maybe there is a bug in #CrossOrigin annotation processing?!
If I understand your problem correctly, it is possible to specify CORS origin on one specific method.
Sprint Boot documentation : https://spring.io/guides/gs/rest-service-cors/
Enabling CORS
Controller method CORS configuration
So that the RESTful web service will include CORS access control
headers in its response, you just have to add a #CrossOrigin
annotation to the handler method:
Here's the example from Spring boot website :
#CrossOrigin(origins = "http://localhost:9000")
#RequestMapping("/greeting")
public #ResponseBody Greeting greeting(#RequestParam(required=false, defaultValue="World") String name) {
System.out.println("==== in greeting ====");
return new Greeting(counter.incrementAndGet(), String.format(template, name));
}

Spring Controller: Custom annotation with combined Spring annotations does not work

I have properly working Spring Controller, where I have a method mapped in a following way
#RequestMapping(
value = "/users"
consumes = MimeTypeUtils.APPLICATION_JSON_VALUE,
produces = MimeTypeUtils.APPLICATION_JSON_VALUE,
method = RequestMethod.GET)
#ResponseBody
#ResponseStatus(HttpStatus.OK)
public UserResponse retrieveUsers() {
return new UserResponse();
}
#RequestMapping(
value = "/contracts"
consumes = MimeTypeUtils.APPLICATION_JSON_VALUE,
produces = MimeTypeUtils.APPLICATION_JSON_VALUE,
method = RequestMethod.GET)
#ResponseBody
#ResponseStatus(HttpStatus.OK)
public ContractResponse retrieveContracts() {
return new ContractResponse();
}
This works fine, GET requests are served as accepted, and in case of for example POST I am receiving proper 405 status code.
Now I want to introduce custom combined annotation, not to have the same bunch of annotations in each and every method.
My custom annotation looks like,
#RequestMapping(
consumes = MimeTypeUtils.APPLICATION_JSON_VALUE,
produces = MimeTypeUtils.APPLICATION_JSON_VALUE,
method = RequestMethod.GET)
#ResponseBody
#ResponseStatus(HttpStatus.OK)
#Target({ElementType.METHOD, ElementType.TYPE})
#Retention(RetentionPolicy.RUNTIME)
public #interface Get {}
Accordingly, I change the method to
#Get
#RequestMapping(value = "/users")
public UserResponse retrieveUsers() {
return new UserResponse();
}
In this case I can see that whatever type of request I send to /users it is served properly. For example even if I do POST, I see response. So the #RequestMapping does not work properly.
What am I doing wrong here? Is it possible to make controllers behave properly with custom combined annotation?
I suspect that the #RequestMapping(value = "/users") on UserResponse#retrieveUsers replaces the #RequestMapping on the #Get interface.
See if this works:
#RequestMapping(
consumes = MimeTypeUtils.APPLICATION_JSON_VALUE,
produces = MimeTypeUtils.APPLICATION_JSON_VALUE,
method = RequestMethod.GET)
#ResponseBody
#ResponseStatus(HttpStatus.OK)
#Target({ElementType.METHOD, ElementType.TYPE})
#Retention(RetentionPolicy.RUNTIME)
public #interface Get {
#AliasFor(annotation = RequestMapping.class, attribute = "value")
String[] value() default {};
}
#Get(value = "/users")
public UserResponse retrieveUsers() {
return new UserResponse();
}
You may be interested in GetJson in the spring-composed project.
Note that #AliasFor was only released with Spring 4.2. It's not available in earlier versions.
You overwrite the #RequestMapping annotation you had set up in your #Get annotation. Therefore it specifies only the value part of the request mapping, leaving all other properties to default.

#ResponseBody annotated method do not return Model as JSON Spring 3.2.10

My method is annotated as
#RequestMapping(value = "/keepAlive", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE,consumes = MediaType.APPLICATION_JSON_VALUE)
public #ResponseBody ModelMap test(HttpServletRequest req, final ModelMap model) {
model.addAttribute("keepAlive", true);
return model;
}
when I call is using ajax using JQuery
it returns 500 server error and at server log I can see that it is looking for KeepAlive.jsp, I am using spring 3.2.10 and have jackson 2 at class path. When I debugged source code request is passed to ModelAndViewMethodReturnValueHandler rather than RequestResponseBodyMethodProcessor , It seems Model and view handler is registered before req res handler. How to solve this. Same code worked for spring 3.1.2.
Thanks

Categories