Spring ResponseEntity best practice - java

I am new to RESTful web services in general, and am learning the Spring implementation of web services.
I am particularly interested in learning how to properly use ResponseEntity return types for most of my use cases.
I have one endpoint:
/myapp/user/{id}
This endpoint supports a GET request, and will return a JSON formatted string of the User object whose ID is {id}. I plan to annotate the controller method as producing JSON.
In the case that a user with ID {id} exists, I set a status of 200, and set the JSON string of the user in the body.
In the event that no user exists with that ID, I was going to return some flavor of a 400 error, but I am not sure what to set in the body of the ResponseEntity. Since I annotate the endpoint method as producing JSON, should I come up with a generic POJO that represents an error, and return that as JSON?

You donĀ“t need to use a generic Pojo, using RequestMapping you can create different responses for every Http code. In this example I show how to control errors and give a response accordingly.
This is the RestController with the service specification
#RestController
public class User {
#RequestMapping(value="/myapp/user/{id}", method = RequestMethod.GET)
public ResponseEntity<String> getId(#PathVariable int id){
if(id>10)
throw new UserNotFoundException("User not found");
return ResponseEntity.ok("" + id);
}
#ExceptionHandler({UserNotFoundException.class})
public ResponseEntity<ErrorResponse> notFound(UserNotFoundException ex){
return new ResponseEntity<ErrorResponse>(
new ErrorResponse(ex.getMessage(), 404, "The user was not found") , HttpStatus.NOT_FOUND);
}
}
Within the getId method there is a little logic, if the customerId < 10 It should response the Customer Id as part of the body message but an Exception should be thrown when the customer is bigger than 10 in this case the service should response with an ErrorResponse.
public class ErrorResponse {
private String message;
private int code;
private String moreInfo;
public ErrorResponse(String message, int code, String moreInfo) {
super();
this.message = message;
this.code = code;
this.moreInfo = moreInfo;
}
public String getMessage() {
return message;
}
public int getCode() {
return code;
}
public String getMoreInfo() {
return moreInfo;
}
}
And finally I'm using an specific Exception for a "Not Found" error
public class UserNotFoundException extends RuntimeException {
public UserNotFoundException(String message) {
super(message);
}
}

In the event that no user exists with that ID, I was going to return
some flavor of a 400 error, but I am not sure what to set in the body
of the ResponseEntity. Since I annotate the endpoint method as
producing JSON, should I come up with a generic POJO that represents
an error, and return that as JSON?
This is definitely a possible solution, if you want to add e.g. a more specific reason why the request failed or if you want to add a specific I18N message or just want to generify your API to provide some abstract structure.
I myself prefer the solution #Herr Derb suggested, if there is nothing to return, don't return anything. The returned data may be completely unnecessary/unused and the receiver may just discard it if the return code is anything else than 2XX.
This may be related:
http://www.bbenson.co/post/spring-validations-with-examples/
The author describes how to validate incoming models and builds a generic error response. Maybe this is something you want to do...

Related

Use Objects.requireNonNull() to validate the request body like Objects.requireNonNull(body.getName()) return error code

when I use the Objects.requireNonNull() to validate the request body, some api return error code 400, others return 500. It should be 400, because the body sent to server side is invalid. I am now confused, do anyone know how to address this. Thanks!
#PostMapping(value = "/referencing-courses")
public Object get(#RequestBody Criteria criteria) {
Objects.requireNonNull(criteria, "body is required");
Objects.requireNonNull(criteria.getContentId(), "contentId is required.");
Objects.requireNonNull(criteria.getSchemaVersion(), "schemaVersion is required.");
return findLatestTreeRevisionReference.find(criteria);
}
Objects.requireNonNull() throws a `NullPointerException if the passed parameter is null. Exceptions trigger an Internal Server Error (500).
The status code 400 is not caused by the exception but because controller parameters are not null by default and spring validates this.
As #chrylis -cautiouslyoptimistic- pointed out in the comments, you can use #Valid:
#PostMapping(value = "/referencing-courses")
public Object get(#RequestBody #Valid Criteria criteria) {
return findLatestTreeRevisionReference.find(criteria);
}
For this to work, Criteria needs to use proper annotations:
public class Criteria{
#NotNull
private String contentId;
#NotNull
private String schemaVersion;
//Getters/Setters
}
You can also create a custom exception and/or exception handler in your controller as described here if #Valid is not viable.
For example, you could catch every NullPointerException in your controller and send back a 400 Bad Request status:
#ResponseStatus(HttpStatus.BAD_REQUEST)
#ExceptionHandler(NullPointerException.class)
public void handleNPE() {
//Done by #ResponseStatus
}
However, NullPointerExceptions might occur because of different reasons than the user sending an invalid request. In order to bypass that issue, you can create your own exception that translates to the error 400:
#ResponseStatus(value=HttpStatus.NOT_FOUND, reason="Request body invalid")
public class InvalidBodyException extends RuntimeException {
//No need for additional logic
}
You can then make your own method that does the null check and use that instead of Objects.requireNonNull:
public void requireNonNull(Object o){
if(o==null){
throw new InvalidBodyException();
}
}

Customizing JAX-RS response when a ConstraintViolationException is thrown by Bean Validation

Bean Validation is a good option to validate objects, but how to customize the response of a REST API (using RESTeasy) when a ConstraintViolationException is thrown?
For example:
#POST
#Path("company")
#Consumes("application/json")
public void saveCompany(#Valid Company company) {
...
}
A request with invalid data will return a HTTP 400 status code with the following body:
[PARAMETER]
[saveCompany.arg0.name]
[{company.name.size}]
[a]
It's nice but not enough, I would like to normalize these kind of errors in a JSON document.
How can I customize this behavior?
With JAX-RS can define an ExceptionMapper to handle ConstraintViolationExceptions.
From the ConstraintViolationException, you can get a set of ConstraintViolation, that exposes the constraint violation context, then map the details you need to an abitrary class and return in the response:
#Provider
public class ConstraintViolationExceptionMapper
implements ExceptionMapper<ConstraintViolationException> {
#Override
public Response toResponse(ConstraintViolationException exception) {
List<ValidationError> errors = exception.getConstraintViolations().stream()
.map(this::toValidationError)
.collect(Collectors.toList());
return Response.status(Response.Status.BAD_REQUEST).entity(errors)
.type(MediaType.APPLICATION_JSON).build();
}
private ValidationError toValidationError(ConstraintViolation constraintViolation) {
ValidationError error = new ValidationError();
error.setPath(constraintViolation.getPropertyPath().toString());
error.setMessage(constraintViolation.getMessage());
return error;
}
}
public class ValidationError {
private String path;
private String message;
// Getters and setters
}
If you use Jackson for JSON parsing, you may want to have a look at this answer, showing how to get the value of the actual JSON property.

Java REST API: Returning Multiple objects from API Method

I am trying to return multiple objects (such as String, Boolean, MyOwnClass, etc) from a Java REST API Method using JAX-RS in Eclipse.
Here's what I have right now:
My API Method
#Path("/")
public class myAPI {
#GET
#Produces({ "application/xml", "application/json" })
#Path("/getusers")
public Response GetAllUsers() {
//Data Type #1 I need to send back to the clients
RestBean result = GetAllUsers();
//Data Type #2 I need to send with in the response
Boolean isRegistered = true;
//The following code line doesn't work. Probably wrong way of doing it
return Response.ok().entity(result, isRegistered).build();
}
}
RestBean class:
public class RestBean {
String status = "";
String description = "";
User user = new User();
//Get Set Methods
}
So I'm basically sending two data types: RestBean and Boolean.
What's the right way of sending back a JSON response with multiple data objects?
Firstly, Java conventions are that class names begin with an uppercase letter and method names with a lowercase letter. It's generally a good idea to follow them.
You need to wrap your response inside a single class, as #Tibrogargan suggests.
public class ComplexResult {
RestBean bean;
Boolean isRegistered;
public ComplexResult(RestBean bean, Boolean isRegistered) {
this.bean = bean;
this.isRegistered = isRegistered;
}
}
and then your resource looks like...
public Response getAllUsers() {
RestBean restBean = GetAllUsers();
Boolean isRegistered = true;
final ComplexResult result = new ComplexResult(bean, isRegistered);
return Response.ok().entity(Entity.json(result)).build();
}
What you really need to know, however, is what your response document should look like. You can only have a single response document - which is what the wrapper is for - and the way your wrapper is serialised affects how the parts of the document are accessed.
Note - you have your resource listed as being able to produce both XML and JSON and what I've done only works for json. You can get the framework to do all the content-negotiation heavy lifting for you, and that would probably be a good idea, by just returning the document type from the method rather than Response ...
public ComplexResponse getAllUsers() {
...
return new ComplexResult(bean, isRegistered);

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

Dropwizard example giving 400 error when creating new resource

I am new to the Dropwizard framework. I am trying to work on creating a new resource similar to person and people resource mentioned in the tutorial here https://github.com/dropwizard/dropwizard/tree/master/dropwizard-example.
I am creating a document class like this -
#Entity
#Table(name = "document")
#NamedQueries({
#NamedQuery(
name = "com.example.helloworld.core.Document.findAll",
query = "SELECT d FROM Document d"
),
#NamedQuery(
name = "com.example.helloworld.core.Document.findById",
query = "SELECT d FROM Document d WHERE d.Id = :Id"
)
})
public class Document {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private long Id;
#Column(name = "ProcessingSetID")
private String ProcessingSetID;
#Column(name = "processed")
private String processed;
public long getId() {
return Id;
}
public void setId(long id) {
this.Id = id;
}
public String getProcessingSetID() {
return ProcessingSetID;
}
public void setProcessingSetID(String processingSetID) {
ProcessingSetID = processingSetID;
}
public String getProcessed() {
return processed;
}
public void setProcessed(String processed) {
this.processed = processed;
}
}
My document Dao is like this,
public Optional<Document> findById(Long id) {
return Optional.fromNullable(get(id));
}
public Document create(Document document) {
return persist(document);
}
public List<Document> findAll() {
return list(namedQuery("com.example.helloworld.core.Document.findAll"));
}
}
I am trying to call the POST method on my document resource,
#Path("/documents")
#Produces(MediaType.APPLICATION_JSON)
public class DocumentsResource {
private final DocumentDao documentDAO;
private static final Logger log = LoggerFactory.getLogger(DocumentsResource.class);
public DocumentsResource(DocumentDao documentDAO) {
this.documentDAO = documentDAO;
}
#POST
#UnitOfWork
public Document createDocument(Document document) {
log.info("inside POST method of document.");
System.out.println("inside POST method of document.....");
return documentDAO.create(document);
}
#GET
#UnitOfWork
public List<Document> listDocuments() {
return documentDAO.findAll();
}
}
But I am getting a 400 response back from my client request, please find below the client request
Client client = Client.create();
WebResource webResource = client.resource("http://localhost:8080/documents");
String input = "{\"processed\":\"new process\",\"ProcessingSetID\":\"new iD\"}";
ClientResponse response =
webResource.type("application/json").post(ClientResponse.class, input);
if (response.getStatus() != 200) {
throw new RuntimeException("Failed : HTTP error code : "
+ response.getStatus());
}
I tried to debug the problem, but the call is not reaching the POST method at the first place. It seems that it is not creating the document object from the JSON string, but i could not see a reason for that. Also when I do an entry directly in my database and make a GET call, perfect JSON string equivalent to object is received.
To get helpful message regarding 400 error, register this on jersey:
environment.jersey().register(new JsonProcessingExceptionMapper(true));
It will give more detailed message on 400 response, useful for debugging.
A little background: Dropwizard utilizes Jersey, and Jersey is what ultimately gives you back the 400 Bad Request response, probably along with a vague and laconic message.
In order to see exactly what did bother Jackson (which in turn bothered Jersey), I started out by sending a blank (empty) JSON object and see whether it was accepted (it did - and all the fields in the POJO where zero-initialized). Then I started to add fields, sending each such object along, until I reached the problematic field (in my case it was a boolean field which should have been a Boolean).
I think I can spot two difficulties in your POJO (the Document class):
The getters/setters should be annotated with #JsonProperty.
Try to change Id's type to Long (nullable long). If you are concerned about getting a null in that field, you can have the getter return a zero or any default value instead.
I faced the same issue. The errors are suppressed and not passed properly in the stack trace.
What I did was to add a try catch around the function. Then added a debugger point in the exception. I was able to figure out the exact reason.
You could try something like this.
#POST
#UnitOfWork
public Document createDocument(Document document) throws Exception{
....
}
Add debugger points in the Exception class. You will find out the exact reason of the parsing failure.
Hope I am clear and it helps!
Http status 400 means "bad request". Which it is, the json you are sending is not a valid Document.
This in turn means you will never reach the body of
#POST
#UnitOfWork
public Document createDocument(Document document){}
To solve it, try passing the json:
String input = "{\"id\":\"123456789\",\"processed\":\"new process\",\"ProcessingSetID\":\"new iD\"}";
Replace 123456789 with your actual id.
PS. it might be a good idea (depending on your scenario) to create a DTO for Document instead of passing the actual entity around.
If you are registering the Jersey's CsrfProtectionFilter in your Dropwizard *Application.java within the run(...) method, make sure you're adding the X-Requested-By header to all of your state changing HTTP calls (POST, PUT, etc). The server will return an HTTP 400 Bad Request if that header is not found in the request.

Categories

Resources