I have Created Java Rest service using Jersey Implementation.
My Requirement is to throw UserDefined Exception in my Rest Service
I have created Exception class and ExceptionMapper Classes
Here is my Java code:
public class AppServerException extends Exception {
private String errorMessage;
private int errorId;
private static final long serialVersionUID = 1L;
// Parameterless Constructor
public IT360AppServerException() {
}
// Constructor that accepts a message
public IT360AppServerException(String message, int errorid) {
super(message);
errorMessage = message;
errorId = errorid;
}
public String getErrorMessage() {
return errorMessage;
}
public int getErrorId() {
return errorId;
}
}
ExceptionMapper Class:
#Provider
public class AppServerExceptionMapper implements
ExceptionMapper<AppServerException> {
#Override
public Response toResponse(AppServerException exception) {
System.out.println("toResponse>>");
System.out.println("Error Code >>>>"+exception.getErrorId());////printing
System.out.println("Error Message >>>"+exception.getErrorMessage());//printing
ErrorResponse errorResponse = new ErrorResponse();
errorResponse.setErrorCode(exception.getErrorMessage());
errorResponse.setErrorId(exception.getErrorId());
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
.entity(errorResponse).type(MediaType.APPLICATION_JSON).build();
}
}
Problem:
I am able to print the logs inside toResponse() method but the response is not returning.Flow is stopping at the logs itself.Could any one help
RestService:
#POST
#Path("/update")
#Consumes("application/json")
#Produces("application/json")
public Response updateIT30(String json,
#HeaderParam("authorization") String authString)
throws UserAuthenticationException, IT360AppServerException {
System.out.println(" JSON Format" + json);
try{
response = client.execute(post);
}
catch(ConnectException e){
throw new IT360AppServerException("App Server is down", 403);
}
}
Related
This is the response I get from the API.
{"get":"statistics","parameters":{"country":"romania"},"errors":[],"results":1,"response":[{"continent":"Europe","country":"Romania","population":19016885,"cases":{"new":"+4521","active":156487,"critical":431,"recovered":2606660,"1M_pop":"148707","total":2827936},"deaths":{"new":"+35","1M_pop":"3407","total":64789},"tests":{"1M_pop":"1149381","total":21857638},"day":"2022-03-24","time":"2022-03-24T07:30:04+00:00"}]}
#RestController
public class CovidTrackerRestController {
#GetMapping("/hello")
public String showCovidInformation() {
// connect to a covid database
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create("https://covid-193.p.rapidapi.com/statistics?country=romania"))
.header("X-RapidAPI-Host", "covid-193.p.rapidapi.com")
.header("X-RapidAPI-Key", "mykey")
.method("GET", HttpRequest.BodyPublishers.noBody())
.build();
HttpResponse<String> response = null;
try {
response = HttpClient.newHttpClient().send(request, HttpResponse.BodyHandlers.ofString());
} catch (IOException | InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
// get the information
String responseString = response.body();
System.out.println(responseString);
Response romaniaData = null;
try {
romaniaData = new ObjectMapper().configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false)
.readValue(responseString, Response.class);
} catch (JsonProcessingException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
// format the information
System.out.println(romaniaData);
// send the information to html page
return "/tracker";
}
}
And this is my Bean class which is annotated with #Bean in the configurator class alonside the RestTemplate bean. Other properties such as Cases, Deaths etc are configured same as Response class except being declared as #Bean in the configurator because from what I know once I declare a class #Bean then other references contained automatically become beans as well.
#JsonIgnoreProperties(ignoreUnknown = true)
public class Response {
#JsonProperty("country")
private String country;
#JsonProperty("cases")
private Cases cases;
#JsonProperty("deaths")
private Deaths deaths;
#JsonProperty("day")
private String day;
#JsonProperty("time")
private String time;
#JsonProperty("test")
private Tests tests;
public String getCountry() {
return country;
}
public void setCountry(String country) {
this.country = country;
}
Your java class needs to be exact representation of received json. Let's call it Wrapper:
public class Wrapper {
#JsonProperty("response")
private List<Response> responses;
public List<Response> getResponses() {
return this.responses;
}
public void setResponses(List<Response> responses) {
this.responses = responses;
}
#Override
public String toString() {
return "Wrapper{" +
"responses=" + responses +
'}';
}
}
I am omiting some properties - get, results, etc. It looks you don't need them. Then deserialization will look like this:
Wrapper data = null;
try {
data = new ObjectMapper()
.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false)
.readValue("json", Wrapper.class);
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println(data);
Few notes:
If json property name matches field name in class, there is no need for #JsonProperty
For tests field annotation should be - #JsonProperty("tests"). Property is tests, not test
If you really want to throw the rest of the data, and only need response property, then you need to write custom deserializer and work the json tree. You can see how to do it in my answer here, or this guide, for example. Like this you can parse the response json to your class, even if their structures do not match.
Yes, your class should be like this:
public class ResponseWrapper {
public List<Response> response;
public setResponse(List<Response> response) {
this.response= response;
}
public List<Response> getResponse() {
return response;
}
}
And class Response is your class as you published it. Your class have to have the same structure as JSON
I register a DateTimeFormatterRegistrar in my class implements WebMvcConfigurer like this:
#Override
public void addFormatters(FormatterRegistry registry) {
DateTimeFormatterRegistrar registrar = new DateTimeFormatterRegistrar();
registrar.setUseIsoFormat(true);
registrar.registerFormatters(registry);
}
In the rest controller, i try to parse client GET params to an object:
#GetMapping("/api/url/path")
public APIResponse getPersonAttendList(#Valid SampleVO vo){}
SampleVO include field LocalDateTime time. If client offered wrong format of time param, the binding will be failed. Server will return 500, and print some log like this:
>ERROR o.a.c.c.C.[.[.[.[dispatcherServlet] 175 - Servlet.service() for
servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed;
nested exception is java.time.format.DateTimeParseException
My question is, how to catch this exception, and return 400 to the client? It seems ControllerAdvice is not working.
In my project I am using #RestControllerAdvice to handle such cases, an example would be,
#RestControllerAdvice
public class MyCustomExceptionsHandler {
#ExceptionHandler({HttpMessageNotReadableException.class})
public ResponseEntity handleException(HttpMessageNotReadableException httpMessageNotReadableException) {
//return the response entity you need here with the correct error
}
}
This is something that is working for me.
You should be able to catch this with a "try{}, catch(){}, and finally{}". You would try the code and then catch the exception, then return a 400 error to the client.
try {
//your code
} catch (java.time.format.DateTimeParseException e) {
//Return 400 here
} finally {
//Other code here
}
I'm sorry. I made a mistake here. First, i design to return client all response like
#Getter
#Setter
#AllArgsConstructor
public class APIResponse {
private Integer status;
private String msg;
private Object data;
#NotNull
public static APIResponse fromData(Object data) {
return new APIResponse(0, "", data);
}
#NotNull
public static APIResponse fromError(#NotNull BaseError err) {
return new APIResponse(err.getStatus(), err.getMsg(), null);
}
#NotNull
public static APIResponse fromError(#NotNull BaseError err, Object data) {
return new APIResponse(err.getStatus(), err.getMsg(), data);
}
#NotNull
public static APIResponse fromEmpty() {
return new APIResponse(0, "", null);
}
}
I made a global error catch like this:
#RestControllerAdvice
#Slf4j
public class ErrorWrapper {
private String getErrMsg(BindingResult bindingResult) {
StringBuilder stringBuilder = new StringBuilder();
for (FieldError error : bindingResult.getFieldErrors()) {
stringBuilder.append(error.getDefaultMessage()).append(";");
}
String msg = stringBuilder.toString();
log.debug("valid error:{}", msg);
return msg;
}
/**
* Bean validation error
* #see javax.validation.Valid
* #see org.springframework.validation.Validator
* #see org.springframework.validation.DataBinder
*/
#ExceptionHandler(MethodArgumentNotValidException.class)
#ResponseStatus(HttpStatus.BAD_REQUEST)
public APIResponse paramNotValidHandler(MethodArgumentNotValidException e) {
BindingResult bindingResult = e.getBindingResult();
return APIResponse.fromError(new ParamError(getErrMsg(bindingResult)));
}
#ExceptionHandler(BindException.class)
#ResponseStatus(HttpStatus.BAD_REQUEST)
public APIResponse paramBindErrorHandler(BindException e) {
BindingResult bindingResult = e.getBindingResult();
return APIResponse.fromError(new ParamError(getErrMsg(bindingResult)));
}
#ExceptionHandler(MethodArgumentTypeMismatchException.class)
#ResponseStatus(HttpStatus.BAD_REQUEST)
private APIResponse paramConvertErrorHandler(MethodArgumentTypeMismatchException e) {
log.debug("valid error:", e);
return APIResponse.fromError(new ParamError("argument error:%s", e.getCause().getMessage()));
}
#ExceptionHandler(ServletRequestBindingException.class)
#ResponseStatus(HttpStatus.BAD_REQUEST)
private APIResponse paramBindErrorHandler(ServletRequestBindingException e) {
return APIResponse.fromError(new ParamError("param bind error"));
}
#ExceptionHandler(InvalidPropertyException.class)
#ResponseStatus(HttpStatus.BAD_REQUEST)
public APIResponse invalidProperty(InvalidPropertyException e) {
return APIResponse.fromError(new ParamError(e.getPropertyName() + " format error"));
}
#ExceptionHandler(PermissionError.class)
#ResponseStatus(HttpStatus.FORBIDDEN)
private APIResponse permissionErrorHandler(PermissionError e) {
log.debug("not allowed:", e);
return APIResponse.fromError(e);
}
#ExceptionHandler(HttpRequestMethodNotSupportedException.class)
#ResponseStatus(HttpStatus.METHOD_NOT_ALLOWED)
private APIResponse methodError() {
return APIResponse.fromError(new ClientError("HTTP Method error"));
}
#ExceptionHandler(MaxUploadSizeExceededException.class)
#ResponseStatus(HttpStatus.BAD_REQUEST)
private APIResponse fileTooLarge() {
return APIResponse.fromError(new ClientError("file is too big"));
}
#ExceptionHandler(MissingServletRequestPartException.class)
#ResponseStatus(HttpStatus.BAD_REQUEST)
private APIResponse badRequest(MissingServletRequestPartException e) {
return APIResponse.fromError(new ClientError("file not exist"));
}
#ExceptionHandler(HttpMessageConversionException.class)
#ResponseStatus(HttpStatus.BAD_REQUEST)
private APIResponse badRequest(HttpMessageConversionException e) {
return APIResponse.fromError(new ClientError("can't parse"));
}
#ExceptionHandler(ClientAbortException.class)
private APIResponse clientAbortHandler(ClientAbortException e) {
return null;
}
#ExceptionHandler(ClientError.class)
#ResponseStatus(HttpStatus.BAD_REQUEST)
private APIResponse clientErrorHandler(ClientError e) {
log.debug("bad request:", e);
return APIResponse.fromError(e);
}
#ExceptionHandler(ServerError.class)
#ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
private APIResponse serverErrorHandler(ServerError e) {
log.error("server error:" + e.getMsg(), e);
return APIResponse.fromError(e);
}
#ExceptionHandler(DataIntegrityViolationException.class)
#ResponseStatus(HttpStatus.BAD_REQUEST)
private APIResponse duplicateKeyError(DataIntegrityViolationException e) {
log.debug("duplicate source:", e);
return APIResponse.fromError(new ClientError("db unqiue key error"));
}
#ExceptionHandler(Exception.class)
#ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
private APIResponse unknownErrorHandler(Exception e) {
String tips = "unknown error:";
if (e.getCause() != null) {
tips += e.getCause().getMessage();
} else if (e.getMessage() != null) {
tips += e.getMessage();
} else {
tips += e.toString();
}
log.error(tips, e);
return APIResponse.fromError(new ServerError());
}
So, if no catcher matched above, the last one will return 500.
There are so many exceptions for spring boot, i don't know how to catch them all and return 400 without missing any one.
If you want to catch all spring's exceptions in your controller advice then you need to catch Exception.class and then check the class name of the exception.
#Slf4j
#RestControllerAdvice(basePackages = "com.your.package.name")
public class ErrorWrapper {
#ExceptionHandler(Exception.class)
public APIResponse handleException(Exception ex, WebRequest request) {
if (ex.getClass().getCanonicalName().startsWith("org.springframework.")) {
return APIResponse.fromError(new ClientError(ex.getMessage()));
}
return APIResponse.fromError(new ServerError());
}
}
Surround this statement(DateTimeFormatterRegistrar registrar = new DateTimeFormatterRegistrar()) in try catch block .
try{
DateTimeFormatterRegistrar registrar = new DateTimeFormatterRegistrar();
} catch (Exception e) {
e.printStacktrace();
}
I am getting Internal Server Error on postman even though I am throwing a custom exception from my code exception.
I want to see the exception of having a valid error message and error code, what I am throwing. It will be a great help if anyone of you can help me on this point. Like how I can get a better error message. Adding below code snap.
Thanks in advance.
#Service
public class FetchActionImpl implements FetchAction {
private static Logger log = LoggerFactory.getLogger(FetchActionImpl.class);
#Autowired
FetchActionServiceImpl fetchActionService;// = new FetchActionServiceImpl();
#Override
public FetchResponse fetchActionRequest(String caseId) throws BBWException,Exception{
//String resp ="";
log.info("fetchaction Request: {}",ApplicationConstants.LOG_ENTRY_MESSAGE);
log.info("The caseId received from BRASS:\n {}",caseId);
FetchResponse resp = null;
try{
if(true) {
throw new BBWException("500","Test");
}
resp = fetchActionService.fetchIt(caseId);
log.debug("fetchaction Response: {}",resp.toString());
}
catch (BBWException be) {
throw be;
}
catch (Exception e) {
throw new BBWException("500",e.getMessage());
}
return resp;
}
}
#Api
#Path("/fetch_service")
public interface FetchAction {
#GET
#Path("/fetchaction/caseid/{caseid}")
#Produces({MediaType.APPLICATION_JSON})
//#Consumes({MediaType.TEXT_XML})
#ApiOperation(
value = "Respond BRASS Request",
notes = "Returns a XML object "
)
#ApiResponses(
value = {
#ApiResponse(code = 404, message = "Service not available"),
#ApiResponse(code = 500, message = "Unexpected Runtime error")
})
public FetchResponse fetchActionRequest(#PathParam("caseid") String caseid) throws BBWException, Exception;
}`
public class BBWException extends Exception {
private static final long serialVersionUID = -7987978270260338068L;
private String errorCode;
private String errorMessage;
public BBWException(String errorCode, String errorMessage) {
super(errorMessage);
this.errorCode = errorCode;
this.errorMessage = errorMessage;
}
public String getErrorCode() {
return errorCode;
}
public void setErrorCode(String errorCode) {
this.errorCode = errorCode;
}
public String getErrorMessage() {
return errorMessage;
}
public void setErrorMessage(String errorMessage) {
this.errorMessage = errorMessage;
}
#Override
public String toString() {
return (this.errorCode + " " + this.errorMessage);
}
}
Each time the (uncaught) exception is thrown, SpringBoot returns Http-500 Internal Server Error. There are many ways of handling exceptions in Spring.
Let's say I have my controller and I implicitly throw an exception.
#RestController
public class HelloWorldController {
#GetMapping("/hello-world")
public String helloWorld() {
throw new MyCustomException("I just can't say hello!");
}
}
It's the same as yours - you can specify anything in the exception.
First:
One of the way of handling it, is to create a class with #ControllerAdvice annotation.
#ControllerAdvice
public class GlobalExceptionHandler {
private static final Logger LOGGER = LoggerFactory.getLogger(GlobalExceptionHandler.class);
#ExceptionHandler(MyCustomException.class)
public ResponseEntity<String> handlyMyCustomException(MyCustomException e) {
logger.error("error occurred {}", e);
return new ResponseEntity<>("Something happened: " + e.getMessage(), HttpStatus.I_AM_A_TEAPOT);
}
}
This way you are able to catch the exception of your choice (globally) and return the message with the HTTP Response Status of your choice, not neccessarily I_AM_A_TEAPOT
Second:
#ExceptionHandler(MyCustomException.class)
public ResponseEntity<String> handlyMyCustomException(MyCustomException e) {
logger.error("error occurred {}", e);
return new ResponseEntity<>("Something happened: " + e.getMessage(), HttpStatus.I_AM_A_TEAPOT);
}
You could also create only method annotated with #ExceptionHandler in your controller class - but this is not global and is going to work only for this exact controller calls.
Result below:
Third:
Another way of dealing with exceptions is to create your own error .html files. If you place a file in resources/static/error/500.html it should be returned when the Http-500 Internal Server Error is thrown.
It's more of a conceptual thing. My method is supposed to return a list of Conferences. But if there is an error, I just want it to send a String response or maybe a JSON response like {err: 'Some error'}.Offcourse following method throws compiler error for this line - return e.getMessage(); . How to achieve this?
#RequestMapping(value = "/api/allconf", method = RequestMethod.GET)
public List<Conferences> getAllConf(#RequestBody Conferences conf) {
List<Conferences> allConf = new ArrayList<Conferences>();
try {
allConf.addAll(confRepository.findAll());
} catch(Exception e){
return e.getMessage();
}
return allConf;
}
e.getMessage() returns a String and you method is a Conferences List, use a new generic response class like
public class Response {
private Object content;
private String error;
// getters and setters
}
and change your method
#RequestMapping(value = "/api/allconf", method = RequestMethod.GET)
public Response getAllConf(#RequestBody Conferences conf) {
Response resp = new Response();
List<Conferences> allConf = new ArrayList<Conferences>();
try{
allConf.addAll(confRepository.findAll());
resp.setContent(allConf);
}catch(Exception e){
resp.setError(e.getMessage());
}
return resp;
}
Have one option:
Best solution it is throw an exception:
#RequestMapping(value = "/api/allconf", method = RequestMethod.GET)
public List<Conferences> getAllConf(#RequestBody Conferences conf) {
List<Conferences> allConf = new ArrayList<Conferences>();
try {
allConf.addAll(confRepository.findAll());
} catch(Exception e){
throw new IllegalArgumentException(e.getMessage());
}
return allConf;
}
And create an error handler to handle exception and how you wanna display it:
#ControllerAdvice
public class CustomErrorHandler {
#ExceptionHandler(IllegalArgumentException.class)
public void handlerIllegalArgumentException(IllegalArgumentException exception, ServletWebRequest webRequest) throws IOException {
webRequest.getResponse().sendError(HttpStatus.BAD_REQUEST.value(), exception.getMessage());
}
}
My Retrofit API method is currently accepting one payload structure. However, the backend may return a different payload structure if there's any error in the request.
For example:
public void search(String term, final CallBack <ArrayList<String>> callBack) {
RetroGenerator.createService(APIServices.class).search(term).enqueue(new Callback<ArrayList<String>> () {
#Override
public void onResponse(Call<ArrayList<String>> call, Response<ArrayList<String>> response) {
if (response.isSuccessful()) {
callBack.onSuccess(response.body());
}
return;
}
callBack.onError();
}
#Override public void onFailure(Call<ArrayList<String>> call, Throwable t) {
callBack.onError();
}
});
}
The backend is returning an array of String values. However if an error occurs, backend may return the following payload structure:
{
"error": "Term can't be empty",
"code": 403
}
But the way my API method is setup, it only accepts one java model.
API Interface:
#FormUrlEncoded
#POST("api/v1/search.json")
Call<ArrayList<String>> search(#Field("term") String term);
Currently it's accepting only an ArrayList<String> and does not accept the custom error payload model. Given that I create a new model called Error:
public class Error {
public String error;
public int code;
}
How can I switch the retrofit API method's model when an error occurs?
You can have an ErrorUtils class to handle your unsuccessful responses:
public class ErrorUtils {
public static ApiError parseError(Response<?> response) {
Converter<ResponseBody, ApiError> converter = ServiceGenerator.retrofit().
responseBodyConverter(ApiError.class, new Annotation[0]);
ApiError apiError;
try {
apiError = converter.convert(response.errorBody());
} catch (IOException e) {
apiError = new ApiError();
}
return apiError;
}
}
Then when you find an unsuccessful response, just parse the response with the ErrorUtils class:
if (!response.isSuccessful()) {
// ...
ApiError apiError = ErrorUtils.parseError(response);
}
The ApiError class:
public class ApiError {
#SerializedName("error")
private String mErrorDescription;
#SerializedName("code")
private Integer mErrorCode;
public ApiError() {}
public void setErrorCode(Integer code) {
this.mErrorCode = code;
}
public Integer getErrorCode() {
return mErrorCode;
}
public String getErrorDescription() {
return mErrorDescription;
}
public void setErrorDescription(String errorDescription) {
mErrorDescription = errorDescription;
}
}