How to apply #ControllerAdvice from a non-controller? - spring-boot

Is there a way in which we can generate the JSON response from within the code that an API endpoint would show when a controller is called.
I basically want the application to call one of its own controllers and generate the response as a JSON string but not by making an HTTP call.
Calling the controller method and applying an objectmapper on it gives back the results just fine, but when there are exceptions it throws it through. However, I want it to generate the error response that would have generated from #ControllerAdvice exception handlers.
Is it possible to apply the exception handlers defined in #ControllerAdvice to an exception caught in a variable from within the application (Not by making an HTTP call)?
I am building a bulk api so that instead of the user calling an api multiple times, they can just put a list of payloads to this new bulk api, and internally I want to save the responses of each subrequest within the bulk api's db entity itself, and for that I need the exact json responses for each subrequest as if the original api was called.
I can do an exception handling separately for this bulk api code but then that would duplicate the code I already have in my controller advice.
One solution I have come up with is given below. However, if I use it I'll have to remove all my current exception handlers and just use one single exception handler accepting Exception.
Controller Advice:
#ExceptionHandler(ConversionFailedException.class)
public ResponseEntity<ErrorResponse> handleConversionFailedException(final ConversionFailedException e) {
return buildResponseEntity(HttpStatus.BAD_REQUEST, e);
}
#ExceptionHandler(value = {ActionNotFoundException.class})
public ResponseEntity<ErrorResponse> handleActionNotFoundException(ActionNotFoundException e) {
return buildResponseEntity(HttpStatus.NOT_FOUND, e);
}
// someone just added it
#ExceptionHandler(value = {NewRuntimeException.class})
public ResponseEntity<ErrorResponse> handleNewRuntimeException(NewRuntimeException e) {
return buildResponseEntity(HttpStatus.INTERNAL_SERVER_ERROR, e);
}
Bulk API code:
try {
}catch(ConversionFailedException e){
return buildResponseEntity(HttpStatus.BAD_REQUEST, e);
}catch(ActionNotFoundException e){
return buildResponseEntity(HttpStatus.NOT_FOUND, e);
}
// they forgot to add it here
My current solution:
Controller Advice:
#ExceptionHandler(Exception.class)
public ResponseEntity<String> handleAllExceptions(final Exception e) {
return ExceptionHandlerUtil.funcA(e);
}
ExceptionHandlerUtil class (New):
private ResponseEntity<ErrorResponse> funcA(Exception e) {
if (e instanceof ConversionFailedException) {
return buildResponseEntity(HttpStatus.BAD_REQUEST, e);
}
else if (e instanceof ActionNotFoundException) {
return buildResponseEntity(HttpStatus.NOT_FOUND, e);
}
// they can add any new exception here, and both the parts of the code will be able to handle it
}
Bulk API code:
try {
}catch(Exception e){
return ExceptionHandlerUtil.funcA(e);
}

Related

Get Stack trace from HttpServletRequest in spring boot

I'm currently running a spring boot application.
I am putting this webpage live for multiple people to use. However, this is the first time launching it and I'm unsure if I've worked out all the bugs so I'm trying to find a way to alert myself when something happens.
I have created a custom error controller in Spring. I want this to display a custom error page that just simply tells the user that something is wrong and that we've been made aware of it. This part is working already.
Also with that, I want it to send me an email with the stack trace information. I would love the same page that the default error controller shows, to be sent to me via email. I'm using AWS SES.
Here's my code sample.
#GetMapping("/error")
public String handleError(HttpServletRequest request) {
Object status = request.getAttribute(RequestDispatcher.ERROR_STATUS_CODE);
if (status != null) {
int statusCode = Integer.parseInt(status.toString());
if(statusCode == HttpStatus.NOT_FOUND.value()) {
return "404";
}
}
sesSender.sendErrorEmail("strack trace error");
return "error";
}
I found the following question provided 5 years ago Spring Boot Custom Error Page Stack Trace
I'm hoping that since then, they've allowed this functionality.
If you are using Spring Boot you can use this bean and this method ExceptionUtils.getStackTrace(). You will need to import the commons.lang dependency. The method from ExceptionUtils will get you the stack trace you are looking for to send to your email. But this will only work for Serverside Errors. If you want emails sent with a stack trace for front end errors you will need to create your own Dispatcher Servlet and handle it in the DoService Filter
#Bean
HandlerExceptionResolver errorHandler() {
return (request, response, handler, ex) -> {
Logger logger = LoggerFactory.getLogger(DispatcherServlet.class);
ModelAndView model = new ModelAndView("error/error");
model.addObject("exceptionType", ex);
model.addObject("handlerMethod", handler);
logger.error(ExceptionUtils.getMessage(ex));
System.out.println("" +
"\n" + ExceptionUtils.getStackTrace(ex));
try {
Utility.sendStackTraceToDeveloper(ex, request, javaMailSender);
} catch (MessagingException e) {
e.printStackTrace();
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
return model;
};
}

How to pass and handle Exceptions through HTTP responses in Spring?

I have a Client and Server module in my Spring project running on separate ports. The Client module makes a POST request to the Server via a RestTemplate. The Server-Module throws a custom Exception with a custom error-message. Currently, in my Project, the Server has a RestControllerAdvice Class that handles such exceptions as follows:
#RestControllerAdvice
public class AppRestControllerAdvice {
#ExceptionHandler(ApiException.class)
#ResponseStatus(HttpStatus.BAD_REQUEST)
public MessageData handle(ApiException e) {
MessageData data = new MessageData();
data.setMessage(e.getMessage());
return data;
}
}
On the Client side, the following method catches the Response from the Server.
#RestControllerAdvice
public class AppRestControllerAdvice {
#ExceptionHandler(ApiException.class)
#ResponseStatus(HttpStatus.BAD_REQUEST)
public MessageData handle(ApiException e) {
MessageData data = new MessageData();
data.setMessage(e.getMessage());
return data;
}
#ExceptionHandler(Throwable.class)
#ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
public MessageData handle(Throwable e) {
MessageData data = new MessageData();
data.setMessage("UNKNOWN ERROR- " + e.getMessage());
e.printStackTrace();
return data;
}
}
Whenever the Exception is thrown on the server, here is what I receive on the Client
{
"message": "UNKNOWN ERROR- org.springframework.web.client.HttpClientErrorException: 400 Bad Request"
}
My question is, how do I retrieve the Custom Exception message that originated on the Server?
Also, why isn't the correct RestControllerAdvice module on the Client side picking up the error? (The INTERNAL_SERVER_ERROR method catches the error instead of the BAD_REQUEST method.)
My question is, how do I retrieve the Custom Exception message that originated on the Server?
To retrieve the orignal exception message you have to use dedicated ResponseErrorHandler that is capable of extracting that information, rather than using the default one (DefaultResponseErrorHandler - which I assume you use because of the message you got - org.springframework.web.client.HttpClientErrorException: 400 Bad Request).
Create:
public class CustomerResponseErrorHandler extends DefaultResponseErrorHandler {
#Override
public void handleError(ClientHttpResponse httpResponse) throws IOException {
// here you have access to the response's body which potentially contains the exception message you are interested in
// simply extract it if possible and throw an exception with that message
// in other case you can simply call `super.handlerError()` - do whatever suits you
}
}
Then use it with your RestTemplate:
#Configuration
public class RestTemplateConfig {
#Bean
public RestTemplate restTemplate(RestTemplateBuilder builder) {
return builder
          .errorHandler(new CustomerResponseErrorHandler())
          .build();
}
}
Also, why isn't the correct RestControllerAdvice module on the Client side picking up the error? (The INTERNAL_SERVER_ERROR method catches the error instead of the BAD_REQUEST method.)
The correct method is executed - your RestTemplate at the moment is throwing HttpClientErrorException which is not an ApiException. It is a Throwable though.

java.lang.IllegalStateException: getWriter() has already been called for this response, Even thought its called only once

I am using spring-boot. I want to send a CSV as the attachment of response for which I am using opencsv to write bean to response. Even though response.getWriter() is called only once, I am getting this exception.
While searching for solution, I came to know that we cannot use response.getWriter() and response.getOutputStream() together. But this is not the case here.
I am only calling getWriter and exactly once.
I also checked if it was due to opencsv library by writing a plain string to it e.g. "name, test". But still the same error. So it's not due to opencsv either.
private Pair<Boolean, String> writeCSVToResponse(List<QuestionDownloadResponse> qdrList, HttpServletResponse response) {
String fileName = new SimpleDateFormat("'CSV'yyyyMMddHHmmss'.csv'").format(new Date());
response.reset();
response.setContentType("application/csv");
response.setHeader(HttpHeaders.CONTENT_DISPOSITION,
"attachment; filename=\"" + fileName + "\"");
response.setCharacterEncoding("UTF-8");
//Find answer, for now we will return any if exists
try {
Writer writer = response.getWriter();
// Create Mapping Strategy to arrange the
// column name in order
final CustomCSVMappingStrategy<QuestionDownloadResponse> mappingStrategy = new CustomCSVMappingStrategy<>();
mappingStrategy.setType(QuestionDownloadResponse.class);
StatefulBeanToCsv<QuestionDownloadResponse> sbc = new StatefulBeanToCsvBuilder<QuestionDownloadResponse>(writer)
.withSeparator(CSVWriter.DEFAULT_SEPARATOR)
.withMappingStrategy(mappingStrategy)
.build();
sbc.write(qdrList);
} catch (IOException e) {
CMSQuestionServiceImpl.logger.error("Error in CSV IO operations", e);
return Pair.of(false, "Failed to Open file");
} catch (CsvDataTypeMismatchException | CsvRequiredFieldEmptyException e) {
CMSQuestionServiceImpl.logger.error("Error in CSV Write operation", e);
return Pair.of(false,"Failed to write in csv");
}
}
Why is this exception coming, even when response.getWriter is called exactly once. How to solve this issue.
I found the issue. The problem was that I was sending a String (a message) in response in the controller, but in service, I am sending an attachment. So getWriter() was being called first time in service by me, and the second time by spring to write the response message of type String. Solution: I set the return type of method in the controller to void ex::
#GetMapping(value = "/downloadCSV")
public void downloadCSV(#RequestBody(required = false) List<Integer> items, HttpServletResponse response){
The only problem is if something goes wrong in service, you cannot send an error message and there are other alternatives to overcome it.
I had the same error: in spring-boot
This error occures when we use a RestExceptionHandler which tries to modify the response by using response.getOutputStream(), remember, the response.getWriter() is already called (even once in our code).
So when we call response.getWriter() , and we have some exception, the RestExceptionHandler executes the response.getOutputStream() which causes java.lang.IllegalStateException: getWriter() has already been called for this response.
What I suggest to do is to add try-catch to the response.getWriter() and when you catch any exception we have to reset the response by using response.reset() like that:
try {
writer.write(beans);
} catch (Exception e) {
response.reset();
throw e;
}
After throwing the exception the RestExceptionHandler will do it's job.

Generic Error page Angular JS + Spring MVC + Controller Advice

similar or related question to this post.
I have written the multiple service calls using angular JS. psudo code here
$http.get('name').success(function(response){
$scope.name= response;
$log.info($scope.rate);
}).error(function() {
});
Now I would like to route to single error page let say error.html for any exception occurs
how would I route to error.html page in Angular JS instead of touching the hundreds of service calls.
I know I would have written/route in the error function below , but I DO NOT want to repeat in reset of my application or hundreds of service calls.
what is the alternate way. please respond
$http.get('indexrates').success(function(response){
$scope.rates= response;
$log.info($scope.rates);
}).error(function() {
$state.go('error');
});
Reference : https://spring.io/blog/2013/11/01/exception-handling-in-spring-mvc
#ControllerAdvice
class GlobalDefaultExceptionHandler {
public static final String DEFAULT_ERROR_VIEW = "error";
#ExceptionHandler(value = Exception.class)
public ModelAndView
defaultErrorHandler(HttpServletRequest req, Exception e) throws Exception {
// If the exception is annotated with #ResponseStatus rethrow it and let
// the framework handle it - like the OrderNotFoundException example
// at the start of this post.
// AnnotationUtils is a Spring Framework utility class.
if (AnnotationUtils.findAnnotation
(e.getClass(), ResponseStatus.class) != null)
throw e;
// Otherwise setup and send the user to a default error-view.
ModelAndView mav = new ModelAndView();
mav.addObject("exception", e);
mav.addObject("url", req.getRequestURL());
mav.setViewName(DEFAULT_ERROR_VIEW);
return mav;
}
}

Spring Exception Handler log all exceptions but return original response

So I am trying to log all uncaught exceptions returned by the controllers of a spring project in a generic fashion.
I was able to do this with the following exception handler:
#ControllerAdvice
public class ControllerConfig {
private final Logger logger = LoggerFactory.getLogger(this.getClass());
public static final String DEFAULT_ERROR_VIEW = "error";
#ExceptionHandler(HttpMessageNotReadableException.class)
#ResponseStatus(HttpStatus.BAD_REQUEST)
public void handleBadRequest(HttpMessageNotReadableException e) {
logger.warn("Returning HTTP 400 Bad Request", e);
throw e;
}
#ExceptionHandler(AccessDeniedException.class)
public void defaultErrorHandler(HttpServletRequest request, Exception e) throws Exception {
logger.error("Error in request:" + request.getRequestURL(), e);
throw e;
}
This also returns the error responses of the request, so I don't have to differentiate between all the different error response codes.
However, for every invocation of the method a second error log is created because of the exception thrown in the method:
Code is from org.springframework.web.servlet.mvc.method.annotation.ExceptionHandlerExceptionResolver#doResolveHandlerMethodException
try {
if (logger.isDebugEnabled()) {
logger.debug("Invoking #ExceptionHandler method: " + exceptionHandlerMethod);
}
exceptionHandlerMethod.invokeAndHandle(webRequest, mavContainer, exception);
}
catch (Exception invocationEx) {
if (logger.isErrorEnabled()) {
logger.error("Failed to invoke #ExceptionHandler method: " + exceptionHandlerMethod, invocationEx);
}
return null;
}
So is there a smarter way to return the original exception of the method?
It depends on what do you mean by "a smarter way to return the original exception". What exactly would you like to return to the client? If this is just the message of the exception you can simply return it from the exception handler and annotate the method with #ResponseBody. Spring will do the rest for you.
#ExceptionHandler(HttpMessageNotReadableException.class)
#ResponseStatus(HttpStatus.BAD_REQUEST)
#ResponseBody
public String handleBadRequest(HttpMessageNotReadableException e) {
logger.warn("Returning HTTP 400 Bad Request", e);
throw e.getMessage();
}
You can also return some custom object which wraps the exception information and any other data that you desire.

Resources