Spring Custom Exception hanlding - spring-boot

#ExceptionHandler(RecordAlreadyExistException.class)
public final ResponseEntity<Object> handleAlreadyExistRecordException(
RecordAlreadyExistException ex, WebRequest request) {
List<String> details = new ArrayList<>();
details.add(ex.getLocalizedMessage());
ErrorResponse errorDetails = new ErrorResponse(
"Already registered user ? Did you forget your password ? Please contact anemail#gmail.com for all queries", details);
return new ResponseEntity(errorDetails, HttpStatus.NOT_FOUND);
}
Here is my scenario:
When the user already exists,
it should throw this custom exception.
I get the response code,
but not the body
(i.e. the error details I passed in the response entity are
not displayed in the browser).
Any idea?

Related

Returning errors in HTML instead of JSON

I followed some tutorial for Spring Boot Security, and in that tutorial the guy is not creating a Frontend so his message for wrong credentials when user is trying to log in are in JSON. I did some changes to my own and created a page for login and registration, but I have problem to show errors when user insert wrong credentials.
This is method that show me JSON when user insert wrong data:
#Override
public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException)
throws IOException, ServletException {
logger.error("Unauthorized error: {}", authException.getMessage());
response.setContentType(MediaType.APPLICATION_JSON_VALUE);
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
final Map<String, Object> body = new HashMap<>();
body.put("status", HttpServletResponse.SC_UNAUTHORIZED);
body.put("error", "Unauthorized");
body.put("message", authException.getMessage());
body.put("path", request.getServletPath());
final ObjectMapper mapper = new ObjectMapper();
mapper.writeValue(response.getOutputStream(), body);
}
And in my controller I have this:
#PostMapping("/login")
#Transactional
public String login(#Valid #ModelAttribute("login") LoginRequest loginRequest, BindingResult result, HttpServletResponse response, Model model) {
Authentication authentication = authenticationManager.authenticate(new UsernamePasswordAuthenticationToken(loginRequest.getUsername(), loginRequest.getPassword()));
SecurityContextHolder.getContext().setAuthentication(authentication);
UserDetailsImpl user = (UserDetailsImpl) authentication.getPrincipal();
ResponseCookie jwtCookie = jwtUtils.generateJwtCookie(user);
boolean thereAreErrors = result.hasErrors();
if (thereAreErrors) {
model.addAttribute("login", loginRequest);
return "login_form";
}
model.addAttribute("login", loginRequest);
response.addHeader(HttpHeaders.SET_COOKIE, jwtCookie.toString());
return "redirect:/api/test/homePage";
}
So far its return error messages (validation field) on HTML if user doesn't populate fields, but if user enter wrong data I'm getting JSON {"path":"/api/auth/login","error":"Unauthorized","message":"Bad credentials","status":401}
I'm guessing that you're using thymeleaf to generate your html because that's the default option in most spring-boot projects, if so - take a look at this example.
Basically you need to pass the information about login error to the model object, and use it in your html template. For example .:
Controller method
model.addAttribute("loginError", true);
Login page template
<p th:if="${loginError}" class="error">Wrong user or password</p>

How to handle the http response from an api call efficiently using spring boot

When we fire an api call to a 3rd party service, we can get different HTTP responses (200, 404 etc.). How can we handle them in a standard way?
private ResponseEntity<ResultHolder> responseEntity;
public ResponseEntity<ResultHolder> serviceTest(String searchText, String countryCode) {
logger.info("Service started");
String url = prepareUrl(searchText,countryCode); //custom method
HttpHeaders header = new HttpHeaders();
prepareHeader(header); //custom method
HttpEntity<String> requestEntity = new HttpEntity<String>(header);
try {
logger.info("Calling the API");
responseEntity = restClient.exchange(url,
HttpMethod.GET,
requestEntity,
ResultHolder.class);
}catch (Exception e) {
logger.error("Exception while calling the API "+ e);
//Here I am trying to get the value of response code and handle based on that
//Is this the right way to solve the problem?
if(responseEntity.getStatusCodeValue() != 200) {
responseEntity = new ResponseEntity<ResultHolder>(
HttpStatus.BAD_REQUEST);
}
}
logger.info("Service Ended");
return responseEntity;
}
What if I want to display distinct custom messages for server side errors and for user errors like 'No Internet Connection'.
Kindly help me to understand the good practises in this area.
Thank you

Is there a simpler exception handling technique for Spring?

I have read about controller based exceptions using #ExceptionHandler.
I have read about global exception handling using #ControllerAdvice.
I have also read about extending HandlerExceptionResolver for more in-depth exception handling.
However, what I would ideally like to do is be able to throw a global exception with parameters that dictate a JSON response returned to the client, at any layer in my application.
For instance:
throw new CustomGlobalException(HttpStatus.UNAUTHORISED, "This JWT Token is not Authorised.")
throw new CustomGlobalException(HttpStatus.FORBIDDEN, "This JWT Token is not valid.")
This would then return a JSON response based on the model I've created, along with the status, such as :
{
"success" : "false",
"message" : "This JWT Token is not Authorised."
}
And for this to be returned as a REST response from my controller.
Is something like this possible? Or Do I have to go through the process of making custom error exceptions for everything as described in the documentation.
To clarify, I require the exception to interrupt whatever the ongoing process is, perhaps fetching data from the database, and immediately return the given exception to the client. I have a web mvc setup.
Further details:
#ControllerAdvice
#RequestMapping(produces = "application/json")
public class GlobalExceptionHandler {
#ExceptionHandler(CustomException.class)
public ResponseEntity<Object> handleCustomException(CustomException ex,
WebRequest request) {
Map<String, Object> response = new HashMap<>();
response.put("message", ex.getMessage());
return new ResponseEntity<>(response, ex.getCode());
}
}
Exception thrown here:
#Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain
filterChain) throws ServletException, IOException {
logger.debug("Filtering request for JWT header verification");
try {
String jwt = getJwtFromRequest(request);
logger.debug("JWT Value: {}", jwt);
if (StringUtils.hasText(jwt) && tokenProvider.validateToken(jwt)) {
String username = tokenProvider.getUserIdFromJWT(jwt);
UserDetails userDetails = customUserDetailsService.loadUserByUsername(username);
UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken
(userDetails, null, userDetails.getAuthorities());
authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
SecurityContextHolder.getContext().setAuthentication(authentication);
} else {
logger.error("No Valid JWT Token Provided");
throw new CustomException(HttpStatus.UNAUTHORIZED, "No Valid JWT Token Provided");
}
} catch (Exception ex) {
logger.error("Could not set user authentication in security context", ex);
}
filterChain.doFilter(request, response);
}
This doesn't exactly do what you want to achieve, but the simplest way of doing almost what you want (and is cleaner, IMO), is to simply define an exception like the following:
#ResponseStatus(HttpStatus.UNAUTHORIZED)
public class UnauthorizedException extends RuntimeException {
public UnauthorisedException(String message) {
super(message);
}
}
Now every time such an exception is thrown (not returned) from a controller method (directly or indirectly), you'll get a response such as
{
"timestamp": "2018-06-24T09:38:51.453+0000",
"status": 401,
"error": "Unauthorized",
"message": "This JWT Token is not Authorised.",
"path": "/api/blabla"
}
And of course the actual status code of the HTTP response will also be 401.
You can also throw a ResponseStatusException, which is more generic and allows you to use the same exception type and pass the status as argument. But I find it less clean.
Following my post on how to handle exception here, you can write your own handler something like this,
class CustomGlobalException {
String message;
HttpStatus status;
}
#ExceptionHandler(CustomGlobalException.class)
public ResponseEntity<Object> handleCustomException(CustomGlobalException ex,
WebRequest request) {
Map<String, Object> response = new HashMap<>();
response.put("success", "false");
response.put("message", ex.getMessage());
return new ResponseEntity<>(response, ex.getStatus());
}
Code mentioned above will handle CustomGlobalException occurred any layer of code.
Since Spring 5 and Above, a ResponseStatusException (spring framework provided) would be better.
Please refer to spring-response-status-exception

Jersey Response for Exception not working as Expected

I am authenticating user with name and password from my database.
If the user name or password is incorrect then I am throwing a custom Exception.
but I am not getting expected status code and response.
I am new to Jersey and web services.
This is my function which creates a response :
public static Response error(int Status_code, String Request_status ,String data, String Response_error){
ResponseBuilder response = new ResponseBuilder();
response.Status_code = Status_code;
response.Request_status = Request_status;
response.data =data;
response.Response_error = Response_error;
return Response.status(Status_code).entity(response).build();
}
This is my custom Exception class code :
public class InvalidcredentialsException extends WebApplicationException {
public InvalidcredentialsException(Response response ) {
super(response);
}
}
This is how I am throwing this exception in my code if credentials are wrong(user name and password) in AuthenticateUser function in my Model.
throw new InvalidcredentialsException(ResponseBuilder.error(Response.Status.UNAUTHORIZED.getStatusCode(),"success","","invalid credentials"));
When I am checking my API I am getting 204 as response , but I am expecting a JSON with the parameters which I have provided.
I have implemented my API in the following way :
#Path("/user")
public class User {
#POST
#Path("/login")
#Consumes(MediaType.APPLICATION_JSON)
#Produces("application/json")
public void UserAuthentication(UserCredentials user) {
UserModel userAuthentication = new UserModel();
userAuthentication.AuthenticateUser(user);
}
}
I have used the following link to create Exception and throw :
JAX-RS / Jersey how to customize error handling?
This is my authenticate function :
public void AuthenticateUser(UserCredentials user) {
Database db = new Database();
Connection con = null;
PreparedStatement preparedStatement = null;
ResultSet rs = null;
try {
String username = user.getUsername();
String password = user.getPassword();
con = db.getConnection();
if (con != null) {
String selectQuery_UserDetails = "SELECT name,password FROM user WHERE name=? AND password = ?";
preparedStatement = con.prepareStatement(selectQuery_UserDetails);
preparedStatement.setString(1, username);
preparedStatement.setString(2, password);
rs = preparedStatement.executeQuery();
if (!rs.isBeforeFirst()) {
throw new InvalidcredentialsException(ResponseBuilder.error(Response.Status.UNAUTHORIZED.getStatusCode(),"success","","invalid credentials"));
}
}}catch (SQLException e) {
} finally {
db.closeConnection(con);
}
}
Thanks
You are catching but not handling the SQLException. When an error occurs, while accessing or trying to access the database, the exception is ignored and no error response is created. Maybe the database is not accessible or configured incorrectly.
You should wrap the exception into a RuntimeException like javax.ws.rs.InternalServerErrorException or just remove the catch statement. And you should log the error here or in an exception mapper, so that you are able to analyze and fix the problem.
I suggest to wrap the exception and log it like that:
}catch(SQLException e){
logger.log(Level.SEVERE, "db error while authenticate user", e);
throw new InternalServerErrorException("db error while authenticate user", e);
}
Now the runtime exception will be handled by a default exception mapper, which will generate the error response. Additional the error is logged. In this code I used java.util.logging - if necessary adjust it to the logging api you use.

Spring Validator and BindingResult - How to set different HttpStatus Codes?

Dear Spring Community,
I am building my project using Spring. In my API layer, I am leveraging the Validator interface in order to do some custom validation and set an error.
#Override
public void validate(Object obj, Errors e) {
SignUpRequest signUpRequest = (SignUpRequest) obj;
User user = userService.getUserByEmail(signUpRequest.getEmail());
if (user != null) {
e.rejectValue("user", ErrorCodes.USER_EXIST, "user already exist");
}
}
Now, in my API signature, since I am using the BindingResult object, in the #ControllerAdvice that I have, if the user provides an empty value for an attribute of my DTO object, I wont be able to get into the #ExceptionHandler(MethodArgumentNotValidException.class).
What this means is that, I wont be able to throw an HttpStatus.BAD_REQUEST for any empty value provided.
In the above case of my validator, It wont be a HttpStatus.BAD_REQUEST but rather it will be a HttpStatus.OK. So my problem is that, how do I provide different HttpStatus types based on the errors I am getting from my validator? Also is there a way to have the empty value still get picked up by the #ExceptionHandler(MethodArgumentNotValidException.class) in my #ControllerAdvice and have my other custom validations picked up by the bindingResult?
I hope I am clear on the question. Appreciate any help!
OK, I believe I came up with the solution!
In order to have different HttpStatus being thrown base on the type of error you have, you need to have custom exceptions. Then have your custom exceptions thrown inside your Validator. Your ControllerAdvice should register to pick up the custom exceptions and act upon them accordingly.
For example the Validator should have something like this:
if (!matcher.matches()) {
e.rejectValue("email", ErrorCodes.EMAIL_INVALID, "email address is invalid");
throw new BadRequestException("email address is invalid", e);
}
User user = userService.getUserByEmail(signUpRequest.getEmail());
if (user != null) {
e.rejectValue("email", ErrorCodes.USER_EXIST, "user already exist");
throw new ValidationException("User Exist", e);
}
And the Controller Advice should have:
#ExceptionHandler(ValidationException.class)
#ResponseBody
public ResponseEntity<Map<String, Object>> handleValidationException(
ValidationException validationException) {
Map<String, Object> result = createErrorResponse(validationException.getErrors());
return new ResponseEntity<Map<String, Object>>(result, HttpStatus.OK);
}
#ExceptionHandler(BadRequestException.class)
#ResponseBody
public ResponseEntity<Map<String, Object>> handleBadRequestException(
BadRequestException badRequestException) {
Map<String, Object> result = createErrorResponse(badRequestException.getErrors());
return new ResponseEntity<Map<String, Object>>(result, HttpStatus.BAD_REQUEST);
}
This way, you can have different HttpStatus returned base on the type of error you have.

Resources