I have a REST Webservice returning an int via #responseBody and I want this response to be in XML, and I don't know how to achieve that despite many tries.
My controller is as follow:
#RequestMapping(value = "/UserByAppli", method = RequestMethod.GET)
#ResponseBody
public List<Application> getNbUserByAppli()
{
return this.DAO.getNbUserByAppli();
}
And my application Object:
#Component
#XmlRootElement(name="Application")
#XmlAccessorType(XmlAccessType.FIELD)
public class Application
{
#XmlElement(name="Nom")
private String name;
#XmlElement(name="NbUtilisateurs")
private int nbUsers;
public Application()
{
}
...
}
It always returns application/json, and when I specify the header "Accept=application/xml" I get a 406 response code with org.springframework.web.HttpMediaTypeNotAcceptableException: Could not find acceptable representation in Spring logs.
An explanation or a search direction would be appreciated...
Make sure you have JAXB2 in your classpath and have registered the appropriate message converter and pass the Accept: application/xml header. Also, like M. Deinum suggested, for the marshalling to work, you also need to wrap the <Application /> elements in another element <Applications />.
Related
I have a Spring-Boot controller application that will be called by the front-end. The Spring-boot #PostMapping would accept the XML and JSON. I want to call different methods based on the Content-Type.
Is there a way to check what is the incoming content type?
#CrossOrigin(origins = "*")
#RestController
#RequestMapping("/api")
public class MyController {
#PostMapping(value = "/generator", consumes = {"application/json", "application/xml"}, produces = "application/json")
public String generate(#RequestBody String input) {
try {
System.out.println("INPUT CONTENT TYPE : ");
if(contentType == "application/xml")
{
//Call Method-1
}else if(contentType == "application/json"){
//Call Method-2
}
} catch (Exception exception) {
System.out.println(exception.getMessage());
}
}
}
As we can see the RestController method accepts XML and JSON. I want to check whats the incoming Content-type is based on its need to make different decisions. Can someone please explain to me how to do it?
Please Note:
I am aware that I can create different methods to handle XML and JSON but I would like to do it in a single method so it would be easy and efficient.
Add RequestHeader with its name Content-type:
public String generate(#RequestBody String input, #RequestHeader("Content-type") String contentType)
Annotation which indicates that a method parameter should be bound to a web request header.
You can use
#RequestHeader Map<String, String> headers
inside param of your generate() methode for get all Header come from the client.
After that, just check the
Content-Type
value
in the below example, I am trying to undersatnd the difference between #RequestMapping and #PostMapping.
For #RequestMapping:
when i do the POST request:
http://localhost:8085/call1/initparam1?val=1111 via postman, it executes correctly.
but when its is proceeded by by GET request
http://localhost:8085/call1/getparam1
i do not get 1111 as a result.
For #PostMapping, when i do the POST request:
http://localhost:8085/call1/initparam2/1999 via postman, it executes correctly.
but when its is proceeded by by GET request
http://localhost:8085/call1/getparam1
i do not get 1999 as a result.
please explain to me what is the difference between using both annotations, as i spent time googling and researching but i could not figure out why the first example is not working.
Controller1
#Controller
#ResponseBody
#RequestMapping("/call1")
public class Call1 {
public String str = "inti";
#RequestMapping(value = "/initparam1", method = RequestMethod.POST)
public void initparam1(#RequestParam(value = "val") String val) {
this.str = val;
}
#PostMapping(value = "/initparam2/{val}")
public void initparam2(#PathVariable String val) {
this.str = val;
}
#RequestMapping("/getparam1")
#ResponseBody
public String getParam1() {
return this.str;
}
}
From the #PostMapping docs :
Specifically, #PostMapping is a composed annotation that acts as a shortcut for #RequestMapping(method = RequestMethod.POST).
So it is only convenience annotation that is more "verbose" and indicates that method annotated with it is used for handling POST HTTP requests.
I have just checked your controller methods with 2.1.4 spring boot version and your scenarios work as expected so there has to be something wrong in your configuration or the way you are sending requests.
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\"}";
}
I want to know why spring mvc transform [""] to [null] when I use PostMan to test my API.
here is my controller:
#RequestMapping(value = "", method = RequestMethod.POST, consumes = "application/json", produces = "application/json")
public #ResponseBody ResponseEntity<Object> participateRstActivities(
HttpServletRequest request, #RequestBody RstActivityFrom rstForm)
throws ServiceException {
log.info("list size:{}, frist object:{}",rstForm.getRestaurant_ids().size(), rstForm.getRestaurant_ids().get(0));
}
here is my java bean:
public class RstActivityFrom {
private List<Integer> restaurant_ids;
private int activity_id;
// omit getter & setter
}
here is my request body when I use postman to test my api:
{
"restaurant_ids":[""],
"activity_id":119129
}
and the log in controller print :
list size:1, frist object:null.
this problem makes me feel confuse, I want to know why. Thanks
Since restaurant_ids is a List and not String, Change your JSON for restaurant_ids:
{
"restaurant_ids":[],
"activity_id":119129
}
If you don't want to allow an empty String value for objects mapped from your JSON, you can look into setting the Jackson's ObjectMapper Features as described here:
https://docs.spring.io/spring-boot/docs/current/reference/html/howto-spring-mvc.html
The Java API for Jackson's DeserializationConfig.Feature(s) can be found here:
http://fasterxml.github.io/jackson-core/javadoc/1.9/org/codehaus/jackson/map/DeserializationConfig.Feature.html
i configure my messageconverter as Jackson's then
class Foo{int x; int y}
and in controller
#ResponseBody
public Foo method(){
return new Foo(3,4)
}
from that i m expecting to return a JSON string {x:'3',y:'4'} from server without any other configuration. but getting 404 error response to my ajax request
If the method is annotated with #ResponseBody, the return type is written to the response HTTP body. The return value will be converted to the declared method argument type using HttpMessageConverters.
Am I wrong ? or should I convert my response Object to Json string myself using serializer and then returning that string as response.(I could make string responses correctly) or should I make some other configurations ? like adding annotations for class Foo
here is my conf.xml
<bean id="jacksonMessageConverter" class="org.springframework.http.converter.json.MappingJacksonHttpMessageConverter">
<bean class="org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter">
<property name="messageConverters">
<list>
<ref bean="jacksonMessageConverter"/>
</list>
</property>
You need the following:
Set annotation-driven programming model: put <mvc:annotation-driven /> in spring.xml
Place jaskson jar (Maven artifactId is org.codehaus.jackson:jackson-mapper-asl) in classpath.
Use as the following:
#RequestMapping(method = { RequestMethod.GET, RequestMethod.POST })
public #ResponseBody Foo method(#Valid Request request, BindingResult result){
return new Foo(3,4)
}
This works for me.
Please note, that
#ResponseBody is applied to return type, not to the method definition.
You need #RequestMapping annotation, so that Spring will detect it.
This worked for me:
#RequestMapping(value = "{p_LocationId}.json", method = RequestMethod.GET)
protected void getLocationAsJson(#PathVariable("p_LocationId") Integer p_LocationId,
#RequestParam("cid") Integer p_CustomerId, HttpServletResponse response) {
MappingJacksonHttpMessageConverter jsonConverter =
new MappingJacksonHttpMessageConverter();
Location requestedLocation = new Location(p_LocationId);
MediaType jsonMimeType = MediaType.APPLICATION_JSON;
if (jsonConverter.canWrite(requestedLocation.getClass(), jsonMimeType)) {
try {
jsonConverter.write(requestedLocation, jsonMimeType,
new ServletServerHttpResponse(response));
} catch (IOException m_Ioe) {
// TODO: announce this exception somehow
} catch (HttpMessageNotWritableException p_Nwe) {
// TODO: announce this exception somehow
}
}
}
Note that the method doesn't return anything: MappingJacksonHttpMessageConverter#write() does the magic.
The MessageConverter interface http://static.springsource.org/spring/docs/3.0.x/javadoc-api/ defines a getSupportedMediaTypes() method, which in case of the MappingJacksonMessageCoverter returns application/json
public MappingJacksonHttpMessageConverter() {
super(new MediaType("application", "json", DEFAULT_CHARSET));
}
I assume a Accept: application/json request header is missing.
A HTTP 404 error just means that the resource cannot be found. That can have 2 causes:
Request URL is wrong (client side error or wrong URL in given link/button).
Resource is not there where you expect it is (server side error).
To fix 1, ensure you're using or providing the correct request URL (casesensitive!). To fix 2, check the server startup logs for any startup errors and fix them accordingly.
This all goes beyond the as far posted code and information.
I found that I need jackson-core-asl.jar too, not only jackson-mapper-asl.jar
This is just a guess, but by default Jackson only auto-detects public fields (and public getters; but all setters regardless of visibility). It is possible to configure this (with version 1.5) to also auto-detect private fields if that is desired (see here for details).
I guess that 404 is not related to your HttpMessageConverter. I had same 404-issue and the reason was that I forgot that only requests matching <url-pattern> are sent to DispatcherServlet (I changed request mapping from *.do to *.json). Maybe this is your case also.
In addition to the answers here..
if you are using jquery on the client side, this worked for me:
Java:
#RequestMapping(value = "/ajax/search/sync")
public ModelAndView sync(#RequestBody Foo json) {
Jquery (you need to include Douglas Crockford's json2.js to have the JSON.stringify function):
$.ajax({
type: "post",
url: "sync", //your valid url
contentType: "application/json", //this is required for spring 3 - ajax to work (at least for me)
data: JSON.stringify(jsonobject), //json object or array of json objects
success: function(result) {
//do nothing
},
error: function(){
alert('failure');
}
});