Exception handling in StreamingResponseBody - java

I'm trying to catch an exception thrown in my implementation of StreamingResponseBody, I can see the exception being thrown inside the class however the thrown exception isn't visible to the method body or my Controller Advice. So none of my handling seems to work, just interested to know which is the correct way to handle exceptions in this case.
#GetMapping(path = "/test", produces = "application/json")
public StreamingResponseBody test(#RequestParam(value = "var1") final String test)
throws IOException{
return new StreamingResponseBody() {
#Override
public void writeTo(final OutputStream outputStream) throws IOException{
try {
// Some operations..
} catch (final SomeCustomException e) {
throw new IOException(e);
}
}
};
}
I would expect my ControllerAdvice to return an ResponseEntity with a Http Status of 500.

The best way I discovered to handle errors/exceptions in the web environment is to create your custom exception with the disabled stack trace, and handle it with #ControllerAdvice.
import lombok.Getter;
import org.springframework.http.HttpStatus;
public class MyException extends RuntimeException {
#Getter private HttpStatus httpStatus;
public MyException(String message) {
this(message, HttpStatus.INTERNAL_SERVER_ERROR);
}
public MyException(String message, HttpStatus status) {
super(message, null, false, false);
this.httpStatus = status;
}
}
And then handle it in #ControllerAdvice like this:
#ExceptionHandler(MyException.class)
public ResponseEntity handleMyException(MyException exception) {
return ResponseEntity.status(exception.getHttpStatus()).body(
ErrorDTO.builder()
.message(exception.getMessage())
.description(exception.getHttpStatus().getReasonPhrase())
.build());
}
where ErrorDTO is just a simple DTO with two fields:
#Value
#Builder
public class ErrorDTO {
private final String message;
private final String description;
}

Related

How can we postHandle an exception in HandlerInterceptorAdapter?

I am currently trying to implement a customized error handler for spring boot and I have done it with the following:
public class ExceptionHandler extends HandlerInterceptorAdapter {
public static Logger log = LoggerFactory.getLogger(LoggingInterceptor.class);
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, #Nullable ModelAndView modelAndView) throws Exception {
try {
log.info("Service {} Calling {} on {} finished with status {}",request.getRemoteUser(), request.getMethod(), request.getRequestURI(), HttpStatus.valueOf(response.getStatus()));
} catch (Exception e) {
// Do nothing
} finally {
log.error("[Spring Boot Interceptor] {} returned with {}", handler, HttpStatus.valueOf(response.getStatus()));
}
}
Somehow this does not work, and the exception is still thrown to the client, is there some way to catch the exception thrown by the method and ignore it for example.
A good way to manage the exception is using #ControllerAdvice, using this you may handle any kind of exception and customize the response as required.
As said in the comment, you have to add InterceptorRegistry to register the interceptor.
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
#Configuration
public class WebConfig implements WebMvcConfigurer {
#Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new Interceptor()).addPathPatterns("/**");
}
}
The catch block inside postHandle will only be executed if an exception occurred inside the try-catch block as below,
#Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, #Nullable ModelAndView modelAndView) throws Exception {
try {
int error = 1/0;
} catch (Exception e) {
log.info("Exception will be handled inside catch block");
}
}
Now let's explore the #ControllerAdvice to manage the exception within the application. These two APIs will generate the exceptions and we will manage the exceptions using #ExceptionHandler
#GetMapping("/exception/404")
public void generateResourceNotFound() {
throw new ResourceNotFoundException("resource not found");
}
#GetMapping("/exception/403")
public void generateAccessDenied() {
throw new AccessDeniedException("access denied");
}
GlobalExceptionHandler.java
import com.learning.annotations.controller.ResourceNotFoundException;
import com.learning.annotations.dto.ErrorResponseDTO;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.context.request.WebRequest;
import org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler;
#ControllerAdvice
public class GlobalExceptionHandler extends ResponseEntityExceptionHandler {
public Logger log = LoggerFactory.getLogger(Interceptor.class);
#ExceptionHandler(AccessDeniedException.class)
public ResponseEntity<ErrorResponseDTO> handleAccessDeniedException(AccessDeniedException ex, WebRequest request) {
ErrorResponseDTO response = new ErrorResponseDTO();
response.setError(ex.getMessage());
response.setMessage("You don't have authority to access the resource");
return new ResponseEntity<>(response, HttpStatus.FORBIDDEN);
}
#ExceptionHandler(ResourceNotFoundException.class)
public ResponseEntity<ErrorResponseDTO> handleResourceNotFoundException(ResourceNotFoundException ex, WebRequest request) {
ErrorResponseDTO response = new ErrorResponseDTO();
response.setError(ex.getMessage());
response.setMessage("Resource might be moved temporary or not available");
return new ResponseEntity<>(response, HttpStatus.NOT_FOUND);
}
}
To customize the response we can create error response DTO as follows,
import lombok.Data;
#Data
public class ErrorResponseDTO {
private String message;
private String error;
}

ExceptionHandler, got java.lang.IllegalStateException: Could not resolve method parameter at index 0?

I've a problem with the ExceptionHandler of Java Spring. I have a my Exception called EntityNotFoundException, and I want call ExceptionHandler method from REST Controller when exception is thrown.
This is my REST Controller method code:
#ExceptionHandler(Exception.class)
public ResponseEntity insertTicket(#Valid #RequestBody Ticket ticket, #AuthenticationPrincipal Principal principal) throws EntityNotFoundException {
ticket.setCreationTimestamp(Instant.now());
ticket.setSource(TicketSource.CLIENT);
ticket.setCurrentTicketStatus(TicketStatus.VALIDATION);
User customer = userController.findUserByUsername(principal.getName());
ticket.setCustomer(customer);
try {
ticket.setAttachments(savedFiles(
ticket.getAttachments(),
ticket.getCustomer().getUsername()
));
} catch (FileUploadException e) {
return CommonResponseEntity.NotFoundResponseEntity("ENTITY_NOT_FOUND");
}
ticketController.insertTicket(ticket);
mailSenderController.sendMail(customer.getEmail(), "TICKET_OPENED");
return CommonResponseEntity.CreatedResponseEntity("CREATED");
}
This is my Exception Handler code:
#EnableWebMvc
#ControllerAdvice
#RestControllerAdvice
public class InterceptedResponseEntityExceptionHandler extends
ResponseEntityExceptionHandler {
#Override
protected ResponseEntity<Object> handleMethodArgumentNotValid(MethodArgumentNotValidException ex, HttpHeaders headers, HttpStatus status, WebRequest request) {
BindingResult bindingResult = ex.getBindingResult();
List<MethodArgumentFieldError> methodArgumentFieldErrors = bindingResult
.getFieldErrors()
.stream()
.map(fieldError -> new MethodArgumentFieldError(fieldError.getField(), fieldError.getCode(), fieldError.getRejectedValue()))
.collect(Collectors.toList());
List<MethodArgumentGlobalError> methodArgumentGlobalErrors = bindingResult
.getGlobalErrors()
.stream()
.map(globalError -> new MethodArgumentGlobalError(globalError.getCode()))
.collect(Collectors.toList());
MethodArgumentError methodArgumentError = new MethodArgumentError(methodArgumentFieldErrors, methodArgumentGlobalErrors);
return new ResponseEntity<>(methodArgumentError, HttpStatus.UNPROCESSABLE_ENTITY);
}
#Override
protected ResponseEntity<Object> handleMissingServletRequestParameter(MissingServletRequestParameterException ex, HttpHeaders headers, HttpStatus status, WebRequest request) {
MissingParameterError missingParameterError = new MissingParameterError(ex.getParameterName(), ex.getMessage());
return new ResponseEntity<>(missingParameterError, HttpStatus.UNPROCESSABLE_ENTITY);
}
#ExceptionHandler(Exception.class)
public ResponseEntity<Object> handleNotFound(Exception ex, WebRequest request) {
System.out.println("inside!");
if( ex instanceof DataIntegrityViolationException){
System.out.println("Data integrity violation");
String constraintViolationErrors = ex.getMessage();
String msgErr = (constraintViolationErrors.substring(constraintViolationErrors.indexOf("=") + 1));
return new ResponseEntity<>(msgErr, HttpStatus.BAD_REQUEST);
}
if(ex instanceof UsernameNotFoundException) {
String msgErr = ex.getMessage();
return new ResponseEntity<>(msgErr, HttpStatus.BAD_REQUEST);
}
if (ex instanceof NotFoundEntityException || ex instanceof EntityNotFoundException || ex instanceof NoSuchElementException){
//return CommonResponseEntity.NotFoundResponseEntity(ex.getMessage());
System.out.println("inside the handler!");
return new ResponseEntity<>(ex.getMessage(),HttpStatus.NOT_FOUND);
}
if(ex instanceof UpdateException){
return new ResponseEntity<>(HttpStatus.CONFLICT);
}
return null;
}
#Data
#AllArgsConstructor
public class MethodArgumentError {
private List<MethodArgumentFieldError> fieldErrors;
private List<MethodArgumentGlobalError> globalErrors;
}
#Data
#AllArgsConstructor
public class MethodArgumentFieldError {
private String field;
private String code;
private Object rejectedValue;
}
#Data
#AllArgsConstructor
public class MethodArgumentGlobalError {
private String code;
}
#Data
#AllArgsConstructor
public class MissingParameterError {
private String parameterName;
private String message;
}
#Data
#AllArgsConstructor
public class ConstraintViolationError {
private String invalidValue;
private String message;
}
}
I don't know why, when I get a DataIntegrityViolationException the ExceptionHandler is called, instead when I get an EntitynotFoundException I get this message:
java.lang.IllegalStateException: Could not resolve method parameter at index 0 in public org.springframework.http.ResponseEntity com.isssr.ticketing_system.rest.TicketRest.insertTicket(com.isssr.ticketing_system.entity.Ticket,java.security.Principal) throws com.isssr.ticketing_system.exception.EntityNotFoundException: No suitable resolver for argument 0 of type 'com.isssr.ticketing_system.entity.Ticket'
What's the problem??
I saw other strage things; I get this message:
Failed to invoke #ExceptionHandler method: public org.springframework.http.ResponseEntity com.isssr.ticketing_system.rest.TicketRest.insertTicket(com.isssr.ticketing_system.entity.Ticket,java.security.Principal) throws com.isssr.ticketing_system.exception.EntityNotFoundException
So it seems that Spring is trying to invoke another method instead of method of my ExceptionHandler.
How is possibile this?

Rest - How to send Http Error Response Without Stack Trace

I have an rest web service. If any exception thrown, web service return http 500 error. But I don't want to send this error response with exception stack trace. I just want to send with error code and error message. I didn't achieve this. How can I do this?
I already tried #ControllerAdvice and #ExceptionHandler annotations but I couldn't. When I used #ResponseStatus annotation, always send static "reason" value. How can I set this value? Thank for your help.
public class SendMessageController{
private Logger log = LogManager.getLogger(getClass());
#Autowired
private QueueService queueService;
#RequestMapping(value="/message/check", method = RequestMethod.POST, headers={ "content-type=application/json"})
public #ResponseBody
ApiResponse sendMessage(#RequestBody String requestMessage) throws Exception {
try {
return new ApiResponse(queueService.processRequestForJSONString(requestMessage);
} catch (Exception e) {
throw new GenericException(HttpStatus.INTERNAL_SERVER_ERROR, e.getMessage());
//throw e;
}
}
#ResponseStatus(value=HttpStatus.INTERNAL_SERVER_ERROR, reason="Exception Message")
public class GenericException extends Exception {
public HttpStatus httpCode;
public String errorMessage;
public GenericException(HttpStatus httpCode, String errorMessage){
this.httpCode = httpCode;
this.errorMessage = errorMessage;
//I can't set "reason"
}
}
}
There are many possible solutions and I'm pretty sure an ErrorHandler is a much better way to go.
#GetMapping(value="/{empId}", produces=MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity<EmployeeInfoItem> getEmployeeInfo(#PathVariable("empId") Integer empId) {
try {
...
} catch (Exception e) {
logger.error( e.getMessage() );
return ResponseEntity.status(HttpStatus.FAILED_DEPENDENCY).build();
}
}

Spring Boot - handle exception wrapped with BindException

I am looking for a way to handle custom exception thrown during binding of request parameter to DTO field.
I have a cantroller in Spring Boot application as follows
#GetMapping("/some/url")
public OutputDTO filterEntities(InputDTO inputDTO) {
return service.getOutput(inputDTO);
}
input DTO has few fields, one of which is of enum type
public class InputDTO {
private EnumClass enumField;
private String otherField;
/**
* more fields
*/
}
user will hit the URL in ths way
localhost:8081/some/url?enumField=wrongValue&otherField=anyValue
Now if user sends wrong value for enumField, I would like to throw my CustomException with particular message. Process of enum instance creation and throwing of exception is implemented in binder
#InitBinder
public void initEnumClassBinder(final WebDataBinder webdataBinder) {
webdataBinder.registerCustomEditor(
EnumClass.class,
new PropertyEditorSupport() {
#Override
public void setAsText(final String text) throws IllegalArgumentException {
try {
setValue(EnumClass.valueOf(text.toUpperCase()));
} catch (Exception exception) {
throw new CustomException("Exception while deserializing EnumClass from " + text, exception);
}
}
}
);
}
Problem is that when exception is thrown it is impossible to handle it with
#ExceptionHandler(CustomException.class)
public String handleException(CustomException exception) {
// log exception
return exception.getMessage();
}
Spring wraps initial exception with BindException. That instance contains my initial error message, but concatenated with other text which is redundant for me. I don't think that parsing and substringing that message is good...
Am I missing something? What is the proper way to get message from initial
CustomException here?
You will not be able to handle exceptions thrown before entering your controller method by using #ExceptionHandler annotated methods. Spring handles these exceptions before entering the controller, by registering DefaultHandlerExceptionResolver extends AbstractHandlerExceptionResolver handler.
This is the case of BindingException, thrown when Spring cannot bind request parameters to match your InputDTO object.
What you can do is to register your own handler (create a Component implementing HandlerExceptionResolver and Ordered interfaces), give it the highest priority in handling errors and play with exceptions as needed.
You have also to pay attention to BindException as it wrappes your custom exception, CustomException.class
import java.io.IOException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.slf4j.Logger; import org.slf4j.LoggerFactory;
import org.springframework.core.Ordered;
import org.springframework.stereotype.Component;
import org.springframework.validation.BindException;
import org.springframework.validation.ObjectError;
import org.springframework.web.servlet.HandlerExceptionResolver;
import org.springframework.web.servlet.ModelAndView;
import yourpackage.CustomException;
#Component()
public class BindingExceptionResolver implements HandlerExceptionResolver, Ordered {
private static final Logger logger = LoggerFactory.getLogger(BindingExceptionResolver.class);
public BindingExceptionResolver() {
}
private ModelAndView handleException(ObjectError objectError, HttpServletResponse response){
if (objectError == null) return null;
try {
if(objectError.contains(CustomException.class)) {
CustomException ex = objectError.unwrap(CustomException.class);
logger.error(ex.getMessage(), ex);
return handleCustomException(ex, response);
}
} catch (Exception e) {
logger.error(e.getMessage(), e);
}
return null;
}
protected ModelAndView handleCustomException(CustomException ex, HttpServletResponse response) throws IOException {
response.sendError(HttpServletResponse.SC_BAD_REQUEST, ex.getMessage());
return new ModelAndView();
}
#Override
public ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {
try {
if (ex instanceof org.springframework.validation.BindException) {
BindException be = (BindException) ex;
logger.debug("Binding exception in {} :: ({}) :: ({})=({})", be.getObjectName(), be.getBindingResult().getTarget().getClass(), be.getFieldError().getField(), be.getFieldError().getRejectedValue());
return be.getAllErrors().stream()
.filter(o->o.contains(Exception.class))
.map(o ->handleException(o, response))
.filter(mv ->mv !=null)
.findFirst().orElse(null);
}
} catch (Exception handlerException) {
logger.error("Could not handle exception", handlerException);
}
return null;
}
#Override
public int getOrder() {
return Integer.MIN_VALUE;
}
}
Hope it helps

Spring RestTemplate, intercepting response before parsing to Json

I have a REST api that responds with some additional non JSON data in the body content. This breaks the use of RestTemplate and jackson. Can I intercept the http response body prior to the parsing?
I am using RestTemplate.getForObject.
I've taken a look at the RestTemplate and couldn't see an appropriate method.
You can try to implement ClientHttpRequestInterceptor and assign it to restTemplate. Implement intercept method:
#Override
public ClientHttpResponse intercept(HttpRequest httpRequest, byte[] bytes,
ClientHttpRequestExecution clientHttpRequestExecution) throws IOException {
ClientHttpResponse response=clientHttpRequestExecution.execute(httpRequest, bytes);
//...do magic with response body from getBody method
return response;
}
You might have to extend AbstractClientHttpResponse with your own implementation to do that.
Another option could be to treat the response from the REST API as String, then format the string as needed and explicitly map it to object using ObjectMapper.
Then in your restTemplate you would have:
String result = restTemplate.getForObject(url, String.class, host);
//..trim the extra stuff
MyClass object=objectMapper.readValue(result, MyClass.class);
Yet another option would be to implement your own HttpMessageConverter which extends AbstractJackson2HttpMessageConverter and register it with restTemplate. In my opinion that would be the cleaneast from the Spring point of view
Another way would be to unwrap the response by implementing a ClientHttpRequestInterceptor along with a ClientHttpResponse.
#Component
public class MyInterceptor implements ClientHttpRequestInterceptor {
#Autowired
Function<ClientHttpResponse, MyResponseWrapper> responseWrapperBeanFactory;
#Autowired
MyRequestAdvice requestAdvice;
#Override
public ClientHttpResponse intercept(HttpRequest httpRequest, byte[] bytes, ClientHttpRequestExecution clientHttpRequestExecution) throws IOException {
byte[] wrappedBody = requestAdvice.wrapRequest(bytes);
ClientHttpResponse res = clientHttpRequestExecution.execute(httpRequest, wrappedBody);
return responseWrapperBeanFactory.apply(res);
}
}
Here's the bean config for the MyResponseWrapper:
#Bean
Function<ClientHttpResponse, MyResponseWrapper> responseWrapperBeanFactory() {
return this::getMyResponseWrapper;
}
#Bean
#Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
public MyResponseWrapper getMyResponseWrapper(ClientHttpResponse originalResponse) {
return new MyResponseWrapper(originalResponse);
}
#Bean
public RestTemplate restTemplate(#Autowired MyInterceptor interceptor) {
RestTemplate t = new RestTemplate();
t.setInterceptors(Arrays.asList(interceptor));
// other setup code
return t;
}
And here's the ClientHttpResponse implementation:
public class MyResponseWrapper implements ClientHttpResponse {
private byte[] filteredContent;
private ByteArrayInputStream responseInputStream;
private ClientHttpResponse originalResponse;
public MyResponseWrapper(ClientHttpResponse originalResponse) {
this.originalResponse = originalResponse;
try {
filteredContent = MyContentUnwrapper.unwrapResponse(originalResponse.getBody().readAllBytes());
} catch (Exception e) {
throw new RuntimeException("There was a problem reading/decoding the response coming from the service ", e);
}
}
#Override
public HttpStatus getStatusCode() throws IOException {
return originalResponse.getStatusCode();
}
#Override
public int getRawStatusCode() throws IOException {
return originalResponse.getRawStatusCode();
}
#Override
public String getStatusText() throws IOException {
return originalResponse.getStatusText();
}
#Override
public void close() {
if (responseInputStream != null) {
try {
responseInputStream.close();
} catch (IOException e) { /* so long */}
}
}
#Override
public InputStream getBody() throws IOException {
if (responseInputStream == null) {
responseInputStream = new ByteArrayInputStream(filteredContent);
}
return responseInputStream;
}
#Override
public HttpHeaders getHeaders() {
return originalResponse.getHeaders();
}
}
From your Controller you can try to return a ResponseEntity and manipulate the entity object manually
If you don't need these extra properties you may add:
#JsonIgnoreProperties(ignoreUnknown = true)
to your mapping class.
From docs:
Property that defines whether it is ok to just ignore any unrecognized
properties during deserialization. If true, all properties that are
unrecognized -- that is, there are no setters or creators that accept them
-- are ignored without warnings (although handlers for unknown properties,
if any, will still be called) without exception.
Does not have any effect on serialization.

Categories