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;
}
Related
Hi I am new to spring boot.when I try submit the request from the postman it is returning org.springframework.transaction.TransactionSystemException with HttpStatus code : 500 if invalid it throwing the javax.validation.ConstraintViolationException in the server.
Can any one share the best solution to handle these exceptions?
I tried in controller with the below code:
#ExceptionHandler(ConstraintViolationException.class)
#ResponseStatus(HttpStatus.BAD_REQUEST)
ResponseEntity<String> handleConstraintViolationException(ConstraintViolationException e) {
return new ResponseEntity<>("not valid due to validation error: " + e.getMessage(), HttpStatus.BAD_REQUEST);
}
But I want the response to be send in Json format with customized error message How can I achieve it ?
And also wanted to avoid the exception handling code in the controller.Is there any better way?
Each of such exceptions must be handled separately like below
#RestControllerAdvice
public class RestExceptionHandler extends ResponseEntityExceptionHandler {
#ExceptionHandler ({ConstraintViolationException.class})
#ResponseStatus(HttpStatus.BAD_REQUEST)
protected ResponseEntity<Object> handleConstraintViolationException(
ConstraintViolationException e) {
return new ResponseEntity<>(new JsonErrorResponse(e.getMessage()), HttpStatus.BAD_REQUEST);
}
}
If you want to create your own response object then u can use the below way in the RestExceptionHandler class.
#Override
protected ResponseEntity<Object> handleHttpMessageNotReadable(
HttpMessageNotReadableException e, HttpHeaders headers, HttpStatus status, WebRequest request) {
return new ResponseEntity<>(new CustomErrorResponse(e.getMessage()), HttpStatus.BAD_REQUEST);
}
private class CustomErrorResponse {
String message;
public CustomErrorResponse() {
}
public CustomErrorResponse(String message) {
super();
this.message = message;
}
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
}
You can use #ControllerAdvice. It's a special component to handle error across the hole application. Here I show you a example:
#ControllerAdvice
public class CustomErrorHandler{
#ResponseStatus(HttpStatus.BAD_REQUEST)
#ExceptionHandler(ConstraintViolationException.class)
public ResponseEntity<String> handleContraintViolationException() {
// Your custom response
}
}
The pro is that you have a different class that handle the exceptions in all the application.
500 is a server problem. It means your server encounter a problem while executing the code. Specifically it's ConstraintViolationException which means you have a constraint when inserting your data in the database.
Example :
Imagine we have an entity that has a unique field
#Entity
user {
UUID id;
#column
String name;
}
and when creating the entity you have made a uniquness contrainte in database :
ALTER TABLE USER
ADD CONSTRAINT UC_name UNIQUE (name);
Here if you try to insert two user with the same name you would receive a ConstraintViolationException
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 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 having a peculiar situation with my #ControllerAdvice annotated ExceptionHandler in Spring Boot 1.5.3. It catches any exceptions default Exceptions, but if I throw a custom exception it does not fire.
The ExceptionHandler:
#ControllerAdvice
public class ResponseEntityExceptionHandler {
#ExceptionHandler({ HttpMessageNotReadableException.class })
protected ResponseEntity<ErrorModel> handleInvalidJson(RuntimeException e, WebRequest request) {
return new ResponseEntity<ErrorModel>(new ErrorModel().message("Could not parse JSON."), HttpStatus.BAD_REQUEST);
}
#ExceptionHandler({ NumberFormatException.class })
protected ResponseEntity<ErrorModel> handleInvalidRequest(RuntimeException e, WebRequest request) {
return new ResponseEntity<ErrorModel>(new ErrorModel().message("Invalid request parameter."), HttpStatus.BAD_REQUEST);
}
#ExceptionHandler({ CannotCreateTransactionException.class })
protected ResponseEntity<ErrorModel> handleTransactionCreationException(RuntimeException e, WebRequest request) {
return new ResponseEntity<ErrorModel>(new ErrorModel().message("Error connecting to the database, please make sure it is still available."), HttpStatus.BAD_REQUEST);
}
#ExceptionHandler({ NotFoundException.class })
protected ResponseEntity<ErrorModel> handleApiException(RuntimeException e, WebRequest request) {
return new ResponseEntity<ErrorModel>(new ErrorModel().message(e.getMessage()), HttpStatus.NOT_FOUND);
}
}
The top 3 Exceptions all get caught and handled as they are supposed to, but the bottom Exception gets handled by the default Spring-Boot ExceptionHandler. It is a custom Exception that I throw inside a Controller:
public ResponseEntity<?> deleteActor(#ApiParam(value = "Used to identify a single actor.", required = true) #PathVariable("actor_id") Integer actorId, #RequestHeader("Accept") String accept) throws Exception {
Actor actor = actorRepository.findOne(actorId);
if (actor == null) {
throw new NotFoundException(404, "Not found");
}
actorRepository.delete(actorId);
return new ResponseEntity<Void>(HttpStatus.NO_CONTENT);
}
I've tried throwing one of the top Exceptions like this:
public ResponseEntity<?> readActor(#ApiParam(value = "Used to identify a single actor.", required = true) #PathVariable("actor_id") Integer actorId, #RequestHeader("Accept") String accept) throws Exception {
Actor actor = actorRepository.findOne(actorId);
if (actor == null) {
throw new NumberFormatException("");
}
return new ResponseEntity<Actor>(actor, HttpStatus.OK);
}
and these get handled just fine...
The tomcat logs also show this:
2017-06-05 11:30:20.080 INFO 9076 --- [ main] .m.m.a.ExceptionHandlerExceptionResolver : Detected #ExceptionHandler methods in responseEntityExceptionHandler
The Exception:
public class NotFoundException extends ApiException {
private int code;
public NotFoundException (int code, String msg) {
super(code, msg);
this.code = code;
}
}
The exception inherits from this baseclass:
public class ApiException extends Exception{
private int code;
public ApiException (int code, String msg) {
super(msg);
this.code = code;
}
}
Any ideas about why the custom Exception avoids detection by the ExceptionHandler?
I would be happy to provide additional information should that be necessary.
For this particular case the answer is to use Exception instead of RuntimeException, since NotFoundException does only inherit from Exception.
Further notable things:
To catch all exceptions one can use an #ExceptionHandler(Exception.class)
If using common names for exceptions, always check if you have imported the right one.
For me the CustomException was not getting caught by #ControllerAdvice method. I searched for hours and finally the issue got resolved on Updating the project.
Right click on your project -> Maven -> Update project.
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.