Let's say I have the following runtime exception:
#ResponseStatus(HttpStatus.EXPECTATION_FAILED)
public class ExpectationsFailedException extends RuntimeException {
public ExpectationsFailedException(String message) {
super(message);
}
}
My question is if it is ok to throw the previous HTTP exception in my service layer or should I throw it from my controller:
#Service
public class UserService {
#Autowired
...
public void addUser(final String email, final String username, final String password){
if(parameters_are_not_valid){
throw new ExpectationsFailedException("Invalid input");
}
}
}
The controller exception throwing solution would be the following:
#Service
public class UserService {
#Autowired
...
public void addUser(final String email, final String username, final String password) throws InvalidInputParameters {
if(parameters_are_not_valid){
throw new InvalidInputParameters("Invalid input");
}
}
}
and in my controller
#RestController
public class XController{
#Autowired
private UserService userService;
#RequestMapping(value = "/addUser", method = RequestMethod.POST)
public void addUser(#Valid #RequestBody SignUpForm form, BindingResult bindingResult){
if(bindingResult.hasErrors()){
throw new ExpectationsFailedException("Input parameters conditions were not fulfilled");
}
try {
userService.addUser(...);
}
catch(InvalidInputParameters ex){
throw new ExpectationsFailedException("Invalid service input parameters");
}
}
}
Which of those solutions is preferred? Why? I have a feeling that I should not throw HTTP exceptions in my services because I may use that services in other contexts which may not be related to HTTP.
I would go with the second one.
What do you think?
I agree with your last statement. Your service layer should be independent of HTTP or frontent frameworks (#ResponseStatus is Spring MVC annotation and therefore it's not the best practice to use it in your service layer).
However you don't have to throw one exception in service layer, catch it in controller and rethrow another exception annotated with #ResponseStatus. Just add exception handler for the service exception and return appropriate response status from it. You have plenty of options, for instance #ExceptionHandler:
#ResponseStatus(HttpStatus.EXPECTATION_FAILED)
#ExceptionHandler(InvalidInputParameters.class)
public void handle() {
// Do nothing, just return the status
}
You can put this code to #ControllerAdvice annotated class to enable it for all controllers or just in you controller if it's not needed elsewhere.
Related
I am using ControllerAdvice to handle exceptions in my spring boot application.
#Order(Ordered.HIGHEST_PRECEDENCE)
#ControllerAdvice
public class ErrorApiHandler extends ResponseEntityExceptionHandler {
final
ResponsesHelper rh;
public ErrorApiHandler(ResponsesHelper rh) {
this.rh = rh;
}
#ExceptionHandler(UsernameNotFoundException.class)
public ResponseEntity<Object> handleUsernameNotFoundException(UsernameNotFoundException ex) {
log.error(ExceptionUtils.getStackTrace(ex));
var error = buildError(ex);
return rh.buildResponse(error, HttpStatus.NOT_FOUND);
}
...
}
It works fine for exceptions thrown within my controllers.
However, with exceptions thrown, for example within a service the ControllerAdvice is not executed.
#Service
public class CustomUserDetailsService implements UserDetailsService {
final
UserRepository userRepository;
public CustomUserDetailsService(UserRepository userRepository) {
this.userRepository = userRepository;
}
#Override
#Transactional
public User loadUserByUsername(String email)
throws UsernameNotFoundException {
log.debug(String.format("Loading user %s", email));
User user = userRepository.findByEmail(email)
.orElseThrow(() -> {
log.debug(String.format("User %s not found", email));
return new UsernameNotFoundException("User not found : " + email); // <- This exception is not handled.
});
log.debug(String.format("User %s loaded", user));
return user;
}
How can I handle all exceptions thrown within my application?
Thanks in advance.
I found this in ResponseEntityExceptionHandler docs:
A convenient base class for #ControllerAdvice classes that wish to provide centralized exception handling across all #RequestMapping methods through #ExceptionHandler methods.
It seems that a custom exception handler that extends that class will only handle exceptions in the controller layer.
I found this tutorial - a solution that uses HandlerExceptionResolver sounds like the one you are looking for.
#ControllerAdvice is meant to handle exceptions that propagate through controller methods (that are thrown from within controller method calls - including bubbling exceptions). So whenever you directly throw exceptions in your controller method or something that controller method is calling throws an exception, an attempt to handle it through advice will be made.
If you want your exceptions to be handled (somehow) outside of web context with a similar manner, your will have to write your own aspect that will literally wrap everything try-catch and will let you handle the exception.
In my case my "CustomUserDetailsService" is called inside a filter:
public class JwtAuthenticationFilter extends OncePerRequestFilter {
#Autowired
private JwtTokenProvider tokenProvider;
#Autowired
private CustomUserDetailsService customUserDetailsService;
#Override
protected void doFilterInternal(
HttpServletRequest request,
HttpServletResponse response,
FilterChain filterChain
) throws ServletException, IOException {
try {
String jwt = getJwtFromRequest(request);
if (StringUtils.hasText(jwt) && tokenProvider.validateToken(jwt)) {
UUID userId = tokenProvider.getUserIdFromJWT(jwt);
UserDetails userDetails = customUserDetailsService.loadUserById(userId);
...
I handled it by catching the exceptions there:
catch (UsernameNotFoundException ex){
log.error(ExceptionUtils.getStackTrace(ex));
var apiError = ErrorApiHandler.buildError(
new ResourceAlreadyTakenException(ex.getMessage())
);
response.addHeader("Content-Type", "application/json");
response.setStatus(HttpStatus.BAD_REQUEST.value());
PrintWriter writer = response.getWriter();
writer.write(convertObjectToJson(apiError));
writer.flush();
return;
}
Ideally, handle all exceptions with the controller advice, but this way it works
I have a simple Spring Boot 2.1 application with a Spring Interceptor and #RestControllerAdvice.
My requirement is to have the Spring Interceptor be called for all situations, including when an exception occurs.
For custom exceptions, the Interceptor handler methods do get called, e.g. preHandle() and afterCompletion(). However, for exceptions handled by ResponseEntityExceptionHandler, the Spring Interceptor does not get called (I need ResponseEntityExceptionHandler's methods to create a custom ResponseBody to send back, however, I also need to trigger Interceptor's afterCompletion() for auditing purposes).
For instance, if a REST request is made with PATCH HTTP method, it only executes PersonControllerExceptionHandler.handleHttpRequestMethodNotSupported() and no PersonInterceptor is invoked.
Exception Handler:
#RestControllerAdvice
#Order(Ordered.HIGHEST_PRECEDENCE)
public class PersonControllerExceptionHandler extends ResponseEntityExceptionHandler {
private final static Logger LOGGER = LoggerFactory.getLogger(PersonControllerExceptionHandler.class);
#ExceptionHandler(value = {PersonException.class })
public ResponseEntity<Object> handlePersonException(PersonException exception) {
LOGGER.info("Person exception occurred");
return new ResponseEntity<Object>(new Person("Bad Age", -1),
HttpStatus.BAD_REQUEST);
}
#ExceptionHandler(value = {Exception.class })
public ResponseEntity<Object> handleException(Exception exception) {
LOGGER.info("Exception occurred");
return new ResponseEntity<Object>(new Person("Unknown Age", -100),
HttpStatus.INTERNAL_SERVER_ERROR);
}
#Override
public ResponseEntity<Object> handleHttpRequestMethodNotSupported(HttpRequestMethodNotSupportedException ex,
HttpHeaders headers,
HttpStatus status,
WebRequest request) {
LOGGER.info("handleHttpRequestMethodNotSupported()...");
return new ResponseEntity<>(new Person("Argh!", 900), HttpStatus.METHOD_NOT_ALLOWED);
}
}
The Interceptor:
#Order(Ordered.HIGHEST_PRECEDENCE)
public class PersonInterceptor extends HandlerInterceptorAdapter {
private static final Logger LOGGER = LoggerFactory.getLogger(PersonInterceptor.class);
#Override
public boolean preHandle(HttpServletRequest request,
HttpServletResponse response, Object handler) throws Exception {
LOGGER.info("PersonInterceptor#preHandler()...");
return true;
}
#Override
public void postHandle(HttpServletRequest request,
HttpServletResponse response, Object handler,
ModelAndView modelAndView) throws Exception {
LOGGER.info("PersonInterceptor#postHandler()...");
}
#Override
public void afterCompletion(HttpServletRequest request,
HttpServletResponse response, Object handler, Exception ex)
throws Exception {
LOGGER.info("PersonInterceptor#afterCompletion()...");
if (ex != null) {
LOGGER.error("afterCompletion(): An exception occurred", ex);
}
}
}
Registering the Interceptor:
#Configuration
public class AppConfig implements WebMvcConfigurer {
#Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new PersonInterceptor()).addPathPatterns("/person/*");
}
}
Controller:
#RestController
#RequestMapping("/")
public class PersonController {
private final static Logger LOGGER = LoggerFactory.getLogger(PersonController.class);
#Autowired
private PersonService personService;
#GetMapping(path = "/person/{age}", produces = MediaType.APPLICATION_JSON_VALUE)
public Person getPerson(#PathVariable("age") Integer age) throws PersonException {
LOGGER.info("Age: {}", age);
return personService.getPerson(age);
}
}
Initially I thought it has something to do with #Ordered but trying various scenarios where I give PersonInterceptor a higher precedence than #RestControllerAdvice yields the same undesirable outcome (or vice versa).
After digging into Spring framework, it seems like if a handler is not found, an exception is thrown back to DispacherServlet#doDispatch() which goes into a catch block, and therefore, it skips interceptor mapping process, including the afterCompletion() (I'm using Spring 5.1. as an example to trace the execution path):
DispacherServlet#doDispatch() is called and attempts is made to get the HandlerExecutionChain
I can see there are several HandlerMapping's; the one that fails is RequestMappingHandlerMapping
In AbstractHandlerMapping#getHandler(), it tries to get the handler via AbstractHandlerMethodMapping#getHandlerInternal()
Eventually, AbstractHandlerMethodMapping#lookupHandlerMethod() is called which fails to find a matching pattern due to the fact that there is no PATCH getPerson(), but rather GET getPerson()
At this point, RequestMappingInfoHandlerMapping#handleNoMatch() throws HttpRequestMethodNotSupportedException
This exception bubbles up to DispatcherServlet#doDispatch() exception clause which then processes by the exception resolver that it finds in
DispatcherServlet#processHandlerException() (of course, this finds an exception resolver and doesn't throw an exception which might trigger DispatcherServlet#triggerAfterCompletion() when an exception is caught in DispacherServlet#doDispatch() exception clause
Is there something I am missing to trigger the interceptor's afterCompletion() in cases when there is no handler match?
I am getting NotFoundException while trying to implement custom exception handling in spring-boot rest application.
The code was working fine when I was using MVC (using #ControllerAdvice) annotations but not sure when I am sending a data which is violating the constraint mentioned in entity(pojo class) it is throwing only NotFoundException (for all validation failure) but not the MethodViolationException or ConstraintViolationException
I am not able to send the message for that particular violation.
Not sure where I am making this mistake. Please help
Code:
#POST
#Path("/customers/add")
public Response addCustomer(#Valid customer cust)
{
// Rest of the code
}
POJO:
#Entity
#Table(name="cust")
public class Customer
{
#NotNull
#Size(min=1,max=50,message ="invalid name")
String name;
}
Exception Handler:
#Provider
public class CustomHandler implements ExceptionMapper<Exception>
{
public Response toResponse(Exception ex)
{
if(ex instanceOf ConstraintViolationException)
{
Do something
}
}
**UPDATE 1
If I enable the send_error_in_response i am getting the message for this but not sure why my custom exception handler is not able to catch this exception and only throwing NotFoundException
Try Handling Exception Using:
#ControllerAdvice
#RestController
public class CustomizedResponseEntityExceptionHandler extends ResponseEntityExceptionHandler {
#ExceptionHandler(StudentNotFoundException)
public final ResponseEntity<ErrorDetails> handleUserNotFoundException(StudentNotFoundException ex, WebRequest request) {
ErrorDetails errorDetails = new ErrorDetails(new Date(), ex.getMessage(),
request.getDescription(false));
return new ResponseEntity<>(errorDetails, HttpStatus.NOT_FOUND);
}
For more information you might want to refer http://www.springboottutorial.com/spring-boot-validation-for-rest-services
I'm trying to handle exceptions in a spring boot application that has SOAP endpoints and Rest controllers.
Catching exceptions that occur in the rest controller is quite straightforward, I just set a class with #controlleradvice that has #exceptionhandler methods and all exceptions get caught. However, this controlleradvice doesn't seem to catch exceptions that occur in the SOAP endpoints. Is there a way to catch the exceptions that are thrown in the endpoints on a #controlleradvice class? If not, is there some other way to centralize exception handling throughout the entire application, independently from where the exceptions are thrown?
Thank you so much in advance.
We can create customized exception:
#ResponseStatus(HttpStatus.NOT_FOUND) public class StudentNotFoundException extends
RuntimeException {}
Exception handler in Spring:
#ControllerAdvice
#RestController
public class CustomizedResponseEntityExceptionHandler extends
ResponseEntityExceptionHandler {
#ExceptionHandler(StudentNotFoundException.class)
public final ResponseEntity<ErrorDetails>
handleUserNotFoundException(StudentNotFoundException ex, WebRequest request) {
ErrorDetails errorDetails = new ErrorDetails(new Date(), ex.getMessage(),
request.getDescription(false));
return new ResponseEntity<>(errorDetails, HttpStatus.NOT_FOUND);
}
public class ErrorDetails {
private Date timestamp;
private String message;
private String details;
public ErrorDetails(Date timestamp, String message, String details) {
super();
this.timestamp = timestamp;
this.message = message;
this.details = details;
}
I would like to manage Exception thrown by simple Controller or RestController in two ways:
1) html redirection
2) Json error
I tested the code below :
#ControllerAdvice(annotations = Controller.class)
public class ExceptionHandlerController
{
#ExceptionHandler(Exception.class)
public ModelAndView handleException(HttpServletRequest _req, Exception _ex)
{
K_LOGGER.info("test");
return new ModelAndView();
}
}
#ControllerAdvice(annotations = RestController.class)
public class ExceptionHandlerRestController
{
#ExceptionHandler(Exception.class)
public ResponseEntity<String> handleException(HttpServletRequest _req, Exception _ex)
{
return new ResponseEntity<>("test", HttpStatus.INTERNAL_SERVER_ERROR);
}
}
#RestController
public class GreetingController
{
#RequestMapping("/greetingexception")
public Greeting greetingException(#RequestParam(value = "name", defaultValue = "World") String name)
throws Exception
{
throw new Exception();
}
}
It doesn't work properly, I always pass by ExceptionHandlerController but not by ExceptionHandlerRestController.
I think it's because #RestController inherit of #Controller.
Do you have a other solution?
Try to add #Order(Ordered.HIGHEST_PRECEDENCE) annotation to rest exception handler. It may helps you.
eg04lt3r answer is correct, just though that more details might be useful for someone.
In case when you have global #ControllerAdvice and want to handle some exception in a different way in one of your Controllers you need to set #Order(Ordered.HIGHEST_PRECEDENCE) on the #ControllerAdvice which should have higher priority.
For example:
#ControllerAdvice
public class GeneralExceptionHandler {
#ExceptionHandler(Exception.class)
protected ResponseEntity<Error> handleException(Exception ex) {
...
}
}
#ControllerAdvice(assignableTypes = MyController.class)
#Order(Ordered.HIGHEST_PRECEDENCE)
public class MyExceptionHandler {
#ExceptionHandler(Exception.class)
protected ResponseEntity<Error> handleException(Exception ex) {
...
}
}
#Order is needed because on startup one of the handlers will register with higher order automatically, anyway and your exception handling will become unpredictable. For example I recently saw a case when if you start an app using bootRun gradle task MyExceptionHandler was primary, but when started as jar GeneralExceptionHandler was primary.