I have a Spring boot Get API which returns a 'User' object for a given user id.
#GetMapping( path = "/users/{userId}")
public ResponseEntity<User> getUser(
#PathVariable( userId )
Long id) throws CustomException {
//retuen user object
}
When someone passes a string value to the endpoint as userId this returns 'NumberFormatException'. Which gives an idea of the type of userId that is used on the side of the system. Is there a possibility that I can return a CustomException rather than returning a 'NumberFormatException'.
One option is to use type String for userId and then try to convert it to Long inside the method.
Other than that, Is there a better way to address this issue with any inbuild finalities of Spring Boot?
Yes you can by creating an exception advice class that can handle the runtime exceptions.
For example to handle the exceptions you must do the following:-
1- Create a custom class to use it as an exception response class.
public class ExceptionResponse {
private String message;
public ExceptionResponse(String message) {
this.message = message;
}
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
}
2- create an exception handler class to handle the thrown exceptions and add the exceptions that you want to handle.
#RestControllerAdvice
public class ExceptionCatcher {
#ExceptionHandler(NumberFormatException.class)
public ResponseEntity<ExceptionResponse> numberFormatExceptionHandler(NumberFormatException exception) {
ExceptionResponse response = new ExceptionResponse(exception.getMessage());
return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(response);
}
}
Or you can check this link to get more informations spring-rest-error-handling-example
You need to validate the inputs using #Validated annotations.
Please follow below link for more details:
https://howtodoinjava.com/spring-rest/request-body-parameter-validation/
Related
I have a GET method inside a controller that get a lot of parameters. I want to redirect the URL in case of exception or invalid input and return (print to the client) the exception with addition message.
My function looks like this:
#GetMapping("/v1.0/hello/{userName}")
public ClientResponse getDetails(#PathVariable(value = "userName") String userName,
#RequestParam(value = "expInp1", required = false) int expInp1,
#RequestParam(value = "expInp2", required = false) int expInp2,
#RequestParam(value = "expInp3", required = false) int expInp3){
// do something...
return clientResponse;
}
ClientResponse is an object that contain all the relevant details and can't be changed.
For example, if someone inserts the following URL /v1.0/hello/{userName}?expInp4=XXX, I want to redirect them to /v1.0/hello/ with a suitable message.
Is there a Spring annotation doing that without a lot of code? And if not, what is the best way in my case?
You can take a look at #RestControllerAdvice coupled with #ExceptionHandler annotation.
you can follow these steps to create a redirect system:
create your own exception
public class RedirectException extends RuntimeException{
private String redirectUrl;
public RedirectException(String message, String rUrl) {
super(message);
this.redirectUrl = rUrl;
}
}
create your controller advice
#RestControllerAdvice
public class ErrorController {
#ExceptionHandler(RedirectExecption.class)
public void handleRedirect(RedirectException re, HttpServletResponse res) {
res.sendRedirect(re.getRedirectUrl);
}
}
when you want to send a redirect response in you running code just throw a redirectException with the redirecturl you want to reach
p.s. you can use the same mechanism for building an error handling system.
create a new class with #RestControllerAdvice or #ControllerAdvice annotation to handle the exception globally in Spring Boot.
Refer this link
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'm using Spring Boot with Data JPA.
I have the following code.
A User Class with name and an informative message.
class UserResponse{
private String name;
private String message;
}
User JPA Repository which finds userBy id;
interface UserRepository{
Optional<User> findUserById(String id);
}
User Service which invokes repo and set message if user not found
class UserService(){
UserResponse user = new UserResponse();
public UserResponse getUserById(String userId){
Optional<User> useroptional = userRepository.findById(userId);
if(userOptional.isPresent()){
user.setName(userOptional.get().getName());
}else{
user.setMessage("User Not Found");
}
}
UserController has to set proper HTTP status code as per the message.
class UserController(){
public ResponseEntity<UserResponse> getUserById(String id){
UserResponse user = userService.getUserById(id);
HttpStatus status = OK;
if(!StringUtils.isEmpty(user.getMessage())){
status = NOT_FOUND;
}
return new ResponseEntity<>(user,status);
}
}
The problems I have faced is inorder to set proper status code in controller layer I have to inspect user message,which i didn't like.
Is there anyway we can create a control flow for Success and Failure cases.
Say One Return type and flow for Success scenario and vice-versa.
I know Scala has this feature with Either keyword.
Is there any alternate in Java ?
Or any other approach I can use to handle this better...
One approach would be returning RepsonseEntity in service layer itself with proper status code but setting status code is controller's Responsibility is what I felt.
In case of failure you can throw custom Exception with proper message. Then you can catch it in #ControllerAdvice. I'll add an example in a moment.
#ControllerAdvice
public class GlobalExceptionHandler {
#ExceptionHandler(MyCustomException.class)
public ResponseEntity<String> exception(MyCustomException e) {
return new ResponseEntity(e.getMessage(), HttpStatus.NotFound);
}
}
In one #ControllerAdvice one could have more methods listening for different Exceptions. Custom Exception can hold whatever you want - it's a normal class - so you can return ResponseEntity of whatever you want.
For example:
#Transactional(readOnly = true)
#GetMapping("/{id}")
public ResponseEntity<?> getUserById(#PathVariable("id") String userId) {
return userRepository.findById(userId)
.map(user -> ResponseEntity.ok().body(user))
.orElse(new ResponseEntity<>(/* new ErrorMessage */, HttpStatus.NOT_FOUND))
}
For 'not found' response you have to create an error message object and return it to client.
I am using methods like this
#RequestMapping(method = RequestMethod.GET)
public ResponseEntity<UserWithPhoto> getUser(#RequestHeader(value="Access-key") String accessKey,
#RequestHeader(value="Secret-key") String secretKey){
try{
return new ResponseEntity<UserWithPhoto>((UserWithPhoto)this.userService.chkCredentials(accessKey, secretKey, timestamp),
new HttpHeaders(),
HttpStatus.CREATED);
}
catch(ChekingCredentialsFailedException e){
e.printStackTrace();
return new ResponseEntity<UserWithPhoto>(null,new HttpHeaders(),HttpStatus.FORBIDDEN);
}
}
And I want to return some text message when exception occurs but now I just return status and null object. Is it possible to do?
As Sotirios Delimanolis already pointed out in the comments, there are two options:
Return ResponseEntity with error message
Change your method like this:
#RequestMapping(method = RequestMethod.GET)
public ResponseEntity getUser(#RequestHeader(value="Access-key") String accessKey,
#RequestHeader(value="Secret-key") String secretKey) {
try {
// see note 1
return ResponseEntity
.status(HttpStatus.CREATED)
.body(this.userService.chkCredentials(accessKey, secretKey, timestamp));
}
catch(ChekingCredentialsFailedException e) {
e.printStackTrace(); // see note 2
return ResponseEntity
.status(HttpStatus.FORBIDDEN)
.body("Error Message");
}
}
Note 1: You don't have to use the ResponseEntity builder but I find it helps with keeping the code readable. It also helps remembering, which data a response for a specific HTTP status code should include. For example, a response with the status code 201 should contain a link to the newly created resource in the Location header (see Status Code Definitions). This is why Spring offers the convenient build method ResponseEntity.created(URI).
Note 2: Don't use printStackTrace(), use a logger instead.
Provide an #ExceptionHandler
Remove the try-catch block from your method and let it throw the exception. Then create another method in a class annotated with #ControllerAdvice like this:
#ControllerAdvice
public class ExceptionHandlerAdvice {
#ExceptionHandler(ChekingCredentialsFailedException.class)
public ResponseEntity handleException(ChekingCredentialsFailedException e) {
// log exception
return ResponseEntity
.status(HttpStatus.FORBIDDEN)
.body("Error Message");
}
}
Note that methods which are annotated with #ExceptionHandler are allowed to have very flexible signatures. See the Javadoc for details.
Here is an alternative. Create a generic exception that takes a status code and a message. Then create an exception handler. Use the exception handler to retrieve the information out of the exception and return to the caller of the service.
http://javaninja.net/2016/06/throwing-exceptions-messages-spring-mvc-controller/
public class ResourceException extends RuntimeException {
private HttpStatus httpStatus = HttpStatus.INTERNAL_SERVER_ERROR;
public HttpStatus getHttpStatus() {
return httpStatus;
}
/**
* Constructs a new runtime exception with the specified detail message.
* The cause is not initialized, and may subsequently be initialized by a
* call to {#link #initCause}.
* #param message the detail message. The detail message is saved for later retrieval by the {#link #getMessage()}
* method.
*/
public ResourceException(HttpStatus httpStatus, String message) {
super(message);
this.httpStatus = httpStatus;
}
}
Then use an exception handler to retrieve the information and return it to the service caller.
#ControllerAdvice
public class ExceptionHandlerAdvice {
#ExceptionHandler(ResourceException.class)
public ResponseEntity handleException(ResourceException e) {
// log exception
return ResponseEntity.status(e.getHttpStatus()).body(e.getMessage());
}
}
Then create an exception when you need to.
throw new ResourceException(HttpStatus.NOT_FOUND, "We were unable to find the specified resource.");
Evaluating the error response from another service invocated...
This was my solution for evaluating the error:
try {
return authenticationFeign.signIn(userDto, dataRequest);
}catch(FeignException ex){
//ex.status();
if(ex.status() == HttpStatus.UNAUTHORIZED.value()){
System.out.println("is a error 401");
return new ResponseEntity<>(HttpStatus.UNAUTHORIZED);
}
return new ResponseEntity<>(HttpStatus.OK);
}
return new ResponseEntity<>(GenericResponseBean.newGenericError("Error during the calling the service", -1L), HttpStatus.EXPECTATION_FAILED);
I am using Below Custom Exception class in my project
public class BadRequestException extends WebApplicationException {
private static final long serialVersionUID = 1L;
private String message;
public BadRequestException(String message) {
super();
this.message = message;
}
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
}
I have created a Mapper class also..
public class BadRequestExceptionMapper implements ExceptionMapper<BadRequestException> {
public Response toResponse(BadRequestException brexec) {
return Response.status(Response.Status.INTERNAL_SERVER_ERROR).entity(brexec.getResponse().getEntity()).type(MediaType.APPLICATION_JSON).build();
}
}
I am calling my service through a Servlet and the Exception is thrown by one of its method but i am not able to catch it in Servlet class.I have used below code to catch the exception..
try{
//Some Business logic then
service.path("restful").path("jwbservice/" + methodName + "/" + id).header("lid", lid).delete(String.class);
}
catch (BadRequestException ex) {
out.println(ex);
}
catch(Exception exe){
out.println(exe);
}
And the service method i have used this code in my Service class which will throw the exception.
#DELETE
#Path("/deleteLink/{id}")
#Produces(MediaType.APPLICATION_JSON)
public String deleteLink(#PathParam("id") int id, #HeaderParam("lid") String lid) throws BadRequestException {
if (id<= 0) {
throw new BadRequestException("Required Parameter: id");
}
//Some Business Logic
}
My Service throw the BadRequestException but in Servlet it is going to Exception catch not in BadRequestException Catch block.
Can any one know what i am doing wrong.
You will never get that exception in your servlet. This is because the servlet is effectively a REST client, and you are invoking a remote resource method to get some data. The resource call will either be successful (and some data will be mapped back), or it will fail and you will get no data (or a client side error).
On a side note, there is a problem in your server side exception mapper. You do not verify that the exception actually has a response entity before calling:
brexec.getResponse().getEntity()
In cases where the exception doesn't have a response the above code will cause a null pointer exception.
Some quick notes:
Exception classes already have a message property. You do not need to define an additional one
Your exception mapper needs to check for a non-existent response property, before trying to do something with it
The resource path in your servlet does not appear to match the server side path. I assume that is a copy/paste error.