I want to create a custom business exception:
public class BusinessException extends RuntimeException {
private static final long serialVersionUID = 1L;
public BusinessException(String msg) {
super(msg);
}
public BusinessException(String msg, Object[] params) {
//Not sure how to pass params to #ExceptionHandler
super(msg);
}
}
and use it in my spring mvc rest controller:
#RequestMapping(value = "/{code}", method = RequestMethod.GET)
public #ResponseBody
String getState(#PathVariable String code) throws Exception {
String result;
if (code.equals("KL")) {
result = "Kerala";
} else {
throw new BusinessException("NotAValidStateCode",new Object[]{code});
}
return result;
}
I am handling all the businessException using common exception handler:
#ControllerAdvice
public class RestErrorHandler {
private static final Logger LOGGER = LoggerFactory
.getLogger(RestErrorHandler.class);
#Autowired
private MessageSource messageSource;
#ExceptionHandler(BusinessException.class)
#ResponseStatus(HttpStatus.BAD_REQUEST)
#ResponseBody
public String handleException(
Exception ex) {
Object[] args=null; // Not sure how do I get the args from custom BusinessException
String message = messageSource.getMessage(ex.getLocalizedMessage(),
args, LocaleContextHolder.getLocale());
LOGGER.debug("Inside Handle Exception:" + message);
return message;
}
}
Now my problem is , I want to read the message text from messages property file where some of the keys are expecting run time bind variables e.g.
NotAValidStateCode= Not a valid state code ({0})
I am not sure how do I pass these arguments to handleException Method of RestErrorHandler.
This is simple as you have already done all the "heavy lifting":
public class BusinessException extends RuntimeException {
private static final long serialVersionUID = 1L;
private final Object[] params;
public BusinessException(String msg, Object[] params) {
super(msg);
this.params = params;
}
public Object[] getParams() {
return params;
}
}
#ExceptionHandler
#ResponseStatus(HttpStatus.BAD_REQUEST)
#ResponseBody
public String handleException(BusinessException ex) {
String message = messageSource.getMessage(ex.getMessage(),
ex.getParams(), LocaleContextHolder.getLocale());
LOGGER.debug("Inside Handle Exception:" + message);
return message;
}
I would recommend encapsulating everything that you need to create the error message in BusinessException. You're already passing in code as part of the array of params. Either expose that whole array with a getParams() method, or (and this is the approach I would take) add a code field and getCode() method to BusinessException and add a code argument to BusinessException's constructor. You can then update handleException to take a BusinessException rather than an Exception and use getCode() when creating the arguments used to create the message.
Related
In Spring boot.
I want to do field validation and return an error if the input does not exist in the database.
I am trying to write the custom annotation for multiple input fields.
The controller is as below
#RestController
#Api(description = "The Mailer controller which provides send email functionality")
#Validated
public class SendMailController {
#Autowired
public SendMailService sendemailService;
org.slf4j.Logger logger = LoggerFactory.getLogger(SendMailService.class);
#RequestMapping(method = RequestMethod.POST, value = "/sendMail", consumes = {MediaType.TEXT_XML_VALUE, MediaType.APPLICATION_JSON_VALUE}, produces = {"text/xml", "application/json"})
#ResponseBody
#Async(value = "threadPoolTaskExecutor")
#ApiOperation("The main service operation which sends one mail to one or may recipient as per the configurations in the request body")
public Future<SendMailResult> sendMail(#ApiParam("Contains the mail content and configurations to be used for sending mail") #Valid #RequestBody MailMessage message) throws InterruptedException {
SendMailResult results = new SendMailResult();
try {
sendemailService.sendMessages(message);
long txnid = sendemailService.createAudit (message);
results.setTxnid (txnid);
results.setStatus("SUCCESS");
} catch(MessagingException | EmailServiceException e) {
logger.error("Exception while processing sendMail " + e);
results.setStatus("FAILED");
// TODO Handle error create results
e.printStackTrace();
} catch(Exception e) {
logger.error("Something went wrong " + e);
results.setStatus("FAILED");
// TODO Handle error create results
e.printStackTrace();
}
return new AsyncResult<SendMailResult>(results);
}
}
one DTO that is mapped with request
public class MailContext {
#NotNull
private String clientId;
#NotNull
private String consumer;
public int getClientId() {
return Integer.parseInt(clientId);
}
public void setClientId(String clientId) {
this.clientId = clientId;
}
public String toJson() throws JsonProcessingException {
ObjectMapper mapper = new ObjectMapper();
String writeValueAsString = mapper.writeValueAsString(this);
return writeValueAsString;
}
}
Request xml
<mailMessage>
<mailContext>
<clientId>10018</clientId>
<consumer>1</consumer>
</mailContext>
</mailMessage>
I want to write a custom annotation to validate client which exists in the database (table client_tbl) if provided in the request.
consumer: is present in database table cunsumer_tbl
if these not present in database send error message else call service method.
Please suggest how to write such custom annotation with the error.
I know another way to validate this.
Inside your controller, you can register a validator.
#InitBinder
public void setup(WebDataBinder webDataBinder) {
webDataBinder.addValidators(dtoValidator);
}
Where dtoValidator is an instance of Spring Bean, for example, which must implements org.springframework.validation.Validator.
So, you just have to implement two methods: supports() and validate(Object target, Errors errors);
Inside supports() method you can do whatever you want to decide whether the object should be validated by this validator or not. (for example, you can create an interface WithClientIdDto and if the tested object isAssignableFrom() this interface you can do this validation. Or you can check your custom annotation is presented on any field using reflection)
For example: (AuthDtoValidator.class)
#Override
public boolean supports(Class<?> clazz) {
return AuthDto.class.isAssignableFrom(clazz);
}
#Override
public void validate(Object target, Errors errors) {
final AuthDto dto = (AuthDto) target;
final String phone = dto.getPhone();
if (StringUtils.isEmpty(phone) && StringUtils.isEmpty(dto.getEmail())) {
errors.rejectValue("email", "", "The phone or the email should be defined!");
errors.rejectValue("phone", "", "The phone or the email should be defined!");
}
if (!StringUtils.isEmpty(phone)) {
validatePhone(errors, phone);
}
}
UPDATE:
You can do that.
Create an annotation
for example:
#Target({ FIELD })
#Retention(RUNTIME)
#Constraint(validatedBy = ClientIdValidator.class)
#Documented
public #interface ClientId {
String message() default "{some msg}";
Class<?>[] groups() default { };
Class<? extends Payload>[] payload() default { };
}
and implement this validator:
class ClientIdValidator implements ConstraintValidator<ClientId, Long> {
#Override
public boolean isValid(Long value, ConstraintValidatorContext context) {
//validation logc
}
}
More details you can find here: https://reflectoring.io/bean-validation-with-spring-boot/
I would like my API to return errorMessage when the request lacks of required parameters. For example let's say there is a method:
#GET
#Path("/{foo}")
public Response doSth(#PathParam("foo") String foo, #NotNull #QueryParam("bar") String bar, #NotNull #QueryParam("baz") String baz)
where #NotNull is from package javax.validation.constraints.
I wrote an exception mapper which looks like this:
#Provider
public class Mapper extends ExceptionMapper<ConstraintViolationException> {
#Override
public Response toResponse(ConstraintViolationException) {
Iterator<ConstraintViolation<?>> it= exception.getConstraintViolations().iterator();
StringBuilder sb = new StringBuilder();
while(it.hasNext()) {
ConstraintViolation<?> next = it.next();
sb.append(next.getPropertyPath().toString()).append(" is null");
}
// create errorMessage entity and return it with apropriate status
}
but next.getPropertyPath().toString() returns string in format method_name.arg_no, f.e. fooBar.arg1 is null
I'd like to receive output fooBar.baz is null or simply baz is null.
My solution was to include -parameters parameter for javac but to no avail.
Probably I could somehow achieve it with the use of filters:
public class Filter implements ContainerResponseFilter {
#Override
public void filter(ContainerRequestContext requestContext, ContainerResponseContext responseContext) {
UriInfo uriInfo = requestContext.getUriInfo();
UriRoutingContext routingContext = (UriRoutingContext) uriInfo;
Throwable mappedThrowable = routingContext.getMappedThrowable();
if (mappedThrowable != null) {
Method resourceMethod = routingContext.getResourceMethod();
Parameter[] parameters = resourceMethod.getParameters();
// somehow transfer these parameters to exceptionMapper (?)
}
}
}
The only problem with the above idea is that ExeptionMapper is executed first, then the filter is executed. Also I have no idea how could I possibly transfer errorMessage between ExceptionMapper and Filter. Maybe there is another way?
You can inject ResourceInfo into the exception mapper to get the resource method.
#Provider
public class Mapper extends ExceptionMapper<ConstraintViolationException> {
#Context
private ResourceInfo resourceInfo;
#Override
public Response toResponse(ConstraintViolationException ex) {
Method resourceMethod = resourceInfo.getResourceMethod();
Parameter[] parameters = resourceMethod.getParameters();
}
}
I am trying to handle all Types of exceptions using #ExceptionHandler(Exception.class). But it's not handling all types of exception.
When I am trying to access wrong HTTP method from postman/ browser I am not getting any response blank page is coming.
Can please any one tell me why I am not getting any response or tell me if I am doing something wrong in my code?
#Order(Ordered.HIGHEST_PRECEDENCE)
#ControllerAdvice
public class RestExceptionHandler extends ResponseEntityExceptionHandler {
#ExceptionHandler(Exception.class)
public ResponseEntity<ExceptionMessage> handleAllExceptionMethod(Exception ex,WebRequest requset,HttpServletResponse res) {
ExceptionMessage exceptionMessageObj = new ExceptionMessage();
exceptionMessageObj.setStatus(res.getStatus());
exceptionMessageObj.setError(ex.getLocalizedMessage());
exceptionMessageObj.setException(ex.getClass().getCanonicalName());
exceptionMessageObj.setPath(((ServletWebRequest) requset).getRequest().getServletPath());
return new ResponseEntity<ExceptionMessage>(exceptionMessageObj, new HttpHeaders(), HttpStatus.INTERNAL_SERVER_ERROR);
}
Either override ResponseEntityExceptionHandler#handleExceptionInternal()or don't extend ResponseEntityExceptionHandler.
#Order(Ordered.HIGHEST_PRECEDENCE) on a #ControllerAdvice should work before ResponseEntityExceptionHandler is invoked as per this answer which suggests that Spring Framework 4.3.7 is needed.
This will handle the exceptions raised from within the controller method.
If you send a request for which there is no mapping the controller method will not be invoked at all thus the #ExceptionHandler will be obsolete in that case.
Maybe this article on creating custom handlers may help: article
Using RequestMapping you can create different responses for every Http code. In this example I show how to control errors and give a response accordingly.
This is the RestController with the service specification
#RestController
public class User {
#RequestMapping(value="/myapp/user/{id}", method = RequestMethod.GET)
public ResponseEntity<String> getId(#PathVariable int id){
if(id>10)
throw new UserNotFoundException("User not found");
return ResponseEntity.ok("" + id);
}
#ExceptionHandler({UserNotFoundException.class})
public ResponseEntity<ErrorResponse> notFound(UserNotFoundException ex){
return new ResponseEntity<ErrorResponse>(
new ErrorResponse(ex.getMessage(), 404, "The user was not found") , HttpStatus.NOT_FOUND);
}
}
Within the getId method there is a little logic, if the customerId < 10 It should response the Customer Id as part of the body message but an Exception should be thrown when the customer is bigger than 10 in this case the service should response with an ErrorResponse.
public class ErrorResponse {
private String message;
private int code;
private String moreInfo;
public ErrorResponse(String message, int code, String moreInfo) {
super();
this.message = message;
this.code = code;
this.moreInfo = moreInfo;
}
public String getMessage() {
return message;
}
public int getCode() {
return code;
}
public String getMoreInfo() {
return moreInfo;
}
}
And finally I'm using an specific Exception for a "Not Found" error
public class UserNotFoundException extends RuntimeException {
public UserNotFoundException(String message) {
super(message);
}
}
I have a Request Mapping -
#RequestMapping("/fetchErrorMessages")
public #ResponseBody int fetchErrorMessages(#RequestParam("startTime") String startTime,#RequestParam("endTime") String endTime) throws Exception
{
if(SanityChecker.checkDateSanity(startTime)&&SanityChecker.checkDateSanity(endTime))
{
return 0;
}
else
{
throw new NotFoundException("Datetime is invalid");
}
}
If the startTime and endTime are invalid, I want to throw a 500 error but return the exception string in JSON. However, I get a HTML Page instead saying
Whitelabel Error Page
This application has no explicit mapping for /error, so you are seeing this as a fallback.
Wed Dec 20 10:49:37 IST 2017
There was an unexpected error (type=Internal Server Error, status=500).
Datetime is invalid
I instead wanted to return 500 with a JSON
{"error":"Date time format is invalid"}
How do I go about this?
Suppose you have a custom Exception class NotFoundException and its implementations something like this:
public class NotFoundException extends Exception {
private int errorCode;
private String errorMessage;
public NotFoundException(Throwable throwable) {
super(throwable);
}
public NotFoundException(String msg, Throwable throwable) {
super(msg, throwable);
}
public NotFoundException(String msg) {
super(msg);
}
public NotFoundException(String message, int errorCode) {
super();
this.errorCode = errorCode;
this.errorMessage = message;
}
public void setErrorCode(int errorCode) {
this.errorCode = errorCode;
}
public int getErrorCode() {
return errorCode;
}
public void setErrorMessage(String errorMessage) {
this.errorMessage = errorMessage;
}
public String getErrorMessage() {
return errorMessage;
}
#Override
public String toString() {
return this.errorCode + " : " + this.getErrorMessage();
}
}
Now you want to throw some exception from controller. If you throw a exception then you must catch it from a standard Error Handler class, say for example in spring they provide #ControllerAdvice annotation to apply to make a class Standard Error Handler. When it is applied to a class then this spring component (I mean the class you annotated) can catch any exception thrown from controller. But We need to map exception class with proper method. So we defined a method with your exception NotFoundException handler something like below.
#ControllerAdvice
public class RestErrorHandler {
#ExceptionHandler(NotFoundException.class)
#ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
#ResponseBody
public Object processValidationError(NotFoundException ex) {
String result = ex.getErrorMessage();
System.out.println("###########"+result);
return ex;
}
}
You want to sent http status to internal server error(500), so here we used #ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR). Since you used Spring-boot so you do not need to make a json string except a simple annotation #ResponseBody can do that for you automagically.
Create a custom exception.
public class SecurityException extends RuntimeException {
private static final long serialVersionUID = -7806029002430564887L;
private String message;
public SecurityException() {
}
public SecurityException(String message) {
this.message = message;
}
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
}
Create a custom response entity.
public class SecurityResponse {
private String error;
public SecurityResponse() {
}
public SecurityResponse(String error) {
this.error = error;
}
public String getError() {
return error;
}
public void setError(String error) {
this.error = error;
}
}
Create a ControllerAdvice with ExceptionHandler for custom exception, it will handle the custom exception, populate and return the custom response as below.
#ControllerAdvice
public class SecurityControllerAdvice {
#ExceptionHandler(SecurityException.class)
#ResponseBody
#ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
public SecurityResponse handleSecurityException(SecurityException se) {
SecurityResponse response = new SecurityResponse(se.getMessage());
return response;
}
}
Throw the custom exception based on your condition.
throw new SecurityException("Date time format is invalid");
Now run and test you app. E.G. :
you can create NotFoundException class with #ResponseStatus annotation like below:
#ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
public class NotFoundException extends RuntimeException {
public NotFoundException() {
}
public NotFoundException(String message) {
super(message);
}
}
Javax has a interface name as ExceptionMapper. Please refer the below code snippet, For every RuntimeException in your application it will map it to a Json Response entity.
public class RuntimeExceptionMapper implements ExceptionMapper <RuntimeException> {
#Override
public Response toResponse(RuntimeException exception) {
ErrorResponse errorResponse = new ErrorResponse();
errorResponse.setMessage(exception.getMessage);
if (exception== null) {
logger.error("Exception Details Not found");
} else {
return Response.status(Status.INTERNAL_SERVER_ERROR)
.entity(errorResponse )
.type(MediaType.APPLICATION_JSON)
.header("trace-id", "1234").build();
}
}
}
This is how I did it in my application:
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
#ControllerAdvice
public class ExceptionHandlingControllerAdvice {
#ExceptionHandler(ExecutionRestrictionViolationException.class)
public ResponseEntity<String> handleExecutionRestrictionViolationException(ExecutionRestrictionViolationException ex) {
return response("Invalid Query", ex.getMessage(), HttpStatus.UNPROCESSABLE_ENTITY);
}
private static String createJson(String message, String reason) {
return "{\"error\" : \"" + message + "\"," +
"\"reason\" : \"" + reason + "\"}";
}
private static ResponseEntity<String> response(String message,
String reason,
HttpStatus httpStatus) {
String json = createJson(message, reason);
return new ResponseEntity<>(json, httpStatus);
}
}
Explanation:
You create a controller Advice, mark it with a special annotation and define just like any other bean (in my case it was a java configuration, but it doesn't really matter)
For each Exception you would like to handle like this - define a handler that will generate a response in a format you want
There is a static method createJson - you can use a different way, it also doesn't matter really.
Now this is only one way to work (its available in more recent spring boot versions) - but there are others:
All the methods I'm aware of (and even more) are listed here.
Spring provides a few ways to do this, some more sensible than others depending on your situation.
(Great tutorial here on several options. https://www.baeldung.com/spring-exceptions-json)
My favorite is this one because I want to send back a proper error message and an appropriate http response without creating a superclass or creating helper methods in a utility class or copying boilerplate everywhere.
If you want to inform the caller that the event caused an error (and in proper JSON), use Spring's ResponseStatusException. It gives you access to the httpReponse object so you can also send back a response other than 'ok'.
It wants an exception as one of it's parameters. For one of my scenarios I wanted to inform the caller that they were trying to register a user that already existed. Typically, looking up a user isn't supposed to throw an exception but in this case I created my own exception and I throw it back to the caller in a ResponseStatusException like so:
#PostMapping("/register")
public ResponseEntity register(#RequestBody AccountUserDto user) {
UserDetails userExists = userDetailsService.loadUserByEmail(user.getEmail());
if (userExists != null) {
UserExistsException exc = new UserExistsException("Error: Email address " + user.getEmail() + " is already in use.");
throw new ResponseStatusException(
HttpStatus.BAD_REQUEST, "User Exists", exc);
}
....(fall through and create user)
In a web application I'm working on using Spring 2.5.6.SEC01, I essentially have an Integer field that takes a number to determine which page to scroll to. The requirements changed, and we no longer want to display an error message, but simply ignore the user's input if they enter an invalid number, say "adfadf".
I was reading that you can do that via:
TypeMismatch.property=Some New Error Message
However, after having tried that, we are still getting the original error message:
java.lang.Integer.TypeMismatch=...
I only want to disable this message for that given property. How can I do that? I still want binding to occur automatically, I just don't want to hear about it now.
Walter
According to DefaultMessageCodesResolver
In case of code "typeMismatch", object name "user", field "age"
typeMismatch.user.age
typeMismatch.age
typeMismatch.int
typeMismatch
So you should get (I suppose your commandName is called command and your property is age) Adapt according to your code
typeMismatch.command.age
typeMismatch.age
typeMismatch.java.lang.Integer
typeMismatch
Notice The third code
typeMismatch.java.lang.Integer
It will solve what you want
UPDATE
I have created a Person command class
public class Person implements Serializable {
private Integer age;
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
}
And a person controller
public class PersonController extends SimpleFormController {
public PersonController() {
setCommandClass(Person.class);
setValidator(new Validator() {
public boolean supports(Class clazz) {
return clazz.isAssignableFrom(Person.class);
}
public void validate(Object command, Errors errors) {
rejectIfEmpty(errors, "age", "Age is required");
}
});
}
#Override
protected ModelAndView onSubmit(Object command) throws Exception {
return new ModelAndView();
}
}
Here goes my myMessages.properties (root of the classpath)
typeMismatch.command.age=typeMismatch.command.age
typeMismatch.age=typeMismatch.age
typeMismatch.java.lang.Integer=typeMismatch.java.lang.Integer
typeMismatch=typeMismatch
So, i have done the following test
public class PersonControllerTest {
private PersonController personController;
private MockHttpServletRequest request;
private MessageSource messageSource;
#Before
public void setUp() {
request = new MockHttpServletRequest();
request.setMethod("POST");
personController = new PersonController();
messageSource = new ResourceBundleMessageSource();
((ResourceBundleMessageSource) messageSource).setBasename("myMessages");
}
#Test
public void failureSubmission() throws Exception {
/**
* Ops... a bindException
*
* Age can not be a plain String, It must be a plain Integer
*/
request.addParameter("age", "not a meaningful age");
ModelAndView mav = personController.handleRequest(request, new MockHttpServletResponse());
BindingResult bindException = (BindingResult) mav.getModel().get(BindingResult.MODEL_KEY_PREFIX + "command");
for (Object object : bindException.getAllErrors()) {
if(object instanceof FieldError) {
FieldError fieldError = (FieldError) object;
assertEquals(fieldError.getField(), "age");
/**
* outputs typeMismatch.command.age
*/
System.out.println(messageSource.getMessage((FieldError) object, null));
}
}
}
}
If you want the second one, you must get rid of typeMismatch.command.age key resource bundle
typeMismatch.age=typeMismatch.age
typeMismatch.java.lang.Integer=typeMismatch.java.lang.Integer
typeMismatch=typeMismatch
Or write your own implementation of MessageCodesResolver
public class MyCustomMessageCodesResolver implements MessageCodesResolver {
private DefaultMessageCodesResolver defaultMessageCodesResolver = new DefaultMessageCodesResolver();
public String [] resolveMessageCodes(String errorCode, String objectName) {
if(errorCode.equals("age"))
/**
* Set up your custom message right here
*/
return new String[] {"typeMismatch.age"};
return defaultMessageCodesResolver.resolveMessageCodes(String errorCode, String objectName);
}
public void String[] resolveMessageCodes(String errorCode, String objectName, String field, Class fieldType) {
if(errorCode.equals("age"))
/**
* Set up your custom message right here
*/
return new String[] {"typeMismatch.age"};
return defaultMessageCodesResolver.resolveMessageCodes(String errorCode, String objectName, String field, Class fieldType);
}
}
And set up your PersonController
public class PersonController extends SimpleFormController {
public PersonController() {
setMessageCodesResolver(new MyCustomMessageCodesResolver());
setCommandClass(Person.class);
setValidator(new Validator() {
public boolean supports(Class clazz) {
return clazz.isAssignableFrom(Person.class);
}
public void validate(Object command, Errors errors) {
rejectIfEmpty(errors, "age", "Age is required");
}
});
}
You can register a custom PropertyEditor for that field, which wouldn't fail on type mismatch.
Since this is a Spring MVC application and assuming that it is a simple form, you can set this up in many ways. Can you specify your controller settings? For post request, you can record a suppressed field before the validator is called (assuming you have specified one) or after the validator is called. If you want to do it before validation, you can call [this][2]. After validation, you can call [this][3]
[2]: http://static.springsource.org/spring/docs/2.0.x/api/org/springframework/web/servlet/mvc/BaseCommandController.html#onBind(javax.servlet.http.HttpServletRequest, java.lang.Object, org.springframework.validation.BindException)
[3]: http://static.springsource.org/spring/docs/2.0.x/api/org/springframework/web/servlet/mvc/BaseCommandController.html#onBindAndValidate(javax.servlet.http.HttpServletRequest, java.lang.Object, org.springframework.validation.BindException)