XML response from API with PathVariable - java

I have the API:
#GetMapping(path = "/users/{userId}")
public ResponseEntity<UserDTO> getUserById(#PathVariable(value = "userId") Long userId) {
//logic here
}
it returns JSON response, as it should.
And there's another app that I don't have access to, and it calls my API as, for example, GET /users/123.xml in order to receive XML response.
But in this case, my API fails with 400 error, because it cannot parse 123.xml into Long.
Option #GetMapping(value = {"/users/{userId}", "/users/{userId}.xml"}) fails with the same error.
What can I do to respond with XML syntax when calling /{userId}.xml and in the mean time, respond with JSON syntax when calling /{userId}?
EDIT:
I want it to do without specifically adding 'Accept' headers, and without writing any additional logic, that'll parse {userId}.xml and then set the appropriate response type.

That's can be done by using a ContentNegotiationConfigurer, you can configure it as follow :
#Configuration
#EnableWebMvc
public class MvcConfig implements WebMvcConfigurer {
#Override
public void configureContentNegotiation(ContentNegotiationConfigurer configurer) {
configurer
.defaultContentType(MediaType.APPLICATION_JSON)
.mediaType("xml", MediaType.APPLICATION_XML)
.mediaType("json", MediaType.APPLICATION_JSON);
}
}
It should work fine with your endpoint :
#GetMapping(path = "/users/{userId}", produces = {MediaType.APPLICATION_JSON_VALUE, MediaType.APPLICATION_XML_VALUE})
public ResponseEntity<UserDTO> getUserById(#PathVariable(value = "userId") Long userId) {
return new ResponseEntity<>(userService.get(userId), HttpStatus.OK);
}

The easiest solution that I can think right off will be to have an optional Request parameter "responseType" with default value as json, and if someone wants XML response, they can call the url like :GET /users/123?responseType=xml
Since the default value of the parameter will be 'json' and it will have property "required= false", you wouldn't need to worry in use cases where json response is desired, and if someone wants XML response, they can add the optional RequestParam. Also, I guess you will need to specify produces with json and xml return types for the controller to let spring-boot know that it can produce different kinds of responses, something like -
#RequestMapping(value = "/users/{userid}", method = RequestMethod.GET,
produces = { MediaType.APPLICATION_JSON_VALUE,
MediaType.APPLICATION_XML_VALUE }, consumes = MediaType.ALL_VALUE)
public ResponseEntity<User> getuserById(#PathVariable String
userid,#RequestParam(required=
false,defaultValue="json",name="responseType"),#RequestHeader ("content-type") String
contentType)
)
EDIT : You can use either the request param or the Request header, I provided both in the example for your reference

As an owner of the API you should declare what kind of a responses you are able to produce - in your case it is either JSON or XML:
#GetMapping(path = "/users/{userId}", produces = {MediaType.APPLICATION_JSON_VALUE, MediaType.APPLICATION_XML_VALUE})
public ResponseEntity<UserDTO> getUserById(#PathVariable(value = "userId") Long userId) {
return new ResponseEntity<>(userService.get(userId), HttpStatus.OK);
}
Any client of the API can now choose which response format is preferred using Accept header - for example Accept: application/xml. Spring will respect that and return response in requested format.
To make it work you need to add additional dependency that will be used by Spring to produce XML responses:
<dependency>
<groupId>com.fasterxml.jackson.dataformat</groupId>
<artifactId>jackson-dataformat-xml</artifactId>
</dependency>
If you really need to go in /users/123.xml direction you'll have to change userId type to String and parse it yourself like this:
#GetMapping(path = "/users/{userId}")
public ResponseEntity<UserDTO> getUserById(#PathVariable(value = "userId") String userId) {
if (hasXMLExtension(userId)) {
return ResponseEntity
.ok()
.contentType(MediaType.XML)
.body(requestdUser);
} else {
return ResponseEntity
.ok()
.contentType(MediaType.JSON)
.body(requestdUser);
}
}

Related

How can I get my spring boot controller to read the body of my http request to an object if the http request has content type urlencoded?

I am new to spring and spring boot and have set up a simple controller that can read http requests to an object if there is a header set setting the content-type to application/json.
However, when I do not set the content type in a header, this does not work and I get the error: "Content type 'application/x-www-form-urlencoded;charset=UTF-8' not supported". I understand that at no point have I told the controller that I actually want the it to read the body as JSON rather than as urlencoded and I'm looking for a way of doing this.
I have experimented with the #RequestBody, #RequestParam and #ResponseBody annotations but so far have had no luck.
I have also looked at overriding the WebMvcConfigurer.configureContentNegotiation method by setting default and other media-types but am less clear on what I am doing here.
Here is my simple controller in its current form
public class GreetingController {
#RequestMapping(value = "/greeting",
method = RequestMethod.POST)
public Greeting postGreeting(#RequestBody Greeting body) {
return new Greeting(body.getId(),"hello " + body.getContent());
}
}
Here is the constructor for my greeting class for reference:
public Greeting(long id, String content) {
this.id = id;
this.content = content;
}
The body of my request is '{"id":10, "content": "world"}'
Ideally, I would like to find a way to be able to process an http request with no content type header set (so presumably defaulting to form-urlencoded) as JSON so that there is less to think about when setting up the post request and the controller is less brittle.
Try this:
#RestController
public class GreetingController {
#RequestMapping(value = "/greeting",
method = RequestMethod.POST)
public HttpEntity<String> postGreeting(#RequestBody Greeting body) {
//SETGREETING()
return new HttpEntity<String>("data has been saved");
}
}
And don't forget to accept application/json header.
Try below code
import org.springframework.web.bind.annotation.ModelAttribute;
public class GreetingController {
#RequestMapping(value = "/greeting",
method = RequestMethod.POST)
public Greeting postGreeting(#ModelAttribute Greeting body) {
return new Greeting(body.getId(),"hello " + body.getContent());
}
}

Spring Rest return a JSON response with a certain http response code

I am very new to Spring. I have a REST api written in Spring, but I don't know how to return a JSON response with a custom http response code.
I return a JSON response as follows:
public String getUser(String id){
...
return jsonObj;
}
But it always displays 200 http ok status code.
Here are my questions:
How can I synchronize the response JSON and HTTP code?
How is it possible to return JSON response and custom HTTP code in void function?
Use #ResponseStatus annotation:
#GetMapping
#ResponseStatus(HttpStatus.ACCEPTED)
public String getUser(String id) {...}
Alternative way: If you want to decide programmatically what status to return you can use ResponseEntity. Change return type of a method to ResponseEntity<String> and you'll be offered with a DSL like this:
ResponseEntity
.status(NOT_FOUND)
.contentType(TEXT_PLAIN)
.body("some body");
How I do it
Here is how I do JSON returns from a Spring Handler method.
My techniques are somewhat out-of-date,
but are still reasonable.
Configure Jackson
Add the following to the spring configuration xml file:
<bean name="jsonView"
class="org.springframework.web.servlet.view.json.MappingJackson2JsonView">
</bean>
With that,
Spring will convert return values to JSON and place them in the body of the response.
Create a utility method to build the ResponseEntity
Odds are good that you will have multiple handler methods.
Instead of boilerplate code,
create a method to do the standard work.
ResponseEntity is a Spring class.
protected ResponseEntity<ResponseJson> buildResponse(
final ResponseJson jsonResponseBody,
final HttpStatus httpStatus)
{
final ResponseEntity<ResponseJson> returnValue;
if ((jsonResponseBody != null) &&
(httpStatus != null))
{
returnValue = new ResponseEntity<>(
jsonResponseBody,
httpStatus);
}
return returnValue;
}
Annotate the handler method
#RequestMapping(value = "/webServiceUri", method = RequestMethod.POST)
you can also use the #PostMethod annotation
#PostMethod("/webServiceUri")
Return ResponseEntity from the handler method
Call the utility method to build the ResponseEntity
public ResponseEntity<ResponseJson> handlerMethod(
... params)
{
... stuff
return buildResponse(json, httpStatus);
}
Annotate the handler parameters
Jackson will convert from json to the parameter type when you use the #RequestBody annotation.
public ResponseEntity<ResponseJson> handlerMethod(
final WebRequest webRequest,
#RequestBody final InputJson inputJson)
{
... stuff
}
A different story
You can use the #JsonView annotation.
Check out the Spring Reference for details about this.
Browse to the ref page and search for #JsonView.

Malformed JSON: Unexpected '<' in Spring

I want to produce json from JPA #Entity, I have
#Entity
#JsonAutoDetect
public class Bar implements Serializable {
#Id
#GeneratedValue
private Integer id;
private String title;
//omitting other stuff
}
my controller is
#RestController
public class BarController {
#Autowired
private BarService barService;
#RequestMapping(value = "/", method = RequestMethod.GET, headers = "Accept=application/json", produces={"application/json"})
public List<Bar> list() {
return barService.findAllBars());
}
}
I'm having this error in browser
and in Postman
what is wrong with it.
The "Malformed JSON" message is from the "Pretty" printing. Click "Raw" to see the actual response.
The actual response is a 406 Not Acceptable error (says so on your screen) with a payload of HTML (hence the unexpected < from "Pretty") that says the request has been rejected by the server.
Remove the headers = "Accept=application/json" from the #RequestMapping. The produces={"application/json"} is already telling Spring to only call this method if application/json is an acceptable response, which it likely is, but the header might say *.*, or something more complex like text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8, both of which will allow application/json.
Of course, since this is likely an AJAX call that is expecting JSON, it should have listed only application/json in the accept value. Check the code executing the AJAX call if that is not the case.
I caught up one fatal mistake which is you are getting list of Bars barService.findAllBars(), you may need to convert that list to json adding the method as
public static String toJSON(Object object)
{
if ( object == null ){
return "{}";
}
try {
ObjectMapper mapper = new ObjectMapper();
return mapper.writeValueAsString(object);
}
catch (Exception e) {
e.printStackTrace();
}
return "{}";
}
Now make change as
#RequestMapping(value = "/", method = RequestMethod.GET, produces={"application/json"})
public String list() {
return toJSON(barService.findAllBars());
}
hope this works, if any issues feel free to query in comments session.
I suspect there is something amiss with your accept header in your get request. Try setting the header to
"Accept=*/*"
and see what you get back.
Use JsonFormatter https://jsonformatter.curiousconcept.com/ to test the JSON before proceeding further. Its a very good tool that validates JSON and shows you possible errors with line number.

Spring MVC - How to return simple String as JSON in Rest Controller

My question is essentially a follow-up to this question.
#RestController
public class TestController
{
#RequestMapping("/getString")
public String getString()
{
return "Hello World";
}
}
In the above, Spring would add "Hello World" into the response body. How can I return a String as a JSON response? I understand that I could add quotes, but that feels more like a hack.
Please provide any examples to help explain this concept.
Note: I don't want this written straight to the HTTP Response body, I want to return the String in JSON format (I'm using my Controller
with RestyGWT which requires the response to be in valid JSON
format).
Either return text/plain (as in Return only string message from Spring MVC 3 Controller) OR wrap your String is some object
public class StringResponse {
private String response;
public StringResponse(String s) {
this.response = s;
}
// get/set omitted...
}
Set your response type to MediaType.APPLICATION_JSON_VALUE (= "application/json")
#RequestMapping(value = "/getString", method = RequestMethod.GET,
produces = MediaType.APPLICATION_JSON_VALUE)
and you'll have a JSON that looks like
{ "response" : "your string value" }
JSON is essentially a String in PHP or JAVA context. That means string which is valid JSON can be returned in response. Following should work.
#RequestMapping(value="/user/addUser", method=RequestMethod.POST)
#ResponseBody
public String addUser(#ModelAttribute("user") User user) {
if (user != null) {
logger.info("Inside addIssuer, adding: " + user.toString());
} else {
logger.info("Inside addIssuer...");
}
users.put(user.getUsername(), user);
return "{\"success\":1}";
}
This is okay for simple string response. But for complex JSON response you should use wrapper class as described by Shaun.
In one project we addressed this using JSONObject (maven dependency info). We chose this because we preferred returning a simple String rather than a wrapper object. An internal helper class could easily be used instead if you don't want to add a new dependency.
Example Usage:
#RestController
public class TestController
{
#RequestMapping("/getString")
public String getString()
{
return JSONObject.quote("Hello World");
}
}
You can easily return JSON with String in property response as following
#RestController
public class TestController {
#RequestMapping(value = "/getString", produces = MediaType.APPLICATION_JSON_VALUE)
public Map getString() {
return Collections.singletonMap("response", "Hello World");
}
}
Simply unregister the default StringHttpMessageConverter instance:
#Configuration
public class WebMvcConfiguration extends WebMvcConfigurationSupport {
/**
* Unregister the default {#link StringHttpMessageConverter} as we want Strings
* to be handled by the JSON converter.
*
* #param converters List of already configured converters
* #see WebMvcConfigurationSupport#addDefaultHttpMessageConverters(List)
*/
#Override
protected void extendMessageConverters(List<HttpMessageConverter<?>> converters) {
converters.removeIf(c -> c instanceof StringHttpMessageConverter);
}
}
Tested with both controller action handler methods and controller exception handlers:
#RequestMapping("/foo")
public String produceFoo() {
return "foo";
}
#ExceptionHandler(FooApiException.class)
public String fooException(HttpServletRequest request, Throwable e) {
return e.getMessage();
}
Final notes:
extendMessageConverters is available since Spring 4.1.3, if are running on a previous version you can implement the same technique using configureMessageConverters, it just takes a little bit more work.
This was one approach of many other possible approaches, if your application only ever returns JSON and no other content types, you are better off skipping the default converters and adding a single jackson converter. Another approach is to add the default converters but in different order so that the jackson converter is prior to the string one. This should allow controller action methods to dictate how they want String to be converted depending on the media type of the response.
I know that this question is old but i would like to contribute too:
The main difference between others responses is the hashmap return.
#GetMapping("...")
#ResponseBody
public Map<String, Object> endPointExample(...) {
Map<String, Object> rtn = new LinkedHashMap<>();
rtn.put("pic", image);
rtn.put("potato", "King Potato");
return rtn;
}
This will return:
{"pic":"a17fefab83517fb...beb8ac5a2ae8f0449","potato":"King Potato"}
Make simple:
#GetMapping("/health")
public ResponseEntity<String> healthCheck() {
LOG.info("REST request health check");
return new ResponseEntity<>("{\"status\" : \"UP\"}", HttpStatus.OK);
}
Add produces = "application/json" in #RequestMapping annotation like:
#RequestMapping(value = "api/login", method = RequestMethod.GET, produces = "application/json")
Hint: As a return value, i recommend to use ResponseEntity<List<T>> type. Because the produced data in JSON body need to be an array or an object according to its specifications, rather than a single simple string. It may causes problems sometimes (e.g. Observables in Angular2).
Difference:
returned String as json: "example"
returned List<String> as json: ["example"]
Add #ResponseBody annotation, which will write return data in output stream.
This issue has driven me mad: Spring is such a potent tool and yet, such a simple thing as writing an output String as JSON seems impossible without ugly hacks.
My solution (in Kotlin) that I find the least intrusive and most transparent is to use a controller advice and check whether the request went to a particular set of endpoints (REST API typically since we most often want to return ALL answers from here as JSON and not make specializations in the frontend based on whether the returned data is a plain string ("Don't do JSON deserialization!") or something else ("Do JSON deserialization!")). The positive aspect of this is that the controller remains the same and without hacks.
The supports method makes sure that all requests that were handled by the StringHttpMessageConverter(e.g. the converter that handles the output of all controllers that return plain strings) are processed and in the beforeBodyWrite method, we control in which cases we want to interrupt and convert the output to JSON (and modify headers accordingly).
#ControllerAdvice
class StringToJsonAdvice(val ob: ObjectMapper) : ResponseBodyAdvice<Any?> {
override fun supports(returnType: MethodParameter, converterType: Class<out HttpMessageConverter<*>>): Boolean =
converterType === StringHttpMessageConverter::class.java
override fun beforeBodyWrite(
body: Any?,
returnType: MethodParameter,
selectedContentType: MediaType,
selectedConverterType: Class<out HttpMessageConverter<*>>,
request: ServerHttpRequest,
response: ServerHttpResponse
): Any? {
return if (request.uri.path.contains("api")) {
response.getHeaders().contentType = MediaType.APPLICATION_JSON
ob.writeValueAsString(body)
} else body
}
}
I hope in the future that we will get a simple annotation in which we can override which HttpMessageConverter should be used for the output.
Simple and Straightforward send any object or return simple List
#GetMapping("/response2")
#ResponseStatus(HttpStatus.CONFLICT)
#ResponseBody List<String> Response2() {
List<String> response = new ArrayList<>(Arrays.asList("Response2"));
return response;
}
I have added HttpStatus.CONFLICT as Random response to show how to pass RequestBody also the HttpStatus
Annotate your method with the #ResponseBody annotation to tell spring you are not trying to render a view and simple return the string plain

Spring Rest RequestMethod.GET returns 400 Bad Request for #RequestParam required=true when missing

I am new to Spring and Rest Endpoints.
I have a controller, which accepts #RequestParam and returns a JSON Response.
By default the #RequestParam required = "true", which is how I need it.
I am using Spring 3.1.3
This is my Get Method in the controller:
#Controller
#RequestMapping("/path")
public class MyController{
#RequestMapping(value = "/search/again.do", method = RequestMethod.GET, produces = {
"application/json"
})
public ResponseEntity<?> find(#RequestParam(value = "test", required = true) final String test) {
return new ResponseEntity<String>("Success ", HttpStatus.OK);
}
}
When I send a get with the request param it hits the endpoint , which is how I expect.
Example : path/search/again.do?test=yes
Everything is perfect.
This is where I am having issue:
When I send a Get with that value missing:
Example: path/search/again.do
I get a 400 Bad Request. May be this is correct.
But what I want to achieve is. When the required value is missing in the GET request.
I can send a JSON response as that #RequestParam Value test is missing.
Can anybody guide me how to achieve this.
I am not sure what I am missing.
Thanks in advance.
If you look closely at your code, you'll see that the answer is staring right at you. Just change required to false and you should be good to go. When the user doesn't provide a value for GET parameter test, then you can return a special message.
#Controller
#RequestMapping("/path")
public class MyController {
#RequestMapping(value = "/search/again.do", method = RequestMethod.GET, produces = {
"application/json"
})
public ResponseEntity<?> find(#RequestParam(value = "test", required = false) final String test) {
if (test == null) {
return new ResponseEntity<String>("test parameter is missing", HttpStatus.OK);
}
else {
return new ResponseEntity<String>("Success ", HttpStatus.OK);
}
}
}
Solution 1: You can use custom #ExceptionHandler in your controller, e.g
#ExceptionHandler(MissingServletRequestParameterException.class)
public ResponseEntity<?> paramterValidationHandler(HttpServletResquest request){
//validate the request here and return an ResponseEntity Object
}
Solution 2: Would be custom spring ErrorController which I never have tried myself but it possible to override it.
Solution 3: You can write an ControllerAdvice for a global controller exception handling.
Well if you set the parameter test is required. U just can't send the request without that param. Try to change the param required= false and handle the missing param in the method. You can us something likeif(test==null) throw new Exception("Param test missing")

Categories

Resources