I'm trying to present an actual message with a failed validation of an #Future Bean Validation annotation but have been unsuccessful thus far. Here is my current code:
public class ForecastDateVO {
#Future(message = "The forecast date must be in the future.") // Message desired for display
private Date forecastDate;
public Date getForecastDate() {
return this.forecastDate;
}
public void setForecastDate(final Date forecastDate) {
this.forecastDate = forecastDate;
}
}
where it is put to use like this:
#PUT
#Path("whatever/{projectId}")
public void updateForecastDate(#PathParam("projectId") #NotNull final long projectId,
#Valid final ForecastDateVO vo) {
// Something absolutely amazing
}
Now the validation is actually working with the #Valid tag applied; when I make the rest call with a date in the payload that is not in the future I receive a "400 Bad Request". However, I would like to actually see the error message display in the annotation above.
Is this possible...and if so what's the best way to accomplish it?
Thanks for your time!
Related
I made a GET web service with Spring web flux.
Code is something like that.
#GetMapping("/url/{reportingDate}")
public ResponseEntity<String> webservice(
#PathVariable #DateTimeFormat(pattern = "yyyy-MM-dd") final LocalDate reportingDate) {
// some code
return ResponseEntity.ok("ok");
}
Then, instead of making this call (OK)
request GET 'http://localhost:8080/url/2022-03-10'
I voluntarily make this one
request GET 'http://localhost:8080/url/20220310'
how does it make Spring returns to me "400 bad request" with absolutely no detail in the payload? (i would have expected here some details regarding my bad date format)
Any idea?
i found it.
using this makes the job.
#ExceptionHandler(IllegalArgumentException.class)
#ResponseStatus(code = HttpStatus.BAD_REQUEST)
#ResponseBody
public String illegalArgument(final IllegalArgumentException exception)
{
return exception.getMessage();
}
then, the web service returns to me..
Parse attempt failed for value [20220310]
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
I have a Java Pojo class which is expected as a #RequestBody in my Rest Controller. This Java Pojo class has an Integer field.
When user is calling the Rest API and they pass Integer field value as a Junk String, then Jackson is throwing InvalidFormatException. I instead want to use Javax Validator framework annotation to handle this error and show a meaningful error message in response. Unfortunately Jackson deserialization from JSON to Java happens before Javax validation, therefore my #Digits validation never gets invoked.
Note, #NotNull gets invoked but #Digits is not getting invoked because jackson fails the request much before the call reaches Javax validation layer IMO.
Java Pojo class:
public class Board implements Serializable{
private static final long serialVersionUID = 1L;
#Digits(message = "quantity must be a number", integer = 8, fraction = 0)
private Integer quantity;
public Integer getQuantity() {
return quantity;
}
public void setQuantity(Integer quantity) {
this.quantity= quantity;
}
}
Controller class:
#Controller
#RequestMapping("boards")
public class EnrichController {
#Autowired
private BoardService boardService;
#RequestMapping(value = "", method = RequestMethod.PUT, produces = "application/json;charset=UTF-8")
public #ResponseStatus(value = HttpStatus.NO_CONTENT) #ResponseBody void updateBoard(
#RequestBody #Valid Board board) throws IllegalArgumentException, MalformedURLException {
boardService.updateUserBoard(board);
}
}
User input:
{
"quantity": "abcdef"
}
As you can see I am using Javax validation annotations #Digits & #Valid but no use because Jackson Deserialization fails while parsing the Integer field quantity.
Is there anyway you can help me to solve this situation by handling this use-case using Javax validation annotations? I reckon changing field type to String in POJO class is an expensive effort as we have to do String to Integer conversion everytime I need some business logic on that field, therefore that is not an option for me.
Well in your case the problem is that you're not conforming to the REST API exposed and try to send a string instead of a number for the quantity field. This should never happen either by you or a third party service that uses your API.
Is there anyway you can help me to solve this situation by handling this use-case using Javax validation annotations?
In any case, if you still want to fix the above, a solution will be to change "quantity" in a string type field and and added pattern matching for it
#Pattern(message = "quantity must be a number", regexp="^[0-9]*$")
private String quantity;
I´m beginner in REST API and Spring Boot. I have a doubt about how to handle the different responses a request can have. For example if I post data of a credit card
{
"number": "3434 3434 3434 3434",
"date_expiration": "09-19",
"identification_card": 23232323
}
then in #RestController
#PostMapping("/card")
public ResponseEntity<CreditCard> payCard(#Valid #RequestBody CreditCard creditCard){
CreditCard creC = cardService.registerCard(creditCard);
return new ResponseEntity<>(creC, HttpStatus.OK);
}
In this case I return an object of ResponseEntity.
What happens if the date_expiration is expired or the identification_card doesn't correspond to the client? They are logical validations that are resolved in the service and can trigger different responses. How should I handle them?
Here you are using same object as your Request body and Response body. That is not a standard practice.
You should have separate objects for Request/Response. In request object you should have only the information you need from the user. But in the response object you should have information you want to send in response and also validation information like Error Details which includes error code and error description which you can use to display validation error to the user.
Hope this helps.
Well, if date_expiration is expired or identification_card doesn't behave to the customer, this is a business failure.
I like to represent Business Errors with an HTTP 422 - Unprocessable Entity. See here
You can change the return object from ResponseEntity<CreditCard> to ResponseEntity<Object> if you want to return diferent objects in your controller, although I prefer to use a ExceptionHandler in a ControllerAdvice annotated method if the purpose is to return errors.
As I said, this situation is a business failure (the credit card is expired or doesn't behave to the current user).
Here's an example. Would be something like this:
CardService.java
#Service
public class CardService {
// ..
public CreditCard registerCard(CreditCard card) throws BusinessException {
if(cardDoesntBehaveToUser(card, currentUser()))) // you have to get the current user
throw new BusinessException("This card doesn't behave to the current user");
if(isExpired(card)) // you have to do this logic. this is just an example
throw new BusinessException("The card is expired");
return cardRepository.save(card);
}
}
CardController.java
#PostMapping("/card")
public ResponseEntity<Object> payCard(#Valid#RequestBody CreditCard creditCard) throws BusinessException {
CreditCard creC = cardService.registerCard(creditCard);
return ResponseEntity.ok(creC);
}
BusinessException.java
public class BusinessException extends Exception {
private BusinessError error;
public BusinessError(String reason) {
this.error = new BusinessError(reason, new Date());
}
// getters and setters..
}
BusinessError.java
public class BusinessError {
private Date timestamp
private String reason;
public BusinessError(String Reason, Date timestamp) {
this.timestamp = timestamp;
this.reason = reason;
}
// getters and setters..
}
MyExceptionHandler.java
#ControllerAdvice
public class MyExceptionHandler extends ResponseEntityExceptionHandler {
// .. other handlers..
#ExceptionHandler({ BusinessException.class })
public ResponseEntity<Object> handleBusinessException(BusinessException ex) {
return ResponseEntity.status(HttpStatus.UNPROCESSABLE_ENTITY).body(ex.getError());
}
}
If the credit card is expired, the JSON will be rendered as:
{
"timestamp": "2019-10-29T00:00:00+00:00",
"reason": "The card is expired"
}
I offer a REST webservice using spring #RestController.
How should in general invalid parameter content be handled? I tried throwing a custom exception, but that will lead to a HTTP 500 error on the client side and expose the stacktrace thereby.
Probably that is not the right way. But how should just simple error messages be returned? (the webservice will not be accessed manually by users. Just by other services connecting to the rest controller).
Im using jersey and this is a simple example that will use the hibernate bean validation framework to validate your beans. This is a work in progress but you should can see how it will work very simply.
#Path("customers")
public class CustomerResource {
#PUT
public Response createCustomer(Customer customer) {
BeanValidator.validate(customer);
final String rialtoId = customerProvider.createCustomer(customer);
return Response.ok(rialtoId).build();
}
}
Here is a generic class that I created that handles the bean validation.
public class BeanValidator {
/**
* Used to validate an order request and all the attached objects that
* support validation.
*
* #param request
* #throws ConstraintViolationException
*/
public static <T> void validate(T request) throws ConstraintViolationException {
ValidatorFactory factory = Validation.buildDefaultValidatorFactory();
Validator validator = factory.getValidator();
Set<ConstraintViolation<T>> constraintViolations = validator.validate(request);
if (constraintViolations.size() > 0) {
throw new ConstraintViolationException(new HashSet<ConstraintViolation<?>>(constraintViolations));
}
}
}
#XmlRootElement
public class Customer {
#NotNull(message = "spCustomerID1 is a required field")
#Size(max = 60, message = "spCustomerID1 has a max length of 60 characters")
private String spCustomerID1;
#Size(max = 60, message = "spCustomerID2 has a max length of 60 characters")
private String spCustomerID2;
#Size(max = 60, message = "spCustomerID3 has a max length of 60 characters")
private String spCustomerID3;
#NotNull(message = "customerName is a required field")
#Size(max = 60)
private String customerName;
#Valid
#NotNull(message = "customerAddress is a required field")
private PostalAddress customerAddress;
#Valid
#NotNull(message = "customerContact is a required field")
private ContactInfo customerContact;
#Valid
#NotNull(message = "technicalContact is a required field")
private ContactInfo technicalContact;
... / Getters and Setters
}
Then here is a simple ExceptionMapper that will support constructing a simple response to be sent back to the client. Notice that it will set the Response type to a 400 BAD_REQUEST instead of a 500+ Server Side error.
public class ConstraintViolationExceptionMapper implements ExceptionMapper<ConstraintViolationException> {
public Response toResponse(ConstraintViolationException exception) {
final StringBuilder strBuilder = new StringBuilder();
for (ConstraintViolation<?> cv : exception.getConstraintViolations()) {
strBuilder.append(cv.getPropertyPath().toString() + " " + cv.getMessage());
}
RestResponse responseEntity = RestResponse.responseCode(ResponseCode.CONSTRAINT_VIOLATION).setResponseMessage(strBuilder.toString()).build();
return Response.status(Response.Status.BAD_REQUEST).entity(responseEntity).build();
}
}
This code hasn't been tested yet but it might help to get some ideas of how to do the validation. This is a pretty straight forward way to do rest service validation in my opinion and allows you to report exact variable paths along with customized error messages for each field.
You should validate your parameters at the very outmost layer of your application before it gets handed off inside your domain. At this point you're still in the HTTP layer so can take the appropriate action which is to return a 400 BAD REQUEST status.
Within that you have complete control over how to relay this information to your users (or other services). Plain text is fine if you're just logging it, or design your own Json/Xml payload describing the error.
if i understand you well,
then , generally i think it's good to have a key in each json response (or even if your response is XML), that indecates the status of the process. this field could be called status.
so each and every response you send back should have this status field, and it's value should indicate what happens while processing, and what should the caller expect in the response.
the value could be a number, or a text message, some constant-like message
also you can add another field, message that contains some text-desc of the status code.
now you have to make a list of possible statues that your service may send back.
ex:
status: 0000
message: success
status: 0001
message: invalid_params
status: 0002
message: invalid_param_value
status: 0003
message: missing_param,
:
:
etc
so your json response will contain those fields always. among the other data supposed to be returned.
now it's the clients duty to handle those responses.
JSON example:
{
"status":"0000",
"message":"success",
"images":[ ... ]
}
{
"status":"0003",
"message":"missing_param"
}
as you notice in case of non 0000 status, no other data is sent back.
just telling the client we have "this issue".
or you can make it more informative, by adding : to the error message constant, telling more info about the error:
ex,
{
"status":"0003",
"message":"missing_param:album_id"
}
telling the user, there is a missing parameter, and it's album_id
now you can write all possible status responses, and there message
this will be part of your service documentation.
I you manually validate your arguments, you could throw a specific exception. Then you can map your exception to a specific HTTP status, for instance BAD REQUEST.
You can map your exception to a response status with Spring Controller Advice: http://spring.io/blog/2013/11/01/exception-handling-in-spring-mvc.