SpringMVC: ExceptionHandler fails to catch my custom exception - java

In my main controller, when the exception is thrown, I want it to be catched by the ExceptionHandler in my error handling controller, but that never happens. Instead, I am getting Error 500. I am suspecting the problem is in #ResponseBody annotation of my main controller. Any idea how to achieve wanted behavior?
Main controller
#RequestMapping(value = "/person/{person}", method = RequestMethod.GET)
public #ResponseBody Person execute(#PathVariable(value = "person") String person) {
if(person.isValid(person)) {
return person;
} else {
throw new ResourceNotFoundException("Invalid person format.");
}
}
Exception
#ResponseStatus(value = HttpStatus.NOT_FOUND)
public class ResourceNotFoundException extends RuntimeException {
public ResourceNotFoundException() {
}
public ResourceNotFoundException(String message) {
super(message);
}
public ResourceNotFoundException(String message, Throwable throwable) {
super(message, throwable);
}
public ResourceNotFoundException(Throwable throwable) {
super(throwable);
}
}
Error controller
private static final String ERROR_PAGE = "errors/error.jsp";
#ResponseStatus(value = HttpStatus.NOT_FOUND)
#ExceptionHandler(ResourceNotFoundException.class)
public ModelAndView invalidApiCall(){
return generateView(ERROR_404);
}
private ModelAndView generateView(String errorCode) {
return new ModelAndView(ERROR_PAGE);
}
My error view never gets generated (#ExceptionHandler never catches the exception). Instead I am getting error 500. Is there a way for ExceptionHandler to catch my exception?

Try to add #ControllerAdvice annotation for the Error Controller. If it is already added, check whether the class' package is included in package scan.

Related

Error with JUnit testing failure condition with Custom Exception

I am writing a test case for the Rest Controller for an error condition and when I set expected exception to Exception.class the test runs with no errors. When I change the expected exception to CustomException.class, the test fails with assertion error
Here is my Controller class:
#RestController
public class CustomController<CustomFieldRequest customFieldRequest, CustomField customField> {
#PutMapping(path = "/{customFieldId}",
consumes = MediaType.APPLICATION_JSON_VALUE, produces = {MediaType.APPLICATION_JSON_VALUE, MediaType.APPLICATION_ATOM_XML_VALUE})
public CustomField updateCustomField(#PathVariable String customFieldId, #RequestBody CustomFieldRequest customFieldRequest) throws CustomException {
customFieldRequest.setAuthorId(getUser());
customFieldRequest.setId(customFieldId);
return CustomService.updateCustomeField(customFieldRequest);
}
}
Here is my service class:
#Service
public class CustomServiceImpl implements CustomService {
#Override
public CustomField updateCustomeField(CustomFieldRequest customFieldRequest) throws CustomException {
}
}
Here is my custom exception class:
public class CustomException extends Exception {
public CustomException(final String message) {
super(message);
}
public CustomException(final String message, final Throwable cause) {
super(message, cause);
}
}
Here is my test class:
public class CustomControllerTest {
private CustomService customService = Mockito.mock(CustomService.class);
#Test(expected=Exception.class)
public void updateCustomFieldThrowsException() throws CustomException {
//exception.expect(Exception.class);
CustomFieldRequest request = getRequest();
when(customService.updateCustomeField(request)).thenThrow(Exception.class);
//customController.updateCustomField(request.getId(), request);
}
}
As mentioned above if I change my expected exception to CustomException.class in the test like below:
#Test(expected=CustomException.class)
public void updateCustomFieldThrowsException() throws CustomException {
//exception.expect(Exception.class);
CustomFieldRequest request = getRequest();
when(customService.updateCustomeField(request)).thenThrow(CustomException.class);
//customController.updateCustomField(request.getId(), request);
}
I see the test failing with java.lang.AssertionError. I tried the #Rule annotation(shown commented above).
You need a default constructor for CustomException or create the exeception in your test like this: new CustomException("")

how can I custom error message in AOP around annotations?

I want to custom error message in AOP around annotations.
I used to use #RestControllerAdvice before but It didn't work in AOP around method.
it outputs default error message.
I tried to input message in try ~ catch I know it's weird like //// 1 or //// 2
But I can't get to the point :(
TransactionAspect class
#Around("execution(* com.bono.server.controller..*(..))")
#Transactional
public Object caculatePerformanceTime(ProceedingJoinPoint proceedingJoinPoint) {
Object result = null;
try {
result = proceedingJoinPoint.proceed();
} catch (CustomeException e) { ////// 1
throw new ErrorMessage(CustomError.HTTP_400_MISTYPE);
}
catch (Throwable throwable) { /////// 2
return new ErrorMessage(CustomError.HTTP_400_MISTYPE);
}
return result;
}
ErrorMessage class
#Getter
#Setter
public class ErrorMessage {
private int errorCode;
private String errorMessage;
public ErrorMessage(CustomError customError) {
this.errorCode = customError.errorCode();
this.errorMessage = customError.errorMessage();
}
}
GroupExceptionAdvice class
#RestControllerAdvice
#Order(Ordered.HIGHEST_PRECEDENCE)
public class GroupExceptionAdvice extends ResponseEntityExceptionHandler {
//// 1
#ExceptionHandler(value = CustomeException.class)
public ResponseEntity<CustomErrorResponse> customhandleNotSatisfied(Exception ex, WebRequest request) {
CustomErrorResponse error = new CustomErrorResponse();
error.setTimestamp(LocalDateTime.now());
error.setError(ex.getMessage());
error.setStatus(HttpStatus.NOT_FOUND.value());
return new ResponseEntity<>(error, HttpStatus.NOT_FOUND);
}
//// 2
#ResponseStatus(value = HttpStatus.BAD_REQUEST)
#ExceptionHandler(value = CustomException.class)
public ErrorMessage handlerUnResolvedAddressException(MisTypingException e) {
return new ErrorMessage(CustomError.HTTP_400_MISTYPE);
}
}
{
"timestamp": "2019-08-12T01:14:16.467+0000",
"status": 500,
"error": "Internal Server Error",
"message": "class com.bono.server.config.exception.ErrorMessage cannot be cast to class org.springframework.http.ResponseEntity (com.bono.server.config.exception.ErrorMessage and org.springframework.http.ResponseEntity are in unnamed module of loader 'app')",
"path": "/bono/api/alarm"
}
I want to show like this
{
"code" : 102,
"message" : "got it !"
}
Converting my previous comments into an answer because the OP said that they solved his problem.
Your ErrorMessage class does not extend any Exception or Throwable, so how can you throw it? The code should not even compile and yield a compile error like:
No exception of type ErrorMessage can be thrown;
an exception type must be a subclass of Throwable
I.e. in your sample class I you ought to write something like
public class ErrorMessage extends Exception {
// (...)
}
for a checked exception or
public class ErrorMessage extends RuntimeException {
// (...)
}
for a non-checked exception. But your class definition does not extend anything, i.e. implicitly it directly extends Object.

Optimize Spring RestControllerAdvice with multiple exceptions

I have around 20 to 30 different types of different Exceptions all extending Exception class in Java.
one example is:
public class NoHandlerFoundException extends Exception {
private static final long serialVersionUID = -9079454849611061074L;
public NoHandlerFoundException() {
super();
}
public NoHandlerFoundException(final String message) {
super(message);
}
}
Other example is:
public class ResourceNotFoundException extends Exception{
private static final long serialVersionUID = -9079454849611061074L;
public ResourceNotFoundException() {
super();
}
public ResourceNotFoundException(final String message) {
super(message);
}
}
and many more.
As you can see most of the code is repeated and then I use ControllerAdvice like (I know code in ControllerAdvice argument exception class should be proper):
#ExceptionHandler({NoHandlerFoundException.class, ResourceNotFoundException.class})
#ResponseStatus(value = HttpStatus.NOT_FOUND)
public #ResponseBody ExceptionResponse handleResourceNotFound(final NoHandlerFoundException exception,
final HttpServletRequest request) {
ExceptionResponse error = new ExceptionResponse();
error.setErrorMessage(exception.getMessage());
error.callerURL(request.getRequestURI());
return error;
}
So I want to know if we have any way in which I can optimize above exceptions and not write individual classes doing almost same job n times of times but still want to differentiate between them.
Thank you.
You can use below approach to reduce code redundancy.
#ExceptionHandler(value = {NoHandlerFoundException.class, ResourceNotFoundException.class})
protected ResponseEntity handleInvalidDataException(RuntimeException exception, WebRequest request) {
ExceptionResponse error = new ExceptionResponse();
error.setErrorMessage(exception.getMessage());
error.callerURL(request.getRequestURI());
return new ResponseEntity<>(error, HttpStatus.NOT_FOUND);
}

Programmatically change http response status using spring 3 restful

I have a controller like below
#Controller("myController")
#RequestMapping("api")
public class MyController {
#RequestMapping(method = RequestMethod.GET, value = "/get/info/{id}", headers = "Accept=application/json")
public #ResponseBody
Student getInfo(#PathVariable String info) {
.................
}
#ExceptionHandler(Throwable.class)
#ResponseStatus( HttpStatus.EXPECTATION_FAILED)
#ResponseBody
public String handleIOException(Throwable ex) {
ErrorResponse errorResponse = errorHandler.handelErrorResponse(ex);
return errorResponse.toString();
}
}
The controller has an error handling mechanism, in the error handling option it always return expectation fail status code 417. But I need to set a dynamic error Http status code like 500, 403 etc depending on type of error. How do I do this?
You need to change the type of the output value ResponseEntity. Answer here:
How to respond with HTTP 400 error in a Spring MVC #ResponseBody method returning String?
I get a solution and going to share this and also like to know any good suggestions.
#Controller("myController")
#RequestMapping("api")
public class MyController {
#RequestMapping(method = RequestMethod.GET, value = "/get/info/{id}", headers = "Accept=application/json")
public #ResponseBody
Student getInfo(#PathVariable String info) {
// ...
}
}
// ...
#ExceptionHandler(Throwable.class)
//#ResponseStatus( HttpStatus.EXPECTATION_FAILED)<<remove this line
#ResponseBody
public String handleIOException(HttpServletResponse httpRes,Throwable ex){ // <<Change this
if (some condition) {
httpRes.setStatus(HttpStatus.BAD_GATEWAY.value());
} else {
httpRes.setStatus(HttpStatus.INTERNAL_SERVER_ERROR.value());
}
ErrorResponse errorResponse = errorHandler.handleErrorResponse(ex);
return errorResponse.toString();
}
Expected out in rest client :
502 Bad Gateway
{
"status":"BAD_GATEWAY",
"error":"java.lang.UnsupportedOperationException",
"message":"Some error message"
}
Thanks for your replies. I still need pointers for good practices.
Going by the code above, you need to be more careful about which exceptions you are throwing and handling. Setting up an exception handler for Throwable seems overly broad.
The way I do this is to create an ErrorMessage class with my XML/JSON marshalling annotations.
#XmlRootElement(name = "error")
public class ErrorMessage {
private Throwable exception;
private String message;
public ErrorMessage() {
this.message = "";
}
public ErrorMessage(String message) {
this.message = message;
}
public ErrorMessage(Throwable exception) {
this.exception = exception;
this.message = exception.getLocalizedMessage();
}
#XmlTransient
#JsonIgnore
public Throwable getException() {
return exception;
}
public void setException(Throwable exception) {
this.exception = exception;
}
#XmlElement(name = "message")
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
}
With that in place, I tend to create my own application exceptions and then create my exception handler methods such as:
#ExceptionHandler(ResourceNotFoundException.class)
#ResponseBody
#ResponseStatus(HttpStatus.NOT_FOUND)
public ErrorMessage handleResourceNotFoundException(ResourceNotFoundException e, HttpServletRequest req) {
return new ErrorMessage(e);
}
#ExceptionHandler(InternalServerErrorException.class)
#ResponseBody
#ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
public ErrorMessage handleInternalServerErrorException(InternalServerErrorException e, HttpServletRequest req) {
return new ErrorMessage(e);
}
With those in place, I just need to throw appropriate exceptions from my controller methods. For instance, if I throw a ResourceNotFoundException, then Spring will redirect that to my handleResourceNotFoundException method, which returns a 404, and that will also return JSON or XML representing the error.
You can use an Aspect for your API. If you define an #Around interceptor for your service, you can change the response content.

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();
}

Categories