Reputation: 81
I have a REST controller in Spring-Boot, either returning the MyPojo.class, or RESTError.class.
I then call this controller from another Spring Service with RestTemplate and want to either get a MyPojo Object, or a RESTError Object.
Is there any way to do this, other than checking the raw String and then manually parsing?
I have two Spring Services.
Lets call them "server-service" and "client-service". The "client-service" has to retrieve a POJO from the "server-service". If something went wrong (Invalid parameter, missing parameter, etc.) a custom error object is returned, that details the error with a 'developer message' and a more broad 'user message'.
That way, if an error occured, I will be able to display the 'user message' in the ui.
This is only meant as a last resort. I should of course do validation in my "client-service" first, before I call the "server-service". Yet, imagine somebody changing the "server-service" without my knowledge...
MyPojo
@Getter @Setter @Accessors(chain = true)
public class MyPojo extends MyPojoSuper{
private String someString;
//hashCode & equals & toString
}
RESTError
Following is the custom error object. It has the errorMessage for the general user and the developerMessage for a developer.
@JsonIgnoreProperties(ignoreUnknown = true)
@Getter @Setter @Accessors(chain = true)
public class RESTError extends MyPojoSuper{
private String errorMessage, developerMessage;
//hashCode & equals & toString
}
@ControllerAdvice
The class ExceptionHandlerAdvice will handle all thrown exeptions and return the RESTError.
@ControllerAdvice
@RequestMapping("/error")
@Slf4j
public class ExceptionHandlerAdvice {
@ExceptionHandler(MissingServletRequestParameterException.class)
@ResponseStatus(value = HttpStatus.BAD_REQUEST)
@ResponseBody
public RESTError missingServletRequestParameterException(MissingServletRequestParameterException ex) {
log.info("A parameter['" + ex.getParameterName() +"'] is missing.", ex);
RESTError error = new RESTError();
error.setDeveloperMessage(ex.getMessage());
error.setErrorMessage("The parameter['" + ex.getParameterName() +"'] is missing.");
return error;
}
}
The client method calling the "server-service"
public MyPojo getMyPojo(){
restTemplate.setErrorHandler(new MyCustomResponseErrorHandler());
return restTemplate.getForObject(getURLWithParams(), MyPojo.class);
}
The RESTError Client side
@JsonIgnoreProperties(ignoreUnknown = true)
@Getter @Setter @Accessors(chain = true)
public class RESTError extends MyPojoSuper{
private String errorMessage, developerMessage;
//hashCode & equals & toString
}
MyPojoSuper
@Getter @Setter @Accessors(chain = true)
public class MyPojoSuper{
public boolean error;
//equals & hashCode & toString
}
The MyCustomResponseErrorHandler
@Slf4j
public class MyCustomResponseErrorHandler implements ResponseErrorHandler {
@Override
public boolean hasError(ClientHttpResponse response) throws IOException {
if (response.getStatusCode() != HttpStatus.OK) {
log.info("There was an error.");
return true;
}
return false;
}
@Override
public void handleError(ClientHttpResponse response) throws IOException {
if (response.getStatusCode() != HttpStatus.OK) {
log.info("There was an error.");
}
}
}
How do I handle my Custom RESTError?
By using the RestTemplate, I can only parse my JSON once and I can only parse it to one Class (either the MyPojo.class, or RESTError.class). Therefore, I could parse to a String, check the String and then reparse the JSON into the desired format:
public MyPojoSuper getMyPojo() {
ObjectMapper mapper = new ObjectMapper();
restTemplate.setErrorHandler(new MyCustomResponseErrorHandler());
String res = restTemplate.getForObject(getURL(), String.class);
try {
if (res.contains("\"error\":true")) {
return mapper.readValue(res, RESTError.class);
} else {
return mapper.readValue(res, MyPojo.class);
}
} catch (IOException e) {
e.printStackTrace();
}
return new RESTResponse().setError(true);
}
I do not like this solution and would appreciate any other suggestions.
Upvotes: 2
Views: 2551
Reputation: 2773
As far as I know, when you use restTemplate
, it throws HttpStatusCodeException
if the response code does not equal 2xx
. that's why I think you have to catch the exception. Like
try {
restTemplate.getForObject(url, MyPojo.class);
} catch (HttpStatusCodeException e) {
if (e.getStatusCode() == 404) {
String body = e.getResponseBodyAsString();
// do smth with error json
}
}
Upvotes: 0
Reputation: 81
Based on the comment by @Andrew S, here is a solution:
One can register a ResponseErrorHandler
by creating a class implementing ResponseErrorHandler
in a custom class and registering it with the RestTemplate
:
@Slf4j
public class MyCustomResponseErrorHandler implements ResponseErrorHandler {
private ObjectMapper mapper = new ObjectMapper();
@Override
public boolean hasError(ClientHttpResponse response) throws IOException {
if (response.getStatusCode() != HttpStatus.OK) {
log.info("loggin stuff");
return true;
}
return false;
}
@Override
public void handleError(ClientHttpResponse response) throws JsonParseException, JsonMappingException, IOException{
if (response.getStatusCode() != HttpStatus.OK) {
log.info("logging stuff");
throw new RestErrorExeption(mapper.readValue(response.getBody(), RESTError.class));
}
}
}
As shown this class now throws a custom exception, the RestErrorExeption
. It contains a RESTError object. One can then parse the JSON in the response body by invoking mapper.readValue(response.getBody(), RESTError.class)
. (Mapper is an instance of com.fasterxml.jackson.databind.ObjectMapper)
One can then catch the exception and get the parsed JSON as a POJO from the RestErrorExeption
:
public MyPojoSuper getMyPojo() {
restTemplate.setErrorHandler(new MyCustomResponseErrorHandler());
try {
return restTemplate.getForObject(getURLWithParams(), MyPojo.class);
} catch (RestErrorExeption e) {
log.info(e.getMessage());
return e.getRestError();
}
}
Please note, that the RESTError.class
and the MyPojo.class
need the MyPojoSuper.class
as their parent to be properly handled.
Upvotes: 1
Reputation: 81
Your RESTError class is a subclass of MyPojoSuper class
RESTError extends MyPojoSuper
And in your try-catch, you are specifically catching IOException
try {
if (res.contains("\"error\":true")) {
return mapper.readValue(res, RESTError.class);
} else {
return mapper.readValue(res, MyPojo.class);
}
} catch (IOException e) {
e.printStackTrace();
}
Can you share your class code for MyPojoSuper.java please? Should it be at least subclass of IOException so that it can be caught in your try catch.
If I understood your problem correctly.
Upvotes: 0