Return HTTP error from RESTeasy interface - java

Is it possible to return a HTTP error from a RESTeasy interface? I am currently using chained web-filters for this but I want to know if it is possible straight from the interface...
Example sudo-code:
#Path("/foo")
public class FooBar {
#GET
#Path("/bar")
#Produces("application/json")
public Object testMethod(#HeaderParam("var_1") #DefaultValue("") String var1,
#HeaderParam("var_2") #DefaultValue("") String var2 {
if (var1.equals(var2)) {
return "All Good";
} else {
return HTTP error 403;
}
}
}

Found the solution and it's very simple:
throw new WebApplicationException();
So:
#Path("/foo")
public class FooBar {
#GET
#Path("/bar")
#Produces("application/json")
public Object testMethod(#HeaderParam("var_1") #DefaultValue("") String var1,
#HeaderParam("var_2") #DefaultValue("") String var2 {
if (var1.equals(var2)) {
return "All Good";
} else {
throw new WebApplicationException(HttpURLConnection.HTTP_FORBIDDEN);
}
}
}

Return a javax.ws.rs.core.Response to set the response code.
import javax.ws.rs.core.Response;
#Path("/foo")
public class FooBar {
#GET
#Path("/bar")
#Produces("application/json")
public Response testMethod(#HeaderParam("var_1") #DefaultValue("") String var1,
#HeaderParam("var_2") #DefaultValue("") String var2 {
if (var1.equals(var2)) {
return Response.ok("All Good").build();
} else {
return Response.status(Response.Status.FORBIDDEN).entity("Sorry").build()
}
}
}
That will save you the stacktrace associated with an exception.

You can also throw java exceptions within your method and then provide an javax.ws.rs.ext.ExceptionMapper to map that to an Http error. The following blog has more details, particularly step #2:
https://www.javacodegeeks.com/2012/06/resteasy-tutorial-part-3-exception.html

Related

#RequestBody #Valid SomeDTO has field of enum type, custom error message

I have the following #RestController
#RequestMapping(...)
public ResponseEntity(#RequestBody #Valid SomeDTO, BindingResult errors) {
//do something with errors if validation error occur
}
public class SomeDTO {
public SomeEnum someEnum;
}
If the JSON request is { "someEnum": "valid value" }, everything works fine. However, if the request is { "someEnum": "invalid value" }, it only return error code 400.
How can I trap this error so I can provide a custom error message, such as "someEnum must be of value A/B/C".
The answer provided by #Amit is good and works. You can go ahead with that if you want to deserialize an enum in a specific way. But that solution is not scalable. Because every enum which needs validation must be annotated with #JsonCreator.
Other answers won't help you beautify the error message.
So here's my solution generic to all the enums in spring web environment.
#RestControllerAdvice
public class ControllerErrorHandler extends ResponseEntityExceptionHandler {
public static final String BAD_REQUEST = "BAD_REQUEST";
#Override
public ResponseEntity<Object> handleHttpMessageNotReadable(HttpMessageNotReadableException exception,
HttpHeaders headers, HttpStatus status, WebRequest request) {
String genericMessage = "Unacceptable JSON " + exception.getMessage();
String errorDetails = genericMessage;
if (exception.getCause() instanceof InvalidFormatException) {
InvalidFormatException ifx = (InvalidFormatException) exception.getCause();
if (ifx.getTargetType()!=null && ifx.getTargetType().isEnum()) {
errorDetails = String.format("Invalid enum value: '%s' for the field: '%s'. The value must be one of: %s.",
ifx.getValue(), ifx.getPath().get(ifx.getPath().size()-1).getFieldName(), Arrays.toString(ifx.getTargetType().getEnumConstants()));
}
}
ErrorResponse errorResponse = new ErrorResponse();
errorResponse.setTitle(BAD_REQUEST);
errorResponse.setDetail(errorDetails);
return handleExceptionInternal(exception, errorResponse, headers, HttpStatus.BAD_REQUEST, request);
}
}
This will handle all the invalid enum values of all types and provides a better error message for the end user.
Sample output:
{
"title": "BAD_REQUEST",
"detail": "Invalid enum value: 'INTERNET_BANKING' for the field: 'paymentType'. The value must be one of: [DEBIT, CREDIT]."
}
#ControllerAdvice
public static class GenericExceptionHandlers extends ResponseEntityExceptionHandler {
#Override
protected ResponseEntity<Object> handleHttpMessageNotReadable(HttpMessageNotReadableException e, HttpHeaders headers, HttpStatus status, WebRequest request) {
return new ResponseEntity<>(new ErrorDTO().setError(e.getMessage()), HttpStatus.BAD_REQUEST);
}
}
I created a fully functional Spring boot Application with a Test on Bitbucket
You do not need #Valid for enum validation, you can achieve the required response using below code:
Controller Code, StackDTO has an enum PaymentType in it:
#RequestMapping(value = "/reviews", method = RequestMethod.POST)
#ResponseBody
public ResponseEntity<String> add(#RequestBody StackDTO review) {
return new ResponseEntity<String>(HttpStatus.ACCEPTED);
}
Create an exception class, as EnumValidationException
public class EnumValidationException extends Exception {
private String enumValue = null;
private String enumName = null;
public String getEnumValue() {
return enumValue;
}
public void setEnumValue(String enumValue) {
this.enumValue = enumValue;
}
public String getEnumName() {
return enumName;
}
public void setEnumName(String enumName) {
this.enumName = enumName;
}
public EnumValidationException(String enumValue, String enumName) {
super(enumValue);
this.enumValue = enumValue;
this.enumName = enumName;
}
public EnumValidationException(String enumValue, String enumName, Throwable cause) {
super(enumValue, cause);
this.enumValue = enumValue;
this.enumName = enumName;
}
}
I have enum as below, with a special annotation #JsonCreator on a method create
public enum PaymentType {
CREDIT("Credit"), DEBIT("Debit");
private final String type;
PaymentType(String type) {
this.type = type;
}
String getType() {
return type;
}
#Override
public String toString() {
return type;
}
#JsonCreator
public static PaymentType create (String value) throws EnumValidationException {
if(value == null) {
throw new EnumValidationException(value, "PaymentType");
}
for(PaymentType v : values()) {
if(value.equals(v.getType())) {
return v;
}
}
throw new EnumValidationException(value, "PaymentType");
}
}
Finally RestErrorHandler class,
#ControllerAdvice
public class RestErrorHandler {
#ExceptionHandler(HttpMessageNotReadableException.class)
#ResponseStatus(HttpStatus.BAD_REQUEST)
#ResponseBody
public ResponseEntity<ValidationErrorDTO> processValidationIllegalError(HttpMessageNotReadableException ex,
HandlerMethod handlerMethod, WebRequest webRequest) {
EnumValidationException exception = (EnumValidationException) ex.getMostSpecificCause();
ValidationErrorDTO errorDTO = new ValidationErrorDTO();
errorDTO.setEnumName(exception.getEnumName());
errorDTO.setEnumValue(exception.getEnumValue());
errorDTO.setErrorMessage(exception.getEnumValue() + " is an invalid " + exception.getEnumName());
return new ResponseEntity<ValidationErrorDTO>(errorDTO, HttpStatus.BAD_REQUEST);
}
}
ValidationErrorDTO is the dto with setters/getters of enumValue, enumName and errorMessage. Now when you send POST call to controller endpoint /reviews with below request
{"paymentType":"Credit2"}
Then code returns response as 400 with below response body -
{
"enumValue": "Credit2",
"enumName": "PaymentType",
"errorMessage": "Credit2 is an invalid PaymentType"
}
Let me know if it resolves your issue.
Yon can achieve this using #ControllerAdvice as follows
#org.springframework.web.bind.annotation.ExceptionHandler(value = {InvalidFormatException.class})
public ResponseEntity handleIllegalArgumentException(InvalidFormatException exception) {
return ResponseEntity.badRequest().body(exception.getMessage());
}
Basically , the idea is to catch com.fasterxml.jackson.databind.exc.InvalidFormatException and handle it as per your requirement.
#Valid has to do with Hibernate bean validation. Currently enum type is not supported out of the box. I found this answer to be the closet, https://funofprograming.wordpress.com/2016/09/29/java-enum-validator/, the drawback however is that you have to make the enum field of type String instead.

Restlet error response format in JSON

Im using the restlet framework to manager a projects API. It seems that by default error responses are formatted in HTML. How can I change that so that by default ALL error responses are in JSON format?
I've tried adding a custom converter which works great for the entity responses but not for error responses.
We have 110+ endpoints that support application/json so ideally I would like to just set the default errors to always return as JSON. The default converter works for all methods that return an actual entity.
#Get("json")
#Produces("application/json")
public User represent() {
...
return result;
}
But the ResourceException thrown by this method returns HTML.
If you are sure about the format your service is going to produce then you can annotate your service class with #Produces annotation at class level. Then you will not be required to define the same for each and every method.
Also, once #Produces is defined at class level and you want to change response format for a particular method then you can annotate that particular method for other format.
Try Below code..
public Response represent(){
try{
}catch(Exception ex){
return Response.status(500)
.entity(new ExceptionMessage("500", ex.getMessage()))
.type(MediaType.APPLICATION_JSON).
build();
}
return Response.status(Response.Status.OK).entity(result).build();
}
And have below Model class for exception message.
#XmlRootElement
class ExceptionMessage{
private String statusCode;
private String errorMessage;
public ExceptionMessage() {
}
public ExceptionMessage(String statusCode, String errorMessage) {
this.statusCode = statusCode;
this.errorMessage = errorMessage;
}
public String getErrorMessage() {
return errorMessage;
}
public void setErrorMessage(String errorMessage) {
this.errorMessage = errorMessage;
}
public String getStatusCode() {
return statusCode;
}
public void setStatusCode(String statusCode) {
this.statusCode = statusCode;
}
}
This is the link dedicated to Restlet.

Jersey: hardcode POST/PUT ObjectMapper, without needing Content-Type header

I have a Jersey 1.19.1 resource that implements a #PUT and #POST method. The #PUT method expects a JSON string as the input/request body, while the #POST method accepts plain text.
For the JSON mapping I am using Jackson 2.8.
Since the resource is defined to work this way, I don't want the client to be required to specify a Content-Type request header, just because Jersey needs it to figure out which ObjectMapper to use on the request body.
What I want instead, is to tell Jersey "Use this ObjectMapper for this #PUT input", or "Always assume this input will have an application/json Content-Type on this method."
#Produces(MediaType.APPLICATION_JSON)
#Path("/some/endpoint/{id}")
public class MyResource {
#PUT
public JsonResult put(
#PathParam("id") String id,
// this should always be deserialized by Jackson, regardless of the `Content-Type` request header.
JsonInput input
) {
log.trace("PUT {}, {}, {}", id, input.foo, input.bar);
return new JsonResult("PUT result");
}
#POST
public JsonResult post(
#PathParam("id") String id,
// this should always be treated as plain text, regardless of the `Content-Type` request header.
String input
) {
log.trace("POST {}, {}", id, input);
return new JsonResult("POST result");
}
}
I only found this answer, but that's not what I'm looking for, as the solution there seems to be that the client should be required to add the correct Content-Type header, or otherwise do the object mapping manually.
I managed to come up with a workaround. Instead of declaring which ObjectMapper to use on a Jersey resource method, I decided to create a ResourceFilter, corresponding ResourceFilterFactory, and an annotation type. Whenever a resource class or method is annotated with this type, the ResourceFilter will override the request's Content-Type to whatever is declared in the annotation's parameter.
Here's my code:
OverrideInputType annotation:
#Target({ElementType.TYPE, ElementType.METHOD})
#Retention(RetentionPolicy.RUNTIME)
public #interface OverrideInputType {
// What the Content-Type request header value should be replaced by
String value();
// which Content-Type request header values should not be replaced
String[] except() default {};
}
OverrideInputTypeResourceFilter:
public class OverrideInputTypeResourceFilter implements ResourceFilter, ContainerRequestFilter {
private MediaType targetType;
private Set<MediaType> exemptTypes;
OverrideInputTypeResourceFilter(
#Nonnull String targetType,
#Nonnull String[] exemptTypes
) {
this.targetType = MediaType.valueOf(targetType);
this.exemptTypes = new HashSet<MediaType>(Lists.transform(
Arrays.asList(exemptTypes),
exemptType -> MediaType.valueOf(exemptType)
));
}
#Override
public ContainerRequest filter(ContainerRequest request) {
MediaType inputType = request.getMediaType();
if (targetType.equals(inputType) || exemptTypes.contains(inputType)) {
// unmodified
return request;
}
MultivaluedMap<String, String> headers = request.getRequestHeaders();
if (headers.containsKey(HttpHeaders.CONTENT_TYPE)) {
headers.putSingle(HttpHeaders.CONTENT_TYPE, targetType.toString());
request.setHeaders((InBoundHeaders)headers);
}
return request;
}
#Override
public final ContainerRequestFilter getRequestFilter() {
return this;
}
#Override
public final ContainerResponseFilter getResponseFilter() {
// don't filter responses
return null;
}
}
OverrideInputTypeResourceFilterFactory:
public class OverrideInputTypeResourceFilterFactory implements ResourceFilterFactory {
#Override
public List<ResourceFilter> create(AbstractMethod am) {
// documented to only be AbstractSubResourceLocator, AbstractResourceMethod, or AbstractSubResourceMethod
if (am instanceof AbstractSubResourceLocator) {
// not actually invoked per request, nothing to do
log.debug("Ignoring AbstractSubResourceLocator {}", am);
return null;
} else if (am instanceof AbstractResourceMethod) {
OverrideInputType annotation = am.getAnnotation(OverrideInputType.class);
if (annotation == null) {
annotation = am.getResource().getAnnotation(OverrideInputType.class);
}
if (annotation != null) {
return Lists.<ResourceFilter>newArrayList(
new OverrideInputTypeResourceFilter(annotation.value(), annotation.except()));
}
} else {
log.warn("Got an unexpected instance of {}: {}", am.getClass().getName(), am);
}
return null;
}
}
Example MyResource demonstrating its use:
#Produces(MediaType.APPLICATION_JSON)
#Path(/objects/{id}")
public class MyResource {
#PUT
// #Consumes(MediaType.APPLICATION_JSON)
#OverrideInputType(MediaType.APPLICATION_JSON)
public StatusResult put(#PathParam("id") int id, JsonObject obj) {
log.trace("PUT {}", id);
// do something with obj
return new StatusResult(true);
}
#GET
public JsonObject get(#PathParam("id") int id) {
return new JsonObject(id);
}
}
In Jersey 2, you could do this with a post-matching ContainerRequestFilters

JAX-RS jersey ExceptionMappers User-Defined Exception

I am new to this, trying to achieve reading some docs but its not working, please bear with me.
I have created a UserNotFoundMapper using ExceptionMappers like this:
public class UserNotFoundMapper implements ExceptionMapper<UserNotFoundException> {
#Override
public Response toResponse(UserNotFoundException ex) {
return Response.status(404).entity(ex.getMessage()).type("text/plain").build();
}
}
This in my service:
#GET
#Path("/user")
public Response getUser(#QueryParam("id") String id) throws UserNotFoundException{
//Some user validation code with DB hit, if not found then
throw new UserNotFoundException();
}
The UserNotFoundException is an User-Defined Exception.
I tried this:
public class UserNotFoundException extends Exception {
//SOME block of code
}
But when I invoke the service, the UserDefinedExceptionMapper is not getting invoked. It seems I might be missing something in the UserDefinedException. How to define this exception then?
Please let me know how to define the UserNotFoundException.
You need to annotate your exception mapper with #Provider, otherwise it will never get registered with the JAX-RS runtime.
#Provider
public class UserNotFoundMapper implements
ExceptionMapper<UserNotFoundException> {
#Override
public Response toResponse(UserNotFoundException ex) {
return Response.status(404).entity(ex.getMessage()).type("text/plain")
.build();
}
}
What I usually do when creating APIs is create my own exception that extends from RuntimeException so I don't necessarily have to catch my exception.
Here's an example:
NOTE: I'm using JAX-RS with Jersey
First: create my own Exception that extends from RuntimeException.
public class ExceptionName extends RuntimeException {
private int code;
private String message;
public int getCode(){
return code;
}
public String getMessage(){
return message;
}
public ExceptionName(int code, String message) {
this.code = code;
this.message = message;
}
}
Also implement a ExceptionMapper
#Provider
public class ExceptionName implements ExceptionMapper<ExceptionName>{
#Override
public Response toResponse(ExceptionName exception) {
return Response.status(exception.getCode()).entity(exception.getMessage()).build();
}
}
And every time that I want to throw an exception I just do it like this anywhere, the exception mapper will take care of returning a response to the client consuming the API
throw new ExceptionName(500,"there was an error with something here");
One small remark , try to Use Response.Status.NOT_FOUND rather than using 404 etc. Code will be more readable and less prone to typos , the same goes for "text/plain". Below is the code that will handle exception as you mentioned.
Oh and one more thing remember to annotate your method #Produces(MediaType.TEXT_PLAIN) in your interface
public class UserNotFoundException extends Exception {
//...
}
public class UserServiceImpl implements UserService {
#Override
public Response getUser(#QueryParam("id") String id) {
final Response response;
try{
// call user method
//if everything is ok
response = Response.status(Response.Status.OK).entity(whateverYouWant).type(MediaType.TEXT_PLAIN).build();
} catch(UserNotFoundException ex) {
response = new UserNotFoundMapper().toResponse(ex);
}
return response;
}
}
In client slide you can check
public static boolean isUserExists(final Response serverResp) {
return serverResp != null && serverResp.getStatus() == Response.Status.NOT_FOUND.getStatusCode();
}

JAX/Jersey Custom error code in Response

In Jersey, how can we 'replace' the status string associated with a known status code?
e.g.
return Response.status(401).build();
generates a HTTP response that contains:
HTTP/1.1 401 Unauthorized
I (not me, but the client application) would like to see the response as:
HTTP/1.1 401 Authorization Required
I tried the following approaches but in vain:
1) This just adds the String in the body of the HTTP response
return Response.status(401).entity("Authorization Required").build();
2) Same result with this too:
ResponseBuilder rb = Response.status(401);
rb = rb.tag("Authorization Required");
return rb.build();
Appreciate your help!
-spd
To do this in Jersey you have the concept of WebApplicationException class. One method is to simply extend this class and all one of the methods to set the error text that is returned. In your case this would be:
import javax.ws.rs.*;
import javax.ws.rs.core.*;
import javax.ws.rs.core.Response.*;
public class UnauthorizedException extends WebApplicationException {
/**
* Create a HTTP 401 (Unauthorized) exception.
*/
public UnauthorizedException() {
super(Response.status(Status.UNAUTHORIZED).build());
}
/**
* Create a HTTP 404 (Not Found) exception.
* #param message the String that is the entity of the 404 response.
*/
public UnauthorizedException(String message) {
super(Response.status(Status.UNAUTHORIZED).entity(message).type("text/plain").build());
}
}
Now in your code that implements the rest service you would simply throw a new exception of this type, passing in the text value in the constructor e.g.
throw new UnauthorizedException("Authorization Required");
That can create a class like this for each of your web exceptions and throw in a similar fashion.
This is also explained in the Jersey user guide - although the code is actually slightly incorrect:
https://jersey.github.io/nonav/documentation/latest/user-guide.html/#d4e435
I'm not sure JSR 339: JAX-RS 2.0: The Java API for RESTful Web Services already covered this or not.
You might have to extend the Response.StatusType for this.
public abstract class AbstractStatusType implements StatusType {
public AbstractStatusType(final Family family, final int statusCode,
final String reasonPhrase) {
super();
this.family = family;
this.statusCode = statusCode;
this.reasonPhrase = reasonPhrase;
}
protected AbstractStatusType(final Status status,
final String reasonPhrase) {
this(status.getFamily(), status.getStatusCode(), reasonPhrase);
}
#Override
public Family getFamily() { return family; }
#Override
public String getReasonPhrase() { return reasonPhrase; }
#Override
public int getStatusCode() { return statusCode; }
public ResponseBuilder responseBuilder() { return Response.status(this); }
public Response build() { return responseBuilder().build(); }
public WebApplicationException except() {
return new WebApplicationException(build());
}
private final Family family;
private final int statusCode;
private final String reasonPhrase;
}
And here are some extended statust types.
public class BadRequest400 extends AbstractStatusType {
public BadRequest400(final String reasonPhrase) {
super(Status.BAD_REQUEST, reasonPhrase);
}
}
public class NotFound404 extends AbstractStatusType {
public NotFound404(final String reasonPhrase) {
super(Status.NOT_FOUND, reasonPhrase);
}
}
This is how I do.
#POST
public Response create(final MyEntity entity) {
throw new BadRequest400("bad ass").except();
}
#GET
public MyEntity read(#QueryParam("id") final long id) {
throw new NotFound404("ass ignorant").except();
}
// Disclaimer
// I'm not a native English speaker.
// I don't know what 'bad ass' or 'ass ignorant' means.

Categories