I have a Spring Boot application that is running Netty for a REST API.
I have a WebExceptionHandler to handle exceptions that may occur, which is working. It builds appropriate responses and sends them.
The problem is, it also still logs the exception as an error. I want to change this to log it as an info instead (because we have tracking and alerts that operate differently based on info or error). It even logs things like 404's as errors.
It seems like exceptionCaught in a ChannelInboundHandler would help, but exceptionCaught is deprecated. I can't find anything that doesn't use this method, and I can't find anything referring to what (if anything) has replaced it).
I also tried using an #ControllerAdvice or #RestControllerAdvice with an #ExceptionHandler, but that is never called.
What is the correct way to intercept and handle the logging of the exception?
A minimal example implementation of our current set-up looks like this:
#RestController
class MyController {
#RequestMapping(method = [GET], value = ["endpoint"], produces = [APPLICATION_JSON_VALUE])
fun myEndpoint(): Mono<MyResponse> = createResponse()
}
#Component
#Order(Ordered.HIGHEST_PRECEDENCE)
class MyWebExceptionHandler : WebExceptionHandler {
// This does get called and sends the appropriate response back, but an error is logged somewhere outside of our code.
override fun handle(exchange: ServerWebExchange, ex: Throwable): Mono<Void> =
createErrorResponse();
}
// Tried using both, or just one at a time, no difference.
// It does get created.
#ControllerAdvice
#RestControllerAdvice
class MyExceptionHandler {
// Never called
#ExceptionHandler(Exception::class)
fun handle(ex: Exception) {
log.error(ex.message)
}
}
More information on how you are implementing your Exception Handler would be helpful.
here's a simple implementation which i follow to convert the exceptions and log them.
#ControllerAdvice
public class DefaultExceptionHandler extends ResponseEntityExceptionHandler {
private static final Logger LOG = LoggerFactory.getLogger(DefaultExceptionHandler.class);
private static String ERROR = "ERROR";
#ExceptionHandler(Exception.class)
ResponseEntity<Map<String, Map<String, String>>> exception(Exception e) {
Map<String,Map<String,String>> map = new HashMap<>(1);
Map<String,String> m = new HashMap<>(1);
m.put("message",e.getMessage());
map.put(ERROR, m);
LOG.info("some error " + e);
return new ResponseEntity<>(map, HttpStatus.INTERNAL_SERVER_ERROR);
}
}
Also don't forget to create a bean or add you ExceptionHandler class to the spring config.
#Bean
public DefaultExceptionHandler defaultExceptionHandler(){
return new DefaultExceptionHandler();
}
Related
In my experience, limited though it may be, I have only ever seen the ExceptionHandler class used to immediately return exceptions. I know this is the purpose of an ExceptionHandler class but this brings me to my question: If a request fails validation, is it possible for the ExceptionHandler class to "fix" the request body and re-run the request?
As an example, given the following object:
public class Person {
#Pattern(regexp = "[A-Za-z]")
private String firstName;
}
Could the following Handler class:
#ExceptionHandler(ParameterNotValidException.class)
public Map<String, String> handleValidationExceptions(
ParameterNotValidException ex) {
Map<String, String> errors = new HashMap<>();
ex.getBindingResult().getAllErrors().forEach((error) -> {
String fieldName = ((FieldError) error).getField();
String errorMessage = error.getDefaultMessage();
errors.put(fieldName, errorMessage);
});
return errors;
}
Be modified like this:
#ExceptionHandler(ParameterNotValidException.class)
public void handleValidationExceptions(String requestBody) {
requestBody = removeSpecialCharacters(requestBody);
try {
personController.putPerson(requestBody);
} catch (Exception e) {
//fail gracefully
}
}
My apologies in advance, this is my first question on StackOverflow.
It is not acceptable. ExceptionHandler is a common place where we can manage and handle exceptions and send the respective error code for the API response.
See documentation.
It is designed for:
Handle exceptions without the #ResponseStatus annotation (typically predefined exceptions that you didn’t write)
Redirect the user to a dedicated error view
Build a totally custom error response
In your case special characters should be handled at json serialisation\deserialisation stage. Escape JSON string in Java
I'm using SAP Hybris 1811 on my local machine. I've got custom error page handler in web.xml
<error-page>
<exception-type>java.lang.NullPointerException</exception-type>
<location>/errors</location>
</error-page>
And a controller for handling this error (note it's not extending AbstractPageController, for reason read further)
#Controller
public class ErrorController {
#RequestMapping(value = "/errors", method = RequestMethod.GET)
public ModelAndView handleErrors(Model model, HttpServletRequest httpRequest) {
httpRequest.getLocale();
.... some code here
}
}
I need to get correct current locale of the app for displaying correct language in error page, but it's still getting only English, although it should be other language.
I tried to load i18nService and it's locale for example like this, but it's still "en":
SpringHelper.getSpringBean(httpRequest, "i18nService", DefaultI18NService.class, true).getCurrentLocale()
I thought the problem was because of the ErrorController doesn't extend AbstractPageController, but when I tried that, none of the error methods could be reached.
In the end I was able to get the correct locale like this:
Locale loc = ((Locale)((CommerceJaloSession)this.pageContext.getSession().getAttribute("jalosession")).getAttribute("locale"));
We have used controller advice concept of Spring MVC where you should be able to receive all the information. Also this way, the error would be caught in the storefront itself and you will have good control there.
#ControllerAdvice(basePackages =
{ "com.custom", "de.hybris.platform", "org.springframework" })
public class GlobalControllerExceptionHandler
{
private static final Logger LOG = Logger.getLogger(GlobalControllerExceptionHandler.class);
private static final String FORWARD_TO_ERROR_PAGE = ControllerConstants.FORWARD_STMT
+ ControllerConstants.ControllerMappings.Error.ErrorController;
#ExceptionHandler(Exception.class)
public String handleException(final Exception exception, final HttpServletRequest request)
{
LOG.error("Exception caught :: " + exception.getMessage(), exception);
request.setAttribute(ControllerConstants.ControllerMappings.Error.ExceptionAttributeName, exception);
return FORWARD_TO_ERROR_PAGE;
}
}
I made a global exception handler to catch my wraped business exception.
I want to return a different status code and a custom response for each wraped exception.
So i made a global exception handler :
#ControllerAdvice
public class ExportWordProfileExceptionControllerAdvice {
#ExceptionHandler({ExportWordProfileException.class})
public ResponseEntity<ApiError> exportWordProfileException(
ExportWordProfileException ex) {
if (ex.getCause() instanceof ProfileNotFoundException) {
var apiError = new ApiError(HttpStatus.NOT_FOUND, ex.getCause().getMessage());
return new ResponseEntity<>(apiError, HttpStatus.NOT_FOUND);
}
var apiError = new ApiError(HttpStatus.INTERNAL_SERVER_ERROR, ex.getMessage());
return new ResponseEntity<>(apiError, HttpStatus.INTERNAL_SERVER_ERROR);
}
}
When i testing my controller which throw ExportWordProfileException, i
always got 200 status code. But i have the correct error message :
<200,{"data":{"status":"NOT_FOUND","message":"Le profile avec l'id '8e6d45ca-d08f-48b8-8ed0-ea7ea067cf5e' n'existe pas."}} .....
How can i fix this problem ?
thanks for reading.
..............
edit
.............
May this problem come from my way to test with RestTemplate ?
I tested with Postman and i have the same result
I don't think there's anything wrong with your code. The probable cause of your issue is because another exception handler is interfering with your ExportWordProfileExceptionControllerAdvice.
This works for me. I think you need to add the #ResponseStatus(HttpStatus.FORBIDDEN) annotation to tell what status to send back for the exception:
#ControllerAdvice
public class GlobalControllerExceptionHandler {
private static Logger LOG = LoggerFactory.getLogger(GlobalControllerExceptionHandler.class);
#ResponseStatus(HttpStatus.FORBIDDEN) // 403
#ExceptionHandler(ForbiddenException.class)
public void handleForbidden() {
LOG.info("Forbidden handler invoked");
}
}
Hi i found what was the problem.
The project has another controller advice (ResponseBodyAdvice) made by a previous person. This controller changes the status code dependings of the response body object. So i return a generic rest error object in my controller designed by the previous person (i had no idea this error existing). This generic error was already taken in consideration by the ResponseBodyAdvice controller.
Using latest Spring Boot as of May 2018. I've created a 404 response like this.
#ResponseStatus(HttpStatus.NOT_FOUND)
public class NotFoundException extends RuntimeException {
private final int errorId;
public NotFoundException(String errorMsg) {
super("-1," + errorMsg);
this.errorId = -1;
}
public NotFoundException(int errorId, String errorMsg) {
super(errorId + "," + errorMsg);
this.errorId = errorId;
}
public int getErrorId() {
return errorId;
}
}
The annotation #ResponseStatus(HttpStatus.NOT_FOUND) makes my NotFoundException appear like a 404 reponse like this
{
"timestamp":1527751944754,
"status":404,
"error":"Not Found",
"exception":"com.myapp.exception.NotFoundException",
"message":"1000,Could not find data for owner: 1234","path":"/resource/owner/1234"
}
I hoped that property "getErrorId" would appear in the response automatically, like this
{
"timestamp":1527751944754,
"status":404,
"error":"Not Found",
"exception":"com.myapp.exception.NotFoundException",
"message":"Could not find data for owner: 1234","path":"/resource/owner/1234",
"errorId": 1000
}
Is the a simply way (like an annotiation to the getErrorId method) of having the property "errorId" in the response?
You use #ControllerAdvice and #ExceptionHanlder in Spring. that is exception controller. In fact, you will make custom exception controller and define exception.
This is sample code for you :
#ControllerAdvice("your.package")
public class CommonExceptionHandler {
#ExceptionHandler(value = NoHandlerFoundException.class)
#ResponseStatus(value = HttpStatus.NOT_FOUND)
public #ResponseBody ResponseEntity<?> setNotFoundException(Exception exception) throws JsonProcessingException {
ObjectMapper mapper = new ObjectMapper();
// this is sample map. you will make your custom model and you use exception parameter.
Map<String, String> map = new HashMap<String, String>();
map.put("timestamp", String.valueOf(new Date().getTime()));
map.put("status", HttpStatus.NOT_FOUND.toString());
map.put("error", "Not Found");
map.put("exception", exception.getMessage());
map.put("message", "Could not find data for owner: 1234");
map.put("path", "/resource/owner/1234");
map.put("errorId", "1000");
String json = mapper.writeValueAsString(map);
return ResponseEntity.status(HttpStatus.NOT_FOUND).body(json);
}
}
what ever Byeon0gam told everything is fine, here i am going to show another way means little bit of difference in maintaining code.
We know already ,
we can handle exceptions in spring-rest by 4 ways:
1. Using ResponseEntity Class.
2. Using #ResponseStatus Annotation.
3. Using #ExceptionHandler() Annotation.
4. Return Error Representation instead of default HTML error Page.
By using Those we can handle Exceptions at Method or Class level only.
But, if you want to handle Globally means throughout application , please follow below steps.
Handling Global Exception:
To Handle all Exceptions in our applications ,
First we need to create a class, after we need to use #ControllerAdvice Annotation on top of a class. In that class body , we can handle the exceptions raised in our application.
In that Class , we will create Exception handling methods , on top of every method we will use #ExceptionHandler() annotation for navigating Exceptions and for Handling .
If any exception raises in our application , based on #ExceptionHandler(“argument”) annotation argument the exception hadling method will be invoked and remaining handling code will be excuted.
#ControllerAdvice
public class SpringRestGlobalExceptionHandler {
#ExceptionHandler(Exception.class)
public ResponseEntity<?> exceptionHandler(HttpServletRequest req, Exception e)
{
JSONObject obj =new JSONObject();
obj.put("msgTxt","Unknown Server Error, Please Contact Admin." );
obj.put("reqUrl", req.getRequestURI());
obj.put("stackTrace", e.toString());
obj.put("isErrorFlag", true);
obj.put("httpStatusCode", HttpStatus.OK.value());
gstcDaoi.saveExceptionOrErrorLog(prepareTGstcExceptionOrErrorLogObject(obj));
e.printStackTrace();
return new ResponseEntity<>(obj, HttpStatus.OK);
}
I'm using RESTEasy 2.2.1.GA as my JAX-RS implementation to create a client to connect to a third party service provider. (Education.com's REST API if it matters)
To make sure I haven't missed an important implementation detail here are code samples:
Service Interface
#Path("/")
public interface SchoolSearch {
#GET
#Produces({MediaType.APPLICATION_XML})
Collection<SchoolType> getSchoolsByZipCode(#QueryParam("postalcode") int postalCode);
}
Calling Class
public class SimpleSchoolSearch {
public static final String SITE_URL = "http://api.education.com/service/service.php?f=schoolSearch&key=****&sn=sf&v=4";
SchoolSearch service = ProxyFactory.create(SchoolSearch.class, SITE_URL);
public Collection<SchoolType> getSchools() throws Exception {
Collection<SchoolType> schools = new ArrayList<SchoolType>();
Collection<SchoolType> response = service.getSchoolsByZipCode(35803);
schools.addAll(response);
return schools;
}
}
After setting up tests to make this call, I execute and see the following exception being thrown.
org.jboss.resteasy.plugins.providers.jaxb.JAXBUnmarshalException: Unable to find JAXBContext for media type: text/html;charset="UTF-8"
From reading the RESTEasy/JAX-RS documentation, as I understand it, when the response is returned to the client, prior to the unmarshaling of the data, a determination is made (Content Negotiation??) about which mechanism to use for unmarshalling. (I think we're talking about a MessageBodyReader here but I'm unsure.) From looking at the body of the response, I see that what is returned is properly formatted XML, but the content negotiation (via HTTP header content-type is indeed text/html;charset ="UTF-8") is not allowing the text to be parsed by JAXB.
I think that the implementation is behaving correctly, and it is the service that is in error, however, I don't control the service, but would still like to consume it.
So that being said:
Am I correct in my understanding of why the exception is thrown?
How do I work around it?
Is there a simple one line annotation that can force JAXB to unmarshal the data, or will I need to implement a custom MessageBodyReader? (If that is even the correct class to implement).
Thanks!
Follow Up:
I just wanted to post the few changes I made to Eiden's answer. I created a ClientExecutionInterceptor using his code and the information available at Resteasy ClientExecutionInterceptor documentation. My final class looks like
#Provider
#ClientInterceptor
public class SimpleInterceptor implements ClientExecutionInterceptor {
#Override
public ClientResponse execute(ClientExecutionContext ctx) throws Exception {
final ClientResponse response = ctx.proceed();
response.getHeaders().putSingle(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_XML);
return response;
}
}
The big difference is the addition of the #Provider and #ClientExecutionInterceptor annotations. This should insure that the interceptor is properly registered.
Also, just for completeness, I registered the Interceptor slightly differently for my tests. I used:
providerFactory.registerProvider(SimpleInterceptor.class);
I'm sure there are several solutions to this problem, but I can only think of one.
Try so set the content-type using a ClientExecutionInterceptor:
public class Interceptor implements ClientExecutionInterceptor {
#Override
public ClientResponse<?> execute(ClientExecutionContext ctx) throws Exception {
final ClientResponse<?> response = ctx.proceed();
response
.getHeaders()
.putSingle(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_XML);
return response;
}
}
public void getSchools() throws Exception {
ResteasyProviderFactory.getInstance()
.getClientExecutionInterceptorRegistry()
.register( new Interceptor() );
SchoolSearch service =
ProxyFactory.create(SchoolSearch.class, SITE_URL);
}
I dont know about any such annotation, others might do, but a workaround is to create a local proxy. Create a controller, that passes all parameters to education.com using a
java.Net.URL.get()
return the answer that you received, but modify the header. Then connect your client to the local proxy controller.