I am hosting Spring Boot app on weblogic 10.3.6 for REST api
I'd like to implement these 2 features:
Whenever my custom exception occurs I would like to send a http response with message e.g. 500 - "request couldn't be parsed because of something..."
Whenever any error is thrown I would like to get the stack trace that is usually printed to console (for debugging purposes)
I tried to solve the first part the following way:
#ControllerAdvice
public class ExceptionHandlerAdvice {
#ExceptionHandler(MyException.class)
public ResponseEntity handleException(MyException e) {
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(e.getMessage());
}
However as a response I only get 500 Internal Server Error with no message
As for the second part I tried simmilar approach but there was no error message either.
How can I solve this?
EDIT:
The best I could achieve was removing ExceptionHandlerAdvice and using annotation on my exception class instead like this:
#ResponseStatus(value = HttpStatus.INTERNAL_SERVER_ERROR, reason="This is a message I'd like to send")
It works, but I have to add some variables from code to the message and this method does not allow it
EDIT2:
This is a bit weird, perhaps a behavior of weblogic server, but when I set the httpStatus to HttpStatus.ACCEPTED I can see the message, if it is HttpStatus.Forbidden or any other 4xx error I just get the error without message
Create 'ResponseEntity' object with message and status and return it, it will display with error message.
/**
* HTTP_STATUS - 500 -Service Unavailable.
*
* #param exception
* Catches the following: MyException
* #return
*/
#ExceptionHandler({ MyException.class})
#ResponseStatus(value = HttpStatus.INTERNAL_SERVER_ERROR)
#ResponseBody
public static ResponseEntity<?> handleConnectionErrorResponse(MyException exception) {
return new ResponseEntity<String>("Some error occurred at server", HttpStatus.INTERNAL_SERVER_ERROR);
}
Do not return something, throw an :
#ResponseStatus(value = HttpStatus.UNPROCESSABLE_ENTITY)
public class UnprocessableEntity extends RuntimeException {
public UnprocessableEntity(String string) {
super(string);
}
}
Or another thing like that.
I also went through the same requirement.
Below is the code which is working for me:
String errMsg = "{\"errorMessage\":\"Parking Slot is not available\"}";
return new ResponseEntity<String>(errMsg, HttpStatus.INTERNAL_SERVER_ERROR);
Whereas errMsg should be written in the format which you want. Like I had requirment for response in JSON.
Hope this will help some of you
Well In my case I have done custom error handling logic.
We can define a custom Base Response class wich accepts generic type(Eg: user desired model)
Return BaseResponse as a response for each REST Methods
(GET, PUT, POST, DELETE, etc)
BaseResponse.java
public class BaseResponse<T> {
int statusCode;
String message;
T data;
public int getStatusCode() {
return statusCode;
}
public BaseResponse<T> getValidResponse(String message, T data) {
BaseResponse<T> baseResponse = new BaseResponse<T>();
baseResponse.statusCode = 200;
baseResponse.message = message;
baseResponse.data = data;
return baseResponse;
}
public BaseResponse<T> getErrorResponse(int StatusCode, String message) {
BaseResponse<T> baseResponse = new BaseResponse<T>();
baseResponse.statusCode = StatusCode;
baseResponse.message = message;
return baseResponse;
}
public void setStatusCode(int statusCode) {
this.statusCode = statusCode;
}
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
public T getData() {
return data;
}
public void setData(T data) {
this.data = data;
}
}
User.java
public class User {
String userName;
String userAddress;
String userEmail;
String userPhoneNumber;
...
//Getters & Setters
}
YourController.java
....
#PostMapping("/addUser")
public BaseResponse<User> addUser(User user) {
if (user.getUserName() != null && !user.getUserName().equals("")) {
UserEntity userEntity = new UserEntity();
userEntity.setName(user.getUserName());
...
userRepository.save(userEntity);
return new BaseResponse<User>().getValidResponse("Successfully Added User", user);
} else {
return new BaseResponse<User>().getErrorResponse(400, "Name field is required");
}
}
...
#DeleteMapping("/deleteUser/{userId}")
//Using ? for uncertain Response.Eg: Some response might have data response and some may not have data response...
public BaseResponse<?> deleteUser(#PathVariable(value = "userId") int userId) {
//After delete operation...we don't require data response.
return new BaseResponse<User>().getValidResponse("Successfully deleted the User", null);
}
This might not be an exact solution for the question asked but will surely help someone else.
Related
I implemented a global Exception Handler in my Spring Boot app by following this approach.
This approach returns error as ResponseEntity<ErrorResponse> type and here is the ErrorResponse class as you can also seen in that GitHub example:
ErrorResponse:
public class ErrorResponse {
private final int status;
private final String message;
private String stackTrace;
private List<String> errors;
}
On the other hand, I implement an ApiResponse class to format the response of my Controller as shown below:
ApiResponse:
public class ApiResponse<T> {
private Long timestamp;
private final String message;
private final T data;
public ApiResponse(Long timestamp, String message) {
this.timestamp = timestamp;
this.message = message;
this.data = data;
}
}
Here is my Controller method using this
Controller:
#GetMapping("/units/{id}")
public ResponseEntity<ApiResponse<UnitResponse>> findById(#PathVariable long id) {
final UnitResponse response = unitService.findById(id);
return ResponseEntity.ok(
new ApiResponse<>(Instant.now(clock).toEpochMilli(), "Success", response));
}
The problem is that, there are 2 different response types:
when I return response from Controller
when exception handler returns response.
So, I think I should merge 2 type of responses (ApiResponse and
ErrorResponse). Is that true?
And I tried to use a merged response class instead of Object in the GlobalExceptionHandler, but overrided methods gives error as the implemented method returns Object.
So, how should I solve this problem and return the same response type in each case (when there is an error or not any error)?
You should not merge the error and api classes. In the FE you should be checking the HTTP status. If it is good (200, 201, 2xx) then process response.json() as an ApiResponse. If it is bad (400) then response.json() is an ErrorResponse.
This is some example ReactJS code to illustrate how the two responses are handled:
componentDidMount() {
this.setState({ isLoading: true })
let api_url = 'http://localhost:8080/units/' + '1'; // id is 1
// Now, use JavaScript's native Fetch API to get
// an ApiResponse<UnitResponse>
fetch(api_url)
.then(res => {
if(res.status >= 400) {
// unpack the error
res.json().then(error => {
// here you have the error
console.error('Error returned is ', error);
});
}
return res.json();
})
.then(apiResponse => {
// here you have the ApiResponse<UnitResponse>>
console.log('Response returned is ', apiResponse );
},
// Note: it's important to handle errors here
// instead of a catch() block so that we don't swallow
// exceptions from actual bugs in components
err => {
// this is not the http.status type error
// this is something like 'server not reached'
});
}
I want to implement custom error pages for error codes with messages.
Following baeldung guide so far i got this on the backend;
Custom Exception:
public class TicketNotFoundException extends RuntimeException
{
public TicketNotFoundException(Long id)
{
super("Ticket not found with id: "+id);
}
}
Custom Response:
public class CustomErrorResponse
{
#JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd hh:mm:ss")
private LocalDateTime timestamp;
private int status;
private String error;
//getters setters
}
Custom Exception Handler:
#ControllerAdvice
public class CustomGlobalExceptionHandler extends ResponseEntityExceptionHandler
{
#ExceptionHandler(value = TicketNotFoundException.class)
public ResponseEntity<CustomErrorResponse> customHandleNotFound(Exception ex)
{
CustomErrorResponse errors = new CustomErrorResponse();
errors.setTimestamp(LocalDateTime.now());
errors.setError(ex.getMessage());
errors.setStatus(HttpStatus.NOT_FOUND.value());
return new ResponseEntity<>(errors, HttpStatus.NOT_FOUND);
}
}
And Response itself works:
{ timestamp: "2020-04-13 09:33:52", status: 404, error: "Ticket not
found with id: 1" }
Backend terminal:
Resolved [com.eggorko.ebt.ticket.TicketNotFoundException: Ticket not found with id: 1]
So my question is what should I do on the client side?
Client controller looks like this:
#GetMapping("/{id}")
public String ticket(#PathVariable Long id, Model model)
{
String url = "http://localhost:8080/api/ticket/";
ResponseEntity<Ticket> ticket = restTemplate.getForEntity( url+ id, Ticket.class);
model.addAttribute("ticket",ticket.getBody());
model.addAttribute("title","Tickets");
return "ticket";
}
I did this bit client side, but it doesn't work:
#Controller
public class MyErrorController implements ErrorController
{
#RequestMapping("/error")
public String handleError(HttpServletRequest request)
{
Object status = request.getAttribute(RequestDispatcher.ERROR_STATUS_CODE);
if (status != null) {
Integer statusCode = Integer.valueOf(status.toString());
if(statusCode == HttpStatus.NOT_FOUND.value()) {
return "error-404";
}
else if(statusCode == HttpStatus.INTERNAL_SERVER_ERROR.value()) {
return "error-500";
}
}
return "error";
}
#Override
public String getErrorPath()
{
return "/error";
}
}
This is what i got in terminal on client:
2020-04-13 10:34:40.559 ERROR 12868 --- [nio-8081-exec-8] o.a.c.c.C.[.[.[/].[dispatcherServlet] : Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed; nested exception is org.springframework.web.client.HttpClientErrorException$NotFound: 404 : [{"timestamp":"2020-04-13 10:34:40","status":404,"error":"Ticket not found with id: 1"}]] with root cause
And it's goes to 500 error page, instead of 404.
I sort of understand why it doesn't work. I don’t have anything handling error on the client side and it goes to error 500. But i don’t know what to do with it.
UPDATE
So i did this:
String url = "http://localhost:8080/api/ticket/";
try {
ResponseEntity<Ticket> ticket = restTemplate.getForEntity(url + id, Ticket.class);
model.addAttribute("ticket",ticket.getBody());
model.addAttribute("title","Tickets");
}catch (Exception e)
{
String msg = e.getMessage();
model.addAttribute("message",msg);
return "/error";
}
return "ticket";
And now at least i'm getting a error page with the actual message from backend. But this solution is working around MyErrorController. MyErrorController dosen't fire and basicly obsolete.
You are returning view name from your MyErrorController, do you have a error page errro.html or error.jsp (whatever suffix you are using for your view resolver) at the location you specified in view resolver? Morever you are using same error page for all error code you can create multiple error pages for different error codes say error-404.jsp and error-500.jsp. Another approach is to return the ModelAndView instead of view name, you can refer to link.
UPDATE
Ok now i get what are you trying to achieve, It is giving 500 because you are not handling exception in your client controller, when you are making rest template call you are saying i want a response of Ticket type but you are getting response of CustomErrorResponse, determine which exception it is throwing either by console log or catching the exception and then handle it and do your processing.
#GetMapping("/{id}")
public String ticket(#PathVariable Long id, Model model)
{
String url = "http://localhost:8080/api/ticket/";
try
{
ResponseEntity<Ticket> ticket = restTemplate.getForEntity( url+ id,
Ticket.class);
}
catch(Exception e)
{
// handel exception
}
model.addAttribute("ticket",ticket.getBody());
model.addAttribute("title","Tickets");
return "ticket";
}
OR
You can wrap your Ticket and CustomErrorResponse is a parent class and then can use instance of to determine which response you received
ResponseEntity<Parent> parent= restTemplate.getForEntity( url+ id,
Parent.class);
if(parent.getBody() instanceof Ticket)
{
//normal flow
}
else
{
//error
}
UPDATE2
ErrorController is obsolete because
String url = "http://localhost:8080/api/ticket/";
try {
ResponseEntity<Ticket> ticket = restTemplate.getForEntity(url + id, Ticket.class);
model.addAttribute("ticket",ticket.getBody());
model.addAttribute("title","Tickets");
}catch (Exception e)
{
String msg = e.getMessage();
model.addAttribute("message",msg);
return "/error";
}
return "ticket";
is returning a view error.jsp(or .html) with model, it is not redirecting to URL /error, you can do that by using redirect see, but I suggest not to, your ErrorController is mean to deal error occurred in your application not in some application your application in calling to, implement a proper response page for such condition.
Happy coding!
I am trying to make a log in system using spring. Problem is if username is not in the database I want to send a different status code and if username is in the database but password is wrong I want to send different status code. Because in my front end i am going to inform user using different alerts according to status code.
I cannot use HttpStatus.NOT_ACCEPTABLE or something like that because my controller is returning a User(my custom class). It will either return User or null.
#GetMapping("/users")
public User userLogin(#RequestParam String username,#RequestParam String password) {
User user = userService.findByUsername(username);
if(user==null) {
return null;
}
if(user.getPassword().equals(password)) {
return user;
} else {
return null;
}
}
Here I am trying to change status while returning nulls.
you can return ResponseEntity to meet your requirement
#GetMapping("/users")
public ResponseEntity<User> userLogin(#RequestParam String username,#RequestParam String password) {
User user = userService.findByUsername(username);
if(user==null) {
return new ResponseEntity<>(null,HttpStatus.NOT_FOUND);
}
if(user.getPassword().equals(password)) {
return new ResponseEntity<>(user,HttpStatus.OK);
} else {
return new ResponseEntity<>(null,HttpStatus.FORBIDDEN);
}
}
Spring 5 introduced the ResponseStatusException class. We can create an instance of it providing an HttpStatus and optionally a reason and a cause:
#GetMapping(value = "/{id}") public Foo findById(#PathVariable("id") Long id, HttpServletResponse response) {
try {
Foo resourceById = RestPreconditions.checkFound(service.findOne(id));
eventPublisher.publishEvent(new SingleResourceRetrievedEvent(this, response));
return resourceById;
}
catch (MyResourceNotFoundException exc) {
throw new ResponseStatusException(
HttpStatus.NOT_FOUND, "Foo Not Found", exc);
} }
Maybe this is which you looking for?
Detail in https://www.baeldung.com/exception-handling-for-rest-with-spring#controlleradvice
In an REST API generated with JHipster, I want to throw some 404 exceptions. It is normally done with
return new ResponseEntity<>(HttpStatus.NOT_FOUND);
which actualy results in a 404 response to the xhr request. The problem is that in the front side, JHipster parses the response with
angular.fromJson(result)
and such result is empty when the 404 is the actual response, which makes the parse to fail.
If I point to an unmapped URI, lets say /api/user while my controller maps to /api/users (note the plural) the 404 I got from the API has a body in it:
{
"timestamp": "2016-04-25T18:33:19.947+0000",
"status": 404,
"error": "Not Found",
"message": "No message available",
"path": "/api/user/myuser/contact"
}
which is correctly parse in angular.
How can I create a body like this? Is this exception thrown by spring or is tomcat who throws it?
I tried this: Trigger 404 in Spring-MVC controller? but I cant set the parameters of the response.
Basic Idea
First option is to define error objects and return them as 404 Not Found body. Something like following:
Map<String, String> errors = ....;
return ResponseEntity.status(HttpStatus.NOT_FOUND).body(errors);
Instead of returning a typical ResponseEntity, you can throw an Exception that will be resolved to a 404 Not Found. Suppose you have a NotFoundException like:
#ResponseStatus(code = HttpStatus.NOT_FOUND)
public class NotFoundException extends RuntimeException {}
Then if you throw this exception in your controllers, you would see something like:
{
"timestamp":1461621047967,
"status":404,
"error":"Not Found",
"exception":"NotFoundException",
"message":"No message available",
"path":"/greet"
}
If you want to customize the message and other parts of body, you should define a ExceptionHandler for NotFoundException.
Introducing Exception Hierarchies
If you're creating a RESTful API and want to have different Error Codes and Error Messages for different exceptional cases, you can create a hierarchy of exceptions representing those cases and extract message and code from each one.
For example, you can introduce an exception, say, APIException which is super-class of all other exceptions thrown by your controllers. This class defines a code/message pair like:
public class APIException extends RuntimeException {
private final int code;
private final String message;
APIException(int code, String message) {
this.code = code;
this.message = message;
}
public int code() {
return code;
}
public String message() {
return message;
}
}
Each subclass depending on the nature of its exception can provide some sensible values for this pair. For example, we could have an InvalidStateException:
#ResponseStatus(code = HttpStatus.BAD_REQUEST)
public class InvalidStateException extends APIException {
public InvalidStateException() {
super(1, "Application is in invalid state");
}
}
Or that notorious not found ones:
#ResponseStatus(code = HttpStatus.NOT_FOUND)
public class SomethingNotFoundException extends APIException {
public SomethingNotFoundException() {
super(2, "Couldn't find something!");
}
}
Then we should define an ErrorController that catches those exceptions and turn them to meaningful JSON representations. That error controller may look like following:
#RestController
public class APIExceptionHandler extends AbstractErrorController {
private static final String ERROR_PATH = "/error";
private final ErrorAttributes errorAttributes;
#Autowired
public APIExceptionHandler(ErrorAttributes errorAttributes) {
super(errorAttributes);
this.errorAttributes = errorAttributes;
}
#RequestMapping(path = ERROR_PATH)
public ResponseEntity<?> handleError(HttpServletRequest request) {
HttpStatus status = getStatus(request);
Map<String, Object> errors = getErrorAttributes(request, false);
getApiException(request).ifPresent(apiError -> {
errors.put("message" , apiError.message());
errors.put("code", apiError.code());
});
// If you don't want to expose exception!
errors.remove("exception");
return ResponseEntity.status(status).body(errors);
}
#Override
public String getErrorPath() {
return ERROR_PATH;
}
private Optional<APIException> getApiException(HttpServletRequest request) {
RequestAttributes attributes = new ServletRequestAttributes(request);
Throwable throwable = errorAttributes.getError(attributes);
if (throwable instanceof APIException) {
APIException exception = (APIException) throwable;
return Optional.of(exception);
}
return Optional.empty();
}
}
So, if you throw an SomethingNotFoundException, the returned JSON would be like:
{
"timestamp":1461621047967,
"status":404,
"error":"Not Found",
"message":"Couldn't find something!",
"code": 2,
"path":"/greet"
}
I guess you can do this if you want to return some message or test with your error code
#ResponseBody
public ResponseEntity somthing() {
HttpHeaders headers = new HttpHeaders();
headers.add("Content-Type", "application/json; charset=utf-8");
return new ResponseEntity<>(new Gson().toJson("hello this is my message"), headers, HttpStatus.NOT_FOUND);
}
throw new ResponseStatusException(HttpStatus.NOT_FOUND, "message");
I use the following code to handle rest calls using Spring MVC.
#RequestMapping(value = "login", method = RequestMethod.GET)
public #ResponseBody
User login(#RequestParam String username, #RequestParam String password) {
User user = userService.login(username, password);
if (user == null)
...
return user;
}
I would like to send the client customer http codes for wrong username, wrong passwords, password changed and password expire conditions. How can I modify the existing code to send these error codes to the client?
You can use controller advice to map exception thrown within controller to some client specific data at runtime.
For example if user is not found, your controller should throw some exception (custom or existed one)
#RequestMapping(value = "login", method = RequestMethod.GET)
#ResponseBody
public User login(#RequestParam String username, #RequestParam String password) {
User user = userService.login(username, password);
if (user == null)
throw new UserNotFoundException(username); //or another exception, it's up to you
return user;
}
}
Then you should add #ControllerAdvice that will catch controller exceptions and make 'exception-to-status' mapping (pros: you will have single point of responsibility for 'exception-to-status-mapping'):
#ControllerAdvice
public class SomeExceptionResolver {
#ExceptionHandler(Exception.class)
public void resolveAndWriteException(Exception exception, HttpServletResponse response) throws IOException {
int status = ...; //you should resolve status here
response.setStatus(status); //provide resolved status to response
//set additional response properties like 'content-type', 'character encoding' etc.
//write additional error message (if needed) to response body
//for example IOUtils.write("some error message", response.getOutputStream());
}
}
Hope this helps.
One way is to add some additional classes for returning HTTP error. Your code will looks like this:
#RequestMapping(value = "login", method = RequestMethod.GET)
#ResponseBody
public User login(#RequestParam String username, #RequestParam String password) {
User user = userService.login(username, password);
if (user == null)
throw new UnauthorizedException();
return user;
}
}
#ResponseStatus(value = HttpStatus.UNAUTHORIZED)
public class UnauthorizedException extends RuntimeException{
}
In this case user will get 401 response status code
I hope it helps
You can return an HTTP 500 or code of your choosing (from the org.springframework.http.HttpStatus enumeration) and use a custom error to emulate something like a SOAP fault within the JSON response.
For example:
#ResponseStatus(value = HttpStatus.INTERNAL_SERVER_ERROR)
#ExceptionHandler(YourTargetException.class)
#ResponseBody
Fault caughtYourTargetException(HttpServletRequest req, Exception ex) {
String code = ex.getClass().getName();
String reason = "Caught YourTargetException."
return new Fault(code, reason);
}
The Fault class could look something like this (inspired by http://www.w3.org/TR/soap12-part1/#soapfault):
/**
* A Fault is an object that can be serialized as JSON when expected errors occur.
*/
public class Fault {
#JsonProperty("faultCode")
private final String code;
#JsonProperty("faultReason")
private final String reason;
#JsonProperty("faultDetails")
private final List<String> details = new ArrayList<>();
public Fault(String code, String reason) {
this.code = code;
this.reason = reason;
}
public Fault(String code, String reason, String... detailEntries) {
this.code = code;
this.reason = reason;
details.addAll(Arrays.asList(detailEntries));
}
public String getCode() {
return code;
}
public String getReason() {
return reason;
}
/**
* Zero or more details may be associated with the fault. It carries
* additional information relative to the fault. For example, the Detail
* element information item might contain information about a message
* not containing the proper credentials, a timeout, etc.
* #return Zero or more detail entries.
*/
public Iterable<String> getDetails() {
return details;
}
#Override
public String toString() {
return String.format("Fault %s occurred. The reason is %s.", getCode(),
getReason());
}
}
You could use one of the existing SOAPFaults in Java frameworks, but I have found they don't play well in REST. Creating my own simple version turned out to be simpler.
You can define your own status code and returning objects. In your code throw custom exceptions and then define an exception handler as follows:
#ControllerAdvice
public class GlobalControllerExceptionHandler {
#ExceptionHandler(MyException.class)
public ResponseEntity<MyRetObject> handleControllerError(HttpServletRequest req, MyException ex) {
LOG.warn("My error", ex);
MyRetObject errorMessage = new MyRetObject(ex.getMessage());
return ResponseEntity.status(600).body(errorMessage);
}
}
In your case replace MyExeption.class by UserNotFoundException.class and build your customer error response object and error code