I am calling REST webservices from JSP using AJAX . Can you tell me the best way to send custom error message from REST webservice to JSP ?
Consider using HTTP response codes with (possibly) json response bodies to supply any required information so the client application can react accordingly.
Consider using the WebapplicationException. You can give it the Errorcode (also custom ones) and a body for the response. You could use the JSON Format if you have a complex structure to display your errors but i would suggest just using the an errormessage (for example in case of a bad request, what part of the request was bad).
If you are using JAX-RS REST webservice, you can configure Spring #Controller. Your method should produce application/json and return Response object, like in this example:
#GET
#Path("/get/{id}")
#Produces(MediaType.APPLICATION_JSON)
public Response getUserById(#PathParam("id") String userId) {
// Here your logic
Foo foo = new Foo();
foo.setMsg("Bad Request");
foo.setData("User " + userId + " not found")
return Response.status(400).entity(foo).build();
}
And from AJAX, you can catch error message
// Get user details
$.getJSON(encodeURI("./rest/user/get/" + userId), function(data) {
// Some logic on success
// Fail
}).fail( function(jqxhr) {
console.log(jqxhr.responseJSON.msg);
});
There are a couple of ways.
1. You can look at the response status you receive from the web service. The statuses starting with 2** are a success response (Eg: 200, 201), the ones starting with 4** or 5** are errors.
But the optimal way to handle and track exceptions is to use ExceptionMapper. You can write your own class that implements ExceptionMapper like below:
#Provider
public class GenericExceptionMapper implements ExceptionMapper<Throwable> {
#Override
public Response toResponse(Throwable arg0) {
return Response.status(Status.INTERNAL_SERVER_ERROR)
.entity("Custom Exception: Error retrieving data")
.build();
}
}
You can write your own custom exceptions like below or can throw blanket exception like below. The above approach is the preferred one though.
#Provider
public class GenericExceptionMapper implements ExceptionMapper<Throwable> {
#Override
public Response toResponse(Throwable arg0) {
return Response.status(Status.INTERNAL_SERVER_ERROR)
.entity("Custom Exception: Error retrieving data")
.build();
}
}
Related
I have a Feign client with a method returning the feign.Response class. When another service throws an exception, feign puts an exception message on response body and puts status, but my service does not throw an exception. Can I throw an exception based on what I received in response like when I use ResponseEntity.
Feign client
#FeignClient(name = "ms-filestorage")
#RequestMapping(value = "/files", produces = "application/json")
public interface FileStorageApi {
#GetMapping(value = "/{id}")
Response getFileById(#PathVariable String id);
}
Usage of client
#Override
public Response getFileFromStorage(String fileId) {
Response fileStorageResponse = fileStorageApi.getFileById(fileId);
// NOW I USE THIS WAY FOR CHECKING RESPONSE BUT IT DOESN'T LOOK GOOD
//if (fileStorageResponse.status() != HttpStatus.OK.value()) {
// throw new OsagoServiceException();
//}
return fileStorageResponse;
}
Usually, if a Feign client call receives an error response from the API it is calling, it throws a FeignException.
This can be caught in a try / catch block (or a Feign ErrorDecoder if you want to be more sophisticated, but that's another post).
However, this is not the case if you map the error response into a Feign.Response return type - see this Github issue.
Instead of returning Feign.Response from getFileFromStorage(), you should create a custom Java object to hold the response, and you will then have access to the FeignException which you can handle as you wish.
Note that if you don't need access to the data that is returned from the API you are calling, changing the return type to void will also resolve this issue.
I have a simple webservice that returns content either as json or as plain text (depending on the clients' accept http header).
Problem: if an error occurs during text/plain request, Spring somehow returns a 406 Not Acceptable. Which is kind of wrong, because spring could as well just write the error out as plain error text, and moreover should absolutely preserve the 400 error status:
#RestController
public class TestServlet {
#PostMapping(value = "/test", produces = {APPLICATION_JSON_VALUE, TEXT_PLAIN_VALUE, "text/csv"})
public Object post() {
throw new BadRequestException("bad req");
}
}
#ResponseStatus(HttpStatus.BAD_REQUEST)
public class BadRequestException extends RuntimeException {
public BadRequestException(String msg) {
super(msg);
}
}
POST request with accept=application/json:
{
"timestamp": "2018-07-30T14:26:02",
"status": 400,
"error": "Bad Request",
"message": "bad req",
"path": "/test"
}
BUT with accept=text/csv (or text/plain) shows an empty response with status 406 Not Acceptable.
I also noticed the DispatcherServlet.processDispatchResult() is called twice: first with my BadRequest exception, 2nd time with HttpMediaTypeNotAcceptableException. So clearly the rendering of my custom exception fails, but why?
The problem is the restrictive Accept header allowing only one content type as response. In case of an error, Spring MVC needs to handle the BadRequestException and produce the required content type using a registered HttpMessageConverter.
By default Spring Boot has no message converter to produce text/plain directly from any object. You may register an ObjectToStringHttpMessageConverter (as a bean should work for Spring Boot) to allow this and you will get the result of BadRequestException.toString() as response body.
I assume a similar problem for text/csv but I am not sure how your setup for CSV message conversion looks like.
The condition written in "produces" determines the media type to use for the response to be "text/csv". So For a success scenario it works fine, **
but when you go for rendering an exception with a JSON body that
becomes a problem and gives you a 406 instead.
**
in latest versions of spring framework the problem fixes, but in old versions,as mentioned in Spring JIRA comments you should remove HandlerMapping.PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE attribute from request
the code might be like this :
#RestControllerAdvice
public class ExampleControllerAdvice {
#ExceptionHandler(value = Exception.class)
public ResponseEntity<?> handleException(HttpServletRequest request, Exception e) {
request.removeAttribute(
HandlerMapping.PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE);
return new ResponseEntity<?>(response, HttpStatus.INTERNAL_SERVER_ERROR);
}
}
we can handle exception in advice
#ControllerAdvice
class ExceptionHandler{
#ExceptionHandler(value = {HttpMediaTypeNotAcceptableException.class})
public ResponseEntity handleMediaTypeException(HttpMediaTypeNotAcceptableException e) {
APIErrorResponse apiErrorResponse = new APIErrorResponse();
apiErrorResponse.setErrorCode("set custom code here");
apiErrorResponse.setErrorMessage("set custom meggage here/ here we can use message from object of exception i.e e.getMessage()");
return new ResponseEntity<>(errorDetails, HttpStatus.BAD_REQUEST);
}
}
I am passing a header to a spring REST api like:
#RequestHeader(value="test-header")
header is mandatory here for the API, so I do not want to keep it optional.
when no header is passed, any call to the API returns a standard 400 error indicating that request is syntantically wrong and then it does not enter the REST API. But, I want to construct a proper ResponseBody and return a json for this error. I am not sure about the best way to do this. I thought about using spring interceptor and check if this header was passed or not, but then I am not sure if I can create a responsebody from here. Atleast I could not figure out how to do so.
will interceptor approach work for this? If yes, how? If not, then what are the options? Can someone please help on this?
Update:
This is how the REST API is:
public void methodA(#RequestHeader(value="test-header") String header, #RequestBody User user, HttpServletRequest httpRequest, HttpServletResponse httpResponse) {
...
...
}
When the header is present, it will enter the REST API and continue with the logic. But, if the header is not present, it does not enter the API and simply returns a standard 400 error.
The interceptor that I wrote is like:
public class XXXInterceptor extends HandlerInterceptorAdapter {
#Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
...
...
...
return true;
}
}
STEP1: Use spring validator annotation like #valid to validate your request.
STEP 2: Write your custom validator class. which will be responsible to check the header and see if it has value or it has the expected value.
STEP 3: If the request is not correct validator throws your custom exception.
STEP 4: write your exception handler class. In the class define what response must me returned if the exception in STEP 3 is caught.
For more information on Exception Handling in Spring.
In our current projet we do use a java interceptor to authenticate the request but nothing beyound that.
Write a method with the annotation #ExceptionHandler and use ServletRequestBindingException.class as this exception is thrown in case of miss. You can return any type of object from this method.
For example
#ExceptionHandler(ServletRequestBindingException.class)
public ResponseEntity<ResponseObject> handleHeaderError(){
ResponseObject responseObject=new ResponseObject();
responseObject.setStatus(Constants.ResponseStatus.FAILURE.getStatus());
responseObject.setMessage(header_missing_message);
ResponseEntity<ResponseObject> responseEntity=new ResponseEntity<ResponseObject>(responseObject, HttpStatus.BAD_REQUEST);
return responseEntity;
}
Another approach would be using Spring Interceptors (HandlerInterceptorAdapter), as you mentioned in your question, with #ControllerAdvice and return your JSON in an #ExceptionHandler method.
Take a look at the following post: http://www.journaldev.com/2651/spring-mvc-exception-handling-exceptionhandler-controlleradvice-handlerexceptionresolver-json-response-example
This is coming late but then, a very straightforward way to deal with this type of issue is to use a Controller Advice class which allows you to handle exceptions across the whole application in one global handling component.
The exception throw by spring is the MissingRequestHeaderException which you can then provide a custom handler in your controller advice class.
#Slf4j
#RestControllerAdvice
public class ControllerAdvice {
#ExceptionHandler(MissingRequestHeaderException.class)
#ResponseStatus(value = HttpStatus.BAD_REQUEST)
public ErrorResponse handleMissingRequestHeaderException(MissingRequestHeaderException ex) {
log.error(ex.getMessage(), ex);
return new ErrorResponse("Missing request header: " + ex.getHeaderName());
}
}
public class ErrorResponse implements Serializable {
private String message;
public ErrorResponse(String message) {
this.message = message;
}
}
I have question that interest me.
Assume that I have some rest controller and some rest client writing in javascript. This client send request to a controller and during a processing occur some error. How should behave controller in this situation? Should return null? or string with message?
For example, We have controller like this:
#RequestMapping("/user", method = RequestMethod.POST)
public #ResponseBody String createUser(User user) {
try {
userService.create(user);
} catch(UserCreationException e) {
}
}
This is very simple example but is many different examples of controllers like controller which return some resources or only change state on the server side and I don't know what to do when occur error.
in improving developer(your consumers) experience , it is a good idea to respond with appropriate error messages on the response body in addition to the Http status code.
Here is an example with spring, mainly throw an exception that you can deal with by extending ResponseEntityExceptionHandler #ControllerAdvice
#ResponseStatus(HttpStatus.NOT_FOUND)
public class ResourceNotFoundException extends RuntimeException{
private static final long serialVersionUID = 1L;
public ResourceNotFoundException(String message) {
super(message);
}
}
#Controller
#RequestMapping("/XXXXXs")
public class DoctypesController {
#RequestMapping( method = RequestMethod.GET , value="/xxx")
public ResponseEntity<?> getXXXXXX(HttpServletRequest request) {
if (XXX == null ) {
throw new ResourceNotFoundException("XXXX Not found for);
}else{
response = buildResponse(xxxx)
}
return response;
}
}
#ControllerAdvice
public class XXXXEntityExceptionHandler extends ResponseEntityExceptionHandler {
#ExceptionHandler(value = { ResourceNotFoundException.class })
protected ResponseEntity<Object> handleMissingResource(RuntimeException ex, final WebRequest request) {
HttpStatus status = HttpStatus.NOT_FOUND;
return new ResponseEntity<Object>(new Error(String.valueOf(status.value()), status.getReasonPhrase(),ex.getMessage()),status);
}
}
According http specifications, the server must return a error code >= 500 in case of internal error during processing.
If the error is caused because the client did a wrong request : the server must return a error code >= 400 and < 500
Of course, on client side you must take care to handle those errors properly (i.e. displaying a friendly error message or something like that).
You should really use the HTTP Error codes and handle the HTTP error codes using your client-side technology, ie. JavaScript in your case.
For example: given a user who is unauthorised to read/access a Resource, then the 403 error code should be returned to the client. By using the standard HTTP/REST Error codes, you conform to an API that can be understood by any client, whether JavaScript or something else.
With Spring MVC and Rest controllers, it's really easy. Create a simple class for your Exception and annotate the class with the HTTP Error code, e.g. #ResponseStatus(value = HttpStatus.FORBIDDEN) for a 403 error. Then in your Controller, you can throw the exception which would in turn return the HTTP error code.
I'm using RESTEasy 2.2.1.GA as my JAX-RS implementation to create a client to connect to a third party service provider. (Education.com's REST API if it matters)
To make sure I haven't missed an important implementation detail here are code samples:
Service Interface
#Path("/")
public interface SchoolSearch {
#GET
#Produces({MediaType.APPLICATION_XML})
Collection<SchoolType> getSchoolsByZipCode(#QueryParam("postalcode") int postalCode);
}
Calling Class
public class SimpleSchoolSearch {
public static final String SITE_URL = "http://api.education.com/service/service.php?f=schoolSearch&key=****&sn=sf&v=4";
SchoolSearch service = ProxyFactory.create(SchoolSearch.class, SITE_URL);
public Collection<SchoolType> getSchools() throws Exception {
Collection<SchoolType> schools = new ArrayList<SchoolType>();
Collection<SchoolType> response = service.getSchoolsByZipCode(35803);
schools.addAll(response);
return schools;
}
}
After setting up tests to make this call, I execute and see the following exception being thrown.
org.jboss.resteasy.plugins.providers.jaxb.JAXBUnmarshalException: Unable to find JAXBContext for media type: text/html;charset="UTF-8"
From reading the RESTEasy/JAX-RS documentation, as I understand it, when the response is returned to the client, prior to the unmarshaling of the data, a determination is made (Content Negotiation??) about which mechanism to use for unmarshalling. (I think we're talking about a MessageBodyReader here but I'm unsure.) From looking at the body of the response, I see that what is returned is properly formatted XML, but the content negotiation (via HTTP header content-type is indeed text/html;charset ="UTF-8") is not allowing the text to be parsed by JAXB.
I think that the implementation is behaving correctly, and it is the service that is in error, however, I don't control the service, but would still like to consume it.
So that being said:
Am I correct in my understanding of why the exception is thrown?
How do I work around it?
Is there a simple one line annotation that can force JAXB to unmarshal the data, or will I need to implement a custom MessageBodyReader? (If that is even the correct class to implement).
Thanks!
Follow Up:
I just wanted to post the few changes I made to Eiden's answer. I created a ClientExecutionInterceptor using his code and the information available at Resteasy ClientExecutionInterceptor documentation. My final class looks like
#Provider
#ClientInterceptor
public class SimpleInterceptor implements ClientExecutionInterceptor {
#Override
public ClientResponse execute(ClientExecutionContext ctx) throws Exception {
final ClientResponse response = ctx.proceed();
response.getHeaders().putSingle(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_XML);
return response;
}
}
The big difference is the addition of the #Provider and #ClientExecutionInterceptor annotations. This should insure that the interceptor is properly registered.
Also, just for completeness, I registered the Interceptor slightly differently for my tests. I used:
providerFactory.registerProvider(SimpleInterceptor.class);
I'm sure there are several solutions to this problem, but I can only think of one.
Try so set the content-type using a ClientExecutionInterceptor:
public class Interceptor implements ClientExecutionInterceptor {
#Override
public ClientResponse<?> execute(ClientExecutionContext ctx) throws Exception {
final ClientResponse<?> response = ctx.proceed();
response
.getHeaders()
.putSingle(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_XML);
return response;
}
}
public void getSchools() throws Exception {
ResteasyProviderFactory.getInstance()
.getClientExecutionInterceptorRegistry()
.register( new Interceptor() );
SchoolSearch service =
ProxyFactory.create(SchoolSearch.class, SITE_URL);
}
I dont know about any such annotation, others might do, but a workaround is to create a local proxy. Create a controller, that passes all parameters to education.com using a
java.Net.URL.get()
return the answer that you received, but modify the header. Then connect your client to the local proxy controller.