I have a controller that, in case there is no user with the given name, will return 404 NOT FOUND.
#GetMapping(value = "/profile/{username}", produces = MediaType.APPLICATION_JSON_VALUE)
public HttpEntity<User> getUsers(#PathVariable("username") String username) {
User user = userService.findOneByUsername(username);
if(user != null) {
return ResponseEntity.ok(user);
}
return new ResponseEntity<>(HttpStatus.NOT_FOUND);
}
Then I created a controller that will be able to handle this exception
#ControllerAdvice
public class ExceptionHandlerController {
#ExceptionHandler(NoHandlerFoundException.class)
#ResponseStatus(value = HttpStatus.NOT_FOUND)
public ModelAndView handleNotFound(NoHandlerFoundException e) {
return new ModelAndView("redirect:/signIn");
}
}
However, it has no effect. The controller returns the normal default 404 error page. It does not respond to my controller.
EDIT: I set spring.mvc.throw-exception-if-no-handler-found = true, but that also did not help. I'm using Spring Boot.
You're not throwing NoHandlerFoundException in your controller. This way the ControllerAdvice will not run.
Related
I'm working on a spring-boot application. I tried handling exceptions .But i guess there is something wrong about how I'm doing it because it always throws internal server error 500.
I tried setting up custom exception classes and also used response status codes with #ResponseStatus. But regardless of what the exception is it throws an internal server error only.
I'm using intellij and the message i've given in the exception is printed there but the response body is empty.This i guess must be because it is throwing an internal server error.
Controller class
#RequestMapping(value = "/attendance",method = RequestMethod.POST)
public ResponseEntity<?> enterAttendance(#RequestBody ViewDTO viewDTO) throws CustomException{
return new ResponseEntity<>(tempResultServices.handleAttendance(viewDTO),HttpStatus.OK);
}
}
Service layer
#Override
public TempResult handleAttendance(ViewDTO viewDTO) throws CustomException {
TempIdentity tempIdentity=new TempIdentity();
tempIdentity.setRegistrationNo(viewDTO.getRegistrationNo());
tempIdentity.setCourseId(viewDTO.getCourseId());
tempIdentity.setYear(viewDTO.getYear());
tempIdentity.setSemester(viewDTO.getSemester());
User user=userService.findByUserId(viewDTO.getUserId());
tempIdentity.setUser(user);
if(!viewDTO.isAttendance()){
TempResult tempResultUser =new TempResult(tempIdentity,viewDTO.isAttendance(),0);
ResultIdentity resultIdentity=new ResultIdentity(tempIdentity.getRegistrationNo(),tempIdentity.getCourseId(),tempIdentity.getYear(),tempIdentity.getSemester());
Result result=new Result(resultIdentity,0,"E*");
AttendanceDraft attendanceDraft=atteDraftService.findDraft(viewDTO.getRegistrationNo(),viewDTO.getCourseId(),viewDTO.getYear(),viewDTO.getSemester(),viewDTO.getUserId());
if(attendanceDraft!=null){
attendanceDraft.setStatus(true);
atteDraftService.save(attendanceDraft);
//atteDraftService.delete(attendanceDraft);
tempResultRepository.save(tempResultUser);
resultRepository.save(result);
return tempResultUser;
}
else{
throw new CustomException("No draft available");
}
}
else{
TempResult tempResultUser =new TempResult(tempIdentity,viewDTO.isAttendance());
AttendanceDraft attendanceDraft=atteDraftService.findDraft(viewDTO.getRegistrationNo(),viewDTO.getCourseId(),viewDTO.getYear(),viewDTO.getSemester(),viewDTO.getUserId());
if(attendanceDraft!=null){
attendanceDraft.setStatus(true);
atteDraftService.save(attendanceDraft);
//atteDraftService.delete(attendanceDraft);
tempResultRepository.save(tempResultUser);
return tempResultUser;
}
else{
throw new CustomException("No draft available");
}
}
}
The exception class
#ResponseStatus(code= HttpStatus.NOT_FOUND)
public class CustomException extends RuntimeException {
public CustomException(String message){
super(message);
}
}
The terminal in the intellij prints "No draft available ". But i want it not as an internal server error.
Can some one tell me how i should be handling these errors please?
I tried using the #RestControllerAdvice
#RestControllerAdvice
public class WebRestControllerAdvice {
#ExceptionHandler(CustomException.class)
public ResponseMsg handleNotFoundException(CustomException ex) {
ResponseMsg responseMsg = new ResponseMsg(ex.getMessage());
return responseMsg;
}
}
And this is my response message class
public class ResponseMsg {
private String message;
//getters and setters
}
This is another simple request in the application
#RequestMapping(value = "/user/view",method = RequestMethod.POST)
public ResponseEntity<?> getUser(#RequestBody UserDTO userDTO) throws CustomException{
User user=userService.findByUsername(userDTO.getUsername());
if(user!=null){
return ResponseEntity.ok(user);
}
//
throw new CustomException("User not found");
}
But still the custom exception is not thrown. The response body is empty. but intellij says "user not found" and postman returns the status code 500.
Spring boot has a very convenient way to handle exceptions in any layer of your application which is defining a #ControllerAdvice bean. Then you can throw any type of exception in your code and it will be "captured" on this class.
After this you can handle and return whatever your app needs to return.
By the way, you can return your custom object and it will be parsed to json automatically.
Documentation: https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/
Sample code:
#ControllerAdvice
public class ErrorHandler {
#ExceptionHandler(BadRequestException.class)
#ResponseStatus(HttpStatus.BAD_REQUEST)
#ResponseBody
public Object processValidationError(BadRequestException ex) {
//return whatever you need to return in your API
}
}
I am working with spring boot with a h2 database. I would like to return a 201 message when the register is inserted succesfully and a 400 when is duplicated. I am using ResponseEntity to achieve this, fot example , the next is my create method from the Service:
#Override
public ResponseEntity<Object> createEvent(EventDTO eventDTO) {
if (eventRepository.findOne(eventDTO.getId()) != null) {
//THis is a test, I am looking for the correct message
return new ResponseEntity(HttpStatus.IM_USED);
}
Actor actor = actorService.createActor(eventDTO.getActor());
Repo repo = repoService.createRepo(eventDTO.getRepo());
Event event = new Event(eventDTO.getId(), eventDTO.getType(), actor, repo, createdAt(eventDTO));
eventRepository.save(event);
return new ResponseEntity(HttpStatus.CREATED);
}
This is my controller:
#PostMapping(value = "/events")
public ResponseEntity addEvent(#RequestBody EventDTO body) {
return eventService.createEvent(body);
}
But I'm not getting any message in the browser, I am doing different tests with postman and when I consult for all the events, the result is correct, but each time that I make a post I dont get any message in the browser, I am not pretty sure what is the cause of this issue. Any ideas?
The ideal way to send Response to the client is to create DTO/DAO with ResponseEntity in Controller
Controller.java
#PostMapping("/test")
public ResponseEntity<Object> testApi(#RequestBody User user)
{
System.out.println("User: "+user.toString());
return assetService.testApi(user);
}
Service.java
public ResponseEntity testApi(User user) {
if(user.getId()==1)
return new ResponseEntity("Created",HttpStatus.CREATED);
else
return new ResponseEntity("Used",HttpStatus.IM_USED);
// for BAD_REQUEST(400) return new ResponseEntity("Bad Request",HttpStatus.BAD_REQUEST);
}
Tested using Postman
Status 201 Created
Status 226 IM Used
Okay, I really don't feel good that service sending the ResponseEntity but not Controller.You could use #ResponseStatus and ExceptionHandler classes for these cases, like below.
Create a class in exception package
GlobalExceptionHandler.java
#ControllerAdvice
public class GlobalExceptionHandler {
#ResponseStatus(HttpStatus.BAD_REQUEST)
#ExceptionHandler(DataIntegrityViolationException.class) // NOTE : You could create a custom exception class to handle duplications
public void handleConflict() {
}
}
Controller.java
#PostMapping(value = "/events")
#ResponseStatus(HttpStatus.CREATED) // You don't have to return any object this will take care of the status
public void addEvent(#RequestBody EventDTO body) {
eventService.createEvent(body);
}
Now changing the service would look like,
Service.java
#Override
public void createEvent(EventDTO eventDTO) { // No need to return
if (eventRepository.findOne(eventDTO.getId()) != null) {
throw new DataIntegrityViolationException("Already exists"); // you have to throw the same exception which you have marked in Handler class
}
Actor actor = actorService.createActor(eventDTO.getActor());
Repo repo = repoService.createRepo(eventDTO.getRepo());
Event event = new Event(eventDTO.getId(), eventDTO.getType(), actor, repo, createdAt(eventDTO));
eventRepository.save(event);
}
I am using Spring Boot for a simple REST API and would like to return a correct HTTP statuscode if something fails.
#RequestMapping(value="/rawdata/", method = RequestMethod.PUT)
#ResponseBody
#ResponseStatus( HttpStatus.OK )
public RestModel create(#RequestBody String data) {
// code ommitted..
// how do i return a correct status code if something fails?
}
Being new to Spring and Spring Boot, the basic question is how do i return different status codes when something is ok or fails?
There are several options you can use. Quite good way is to use exceptions and class for handling called #ControllerAdvice:
#ControllerAdvice
class GlobalControllerExceptionHandler {
#ResponseStatus(HttpStatus.CONFLICT) // 409
#ExceptionHandler(DataIntegrityViolationException.class)
public void handleConflict() {
// Nothing to do
}
}
Also you can pass HttpServletResponse to controller method and just set response code:
public RestModel create(#RequestBody String data, HttpServletResponse response) {
// response committed...
response.setStatus(HttpServletResponse.SC_ACCEPTED);
}
Please refer to the this great blog post for details: Exception Handling in Spring MVC
NOTE
In Spring MVC using #ResponseBody annotation is redundant - it's already included in #RestController annotation.
One of the way to do this is you can use ResponseEntity as a return object.
#RequestMapping(value="/rawdata/", method = RequestMethod.PUT)
public ResponseEntity<?> create(#RequestBody String data) {
if(everything_fine) {
return new ResponseEntity<>(RestModel, HttpStatus.OK);
} else {
return new ResponseEntity<>(null, HttpStatus.INTERNAL_SERVER_ERROR);
}
}
A nice way is to use Spring's ResponseStatusException
Rather than returning a ResponseEntityor similar you simply throw the ResponseStatusException from the controller with an HttpStatus and cause, for example:
throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "Cause description here");
This results in a response to the client containing the HTTP status:
{
"timestamp": "2020-07-09T04:43:04.695+0000",
"status": 400,
"error": "Bad Request",
"message": "Cause description here",
"path": "/test-api/v1/search"
}
Note: HttpStatus provides many different status codes for your convenience.
In case you want to return a custom defined status code, you can use the ResponseEntity as here:
#RequestMapping(value="/rawdata/", method = RequestMethod.PUT)
public ResponseEntity<?> create(#RequestBody String data) {
int customHttpStatusValue = 499;
Foo foo = bar();
return ResponseEntity.status(customHttpStatusValue).body(foo);
}
The CustomHttpStatusValue could be any integer within or outside of standard HTTP Status Codes.
Try this code:
#RequestMapping(value = "/validate", method = RequestMethod.GET, produces = "application/json")
public ResponseEntity<ErrorBean> validateUser(#QueryParam("jsonInput") final String jsonInput) {
int numberHTTPDesired = 400;
ErrorBean responseBean = new ErrorBean();
responseBean.setError("ERROR");
responseBean.setMensaje("Error in validation!");
return new ResponseEntity<ErrorBean>(responseBean, HttpStatus.valueOf(numberHTTPDesired));
}
There are different ways to return status code,
1 : RestController class should extends BaseRest class, in BaseRest class we can handle exception and return expected error codes.
for example :
#RestController
#RequestMapping
class RestController extends BaseRest{
}
#ControllerAdvice
public class BaseRest {
#ExceptionHandler({Exception.class,...})
#ResponseStatus(value=HttpStatus.INTERNAL_SERVER_ERROR)
public ErrorModel genericError(HttpServletRequest request,
HttpServletResponse response, Exception exception) {
ErrorModel error = new ErrorModel();
resource.addError("error code", exception.getLocalizedMessage());
return error;
}
I think the easiest way is to make return type of your method as
ResponseEntity<WHATEVER YOU WANT TO RETURN>
and for sending any status code, just add return statement as
return ResponseEntity.status(HTTP STATUS).build();
For example, if you want to return a list of books,
public ResponseEntity<List<books>> getBooks(){
List<books> list = this.bookService.getAllBooks();
if(list.size() <= 0)
return ResponseEntity.status(HttpStatus.NOT_FOUND).build();
else
return ResponseEntity.of(Optional.of(list));
}
If I handle exceptions with #ControllerAdvice and #ExceptionHandler How can I show error message to user in the same View. For ex. suppose user in "customer/new" view. and invoke "save" action, then it will go to controller and I call methods service layer. but if internal exception occurred in service layer, I want to show error message on same "customer/new" View.
I have written a separate class to handle exceptions as follow.
#ControllerAdvice
public class DefaultControllerHandler {
#ExceptionHandler({MyProjectException.class, DataAccessException.class})
public ResponseEntity<String> handleInternalErrorException(Exception e) {
logger.error(e.getMessage(), e);
return new ResponseEntity<String>(e.getMessage(), HttpStatus.INTERNAL_SERVER_ERROR);
}
}
Appreciate your ideas.
Thanks!
You can use flash redirect attributes.
#RequestMapping(value = "/administrator/users", method = RequestMethod.POST)
public String adminAddUser(#ModelAttribute("user") #Valid User user, BindingResult bindingResult, Model model, RedirectAttributes redirectAttrs) {
String redirectUrl = "/administrator/users";
try {
userService.save(user);
} catch (YourServiceException e) {
redirectAttrs.addFlashAttribute("errorMessage", "error occured: " + e.getMessage());
redirectAttrs.addFlashAttribute("userObject", user);
redirectUrl = "/administrator/users?form"; // If error - return to same view
}
return "redirect:" + redirectUrl;
}
#RequestMapping(value = "/administrator/users", params = "form", method = RequestMethod.GET, produces = "text/html")
public String adminUsersList(#ModelAttribute("errorMessage") final String errorMessage, #ModelAttribute("userObject") final User user Model model) {
if(user == null) {
user = new User();
}
model.addAttribute("user", user);
if(errorMessage != null) {
model.addAttribure("errorMessage", errorMessage);
}
return "administrator/users/create";
}
In that case you must have section on your users.jsp page to show errorMessaage. Something like this:
<c:if test="${not empty errorMessage}">${errorMessage}</c:if>
If you can fetch url path and redirectAttributes from controller method - you can do this through #ControllerAdvice
The only solution I can think about is make your call to the service layer AJAX and then redirect only if there are no errors, if not display the error message.
It might look something like this in your Javascript file
$("#someButton").click(function(){
//make your ajax call
if (noError){
window.location.href = contextPath+"/somePath";
}
else{
//display your error message
}
});
I need to do some validations on the login form before calling the authenticationManager for authentication. Have been able to achieve it with help from one existing post - How to make extra validation in Spring Security login form?
Could someone please suggest me whether I am following the correct approach or missing out something? Particularly, I was not very clear as to how to show the error messages.
In the filter I use validator to perform validations on the login field and in case there are errors, I throw an Exception (which extends AuthenticationException) and encapsulate the Errors object. A getErrors() method is provided to the exception class to retrieve the errors.
Since in case of any authentication exception, the failure handler stores the exception in the session, so in my controller, I check for the exception stored in the session and if the exception is there, fill the binding result with the errors object retrieved from the my custom exception (after checking runtime instance of AuthenticationException)
The following are my code snaps -
LoginFilter class
public class UsernamePasswordLoginAuthenticationFilter extends
UsernamePasswordAuthenticationFilter {
#Autowired
private Validator loginValidator;
/* (non-Javadoc)
* #see org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter#attemptAuthentication(javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse)
*/
#Override
public Authentication attemptAuthentication(HttpServletRequest request,
HttpServletResponse response) throws AuthenticationException {
Login login = new Login();
login.setUserId(request.getParameter("userId"));
login.setPassword(request.getParameter("password"));
Errors errors = new BeanPropertyBindingResult(login, "login");
loginValidator.validate(login, errors);
if(errors.hasErrors()) {
throw new LoginAuthenticationValidationException("Authentication Validation Failure", errors);
}
return super.attemptAuthentication(request, response);
}
}
Controller
#Controller
public class LoginController {
#RequestMapping(value="/login", method = RequestMethod.GET)
public String loginPage(#ModelAttribute("login") Login login, BindingResult result, HttpServletRequest request) {
AuthenticationException excp = (AuthenticationException)
request.getSession().getAttribute(WebAttributes.AUTHENTICATION_EXCEPTION);
if(excp != null) {
if (excp instanceof LoginAuthenticationValidationException) {
LoginAuthenticationValidationException loginExcp = (LoginAuthenticationValidationException) excp;
result.addAllErrors(loginExcp.getErrors());
}
}
return "login";
}
#ModelAttribute
public void initializeForm(ModelMap map) {
map.put("login", new Login());
}
This part in the controller to check for the instance of the Exception and then taking out the Errors object, does not look a clean approach. I am not sure whether this is the only way to handle it or someone has approached it in any other way? Please provide your suggestions.
Thanks!
#RequestMapping(value = "/login", method = RequestMethod.GET)
public ModelAndView signInPage(
#RequestParam(value = "error", required = false) String error,
#RequestParam(value = "logout", required = false) String logout) {
ModelAndView mav = new ModelAndView();
//Initially when you hit on login url then error and logout both null
if (error != null) {
mav.addObject("error", "Invalid username and password!");
}
if (logout != null) {
mav.addObject("msg", "You've been logged out successfully.");
}
mav.setViewName("login/login.jsp");
}
Now if in case login become unsuccessfull then it will again hit this url with error append in its url as in spring security file you set the failure url.
Spring security file: -authentication-failure-url="/login?error=1"
Then your URl become url/login?error=1
Then automatically signInPage method will call and with some error value.Now error is not null and you can set any string corresponding to url and we can show on jsp using these following tags:-
<c:if test="${not empty error}">
<div class="error">${error}</div>
</c:if>