Rest Endpoint Exception Handling - java

Below is my rest endpoint. I used Long for data type for userId, Its working fine when calling the endpoint via postmen like below and I am able to handle exceptions explicitly.
localhost:8080/order-service/save-order/1
but when I am calling like this with a string type parameter,
localhost:8080/order-service/save-order/abc
the spring boot implicitly handles the exception and give 400 bad request.
what I want is to throw a custom error message like "please send proper userId" when the variable type of parameter not equal to long.
#PostMapping(path = "/save-order/{userId}")
#ResponseBody
public ResponseEntity<ExceptionResponse> addOrder(#Valid #RequestBody
OrderDTO orderDto, #Valid #PathVariable(name = "userId") Long userId) throws BatchException, UserExceptions, BatchIdException, InvalidDateFormatException, DeliveryIdException,BatchMerchantException {
return ResponseEntity.ok(new ExceptionResponse("Order Saved", 201, orderServiceImpl.saveOrder(orderDto, userId)));
}

You can implement your own custom validator, see here: https://www.baeldung.com/spring-mvc-custom-validator
Return true if the input fits and false if not, you can also define the message there you want to show the user if he enters a wrong input.

Related

Use Objects.requireNonNull() to validate the request body like Objects.requireNonNull(body.getName()) return error code

when I use the Objects.requireNonNull() to validate the request body, some api return error code 400, others return 500. It should be 400, because the body sent to server side is invalid. I am now confused, do anyone know how to address this. Thanks!
#PostMapping(value = "/referencing-courses")
public Object get(#RequestBody Criteria criteria) {
Objects.requireNonNull(criteria, "body is required");
Objects.requireNonNull(criteria.getContentId(), "contentId is required.");
Objects.requireNonNull(criteria.getSchemaVersion(), "schemaVersion is required.");
return findLatestTreeRevisionReference.find(criteria);
}
Objects.requireNonNull() throws a `NullPointerException if the passed parameter is null. Exceptions trigger an Internal Server Error (500).
The status code 400 is not caused by the exception but because controller parameters are not null by default and spring validates this.
As #chrylis -cautiouslyoptimistic- pointed out in the comments, you can use #Valid:
#PostMapping(value = "/referencing-courses")
public Object get(#RequestBody #Valid Criteria criteria) {
return findLatestTreeRevisionReference.find(criteria);
}
For this to work, Criteria needs to use proper annotations:
public class Criteria{
#NotNull
private String contentId;
#NotNull
private String schemaVersion;
//Getters/Setters
}
You can also create a custom exception and/or exception handler in your controller as described here if #Valid is not viable.
For example, you could catch every NullPointerException in your controller and send back a 400 Bad Request status:
#ResponseStatus(HttpStatus.BAD_REQUEST)
#ExceptionHandler(NullPointerException.class)
public void handleNPE() {
//Done by #ResponseStatus
}
However, NullPointerExceptions might occur because of different reasons than the user sending an invalid request. In order to bypass that issue, you can create your own exception that translates to the error 400:
#ResponseStatus(value=HttpStatus.NOT_FOUND, reason="Request body invalid")
public class InvalidBodyException extends RuntimeException {
//No need for additional logic
}
You can then make your own method that does the null check and use that instead of Objects.requireNonNull:
public void requireNonNull(Object o){
if(o==null){
throw new InvalidBodyException();
}
}

Custom validation for 2 request params in Spring

Is there a way to custom validate 2 of request parameters coming into endpoint in Spring? I would like to be able to validate them with my custom function. Something like add annotation to the request params or on the function where these params are and force these params to be validated by another custom written function.
I need to take both params at the same time, because the validation output of one is dependent on the value of the other one.
I have searched and found some solutions with custom constraint annotations but from what I've read it doesn't seem to solve my problem.
As rightly mentioned, using valiktor is the best option. I have used it in our product as well and it works like a charm.
Below is a snippet example as how you are use it to compare two properties of the same class.
fun isValid(myObj: Myobj): Boolean {
validate(myObj) {
validate(MyObj::prop1).isGreaterThanOrEqualTo(myobj.prop2)
}
Valiktor throws exception with proper message if the validation fails. It also enables you to create custom exception messages if you want to.
Now all you need to do is, create a class for your requestBody and check your conditions with isValid() method explicitly or move it into init block and do it implicitly.
Valiktor has a large number of validations as compared to JSR380, where creating custom validation is a little messy as compared to Valiktor.
If you're going to use the request params to create a POJO, then you can simply use the Javax Validation API.
public class User {
private static final long serialVersionUID = 1167460040423268808L;
#NotBlank(message = "ID cannot be to empty/null")
private int id;
#NotBlank(message = "Group ID cannot be to empty/null")
private String role;
#NotBlank(message = "Email cannot be to empty/null")
private String email;
#NotNull(message = "Password cannot be to null")
private String password;
}
To validate -
#PostMapping("/new")
public String save(#ModelAttribute #Validated User user, BindingResult bindingResult, ModelMap modelMap) throws UnknownHostException {
if (!bindingResult.hasErrors()) {
// Proceed with business logic
} else {
Set<ConstraintViolation<User>> violations = validator.validate(user);
List<String> messages = new ArrayList<>();
if (!violations.isEmpty()) {
violations.stream().forEach(staffConstraintViolation -> messages.add(staffConstraintViolation.getMessageTemplate()));
modelMap.addAttribute("errors", messages);
Collections.sort(messages);
}
return "new~user";
}
}
You can write custom validator by using Validator
Check :: https://docs.spring.io/spring-framework/docs/3.0.0.RC3/reference/html/ch05s02.html
Example :: https://www.baeldung.com/spring-data-rest-validators
valiktor is really good library to validate.
You can do somenthing like:
data class ValidatorClass(val field1: Int, val field2: Int) {
init {
validate(this) {
validate(ValidatorClass::field1).isPositive()
validate(ValidatorClass::field2).isGreaterThan(field1)
}
}
}
make request parameter not required:
#RequestMapping(path = ["/path"])
fun fooEndPoint(#RequestParam("field1", required = false) field1: Int,
#RequestParam("field2", required = false) field2: Int) {
ValidatorClass(field1, field2) //it will throw an exception if validation fail
}
You can handle exception using try-catch or using and ExceptionHandler defined by valiktor.
Using valiktor you can validate fields depending on other fields. You can create one kotlin file where you write all classes that you use to validate fields from requests and in the same way you can use valiktor in you #RequestBody models to validate it.

Is it possible to verify empty input from the user in spring boot?

I'm writing a spring boot application and I'm having some troubles in verifying empty input from the user.
Is there a way to validate an empty input from the user?
For example:
#PostMapping("/new_post/{id}")
public int addNewPost(#PathVariable("id") Integer id, #RequestBody Post post) {
return postService.addNewPost(id, post);
}`
Here I want to add a new post only if the user exists in the database but when I send this post request I am getting the regular 404 error message and I am not able to provide my own exception although in my code I validate if the id equals to null.
http://localhost:8080/new_post/
Any idea what can I do?
Thanks
You can do something like this
#PostMapping(value = {"/new_post/{id}", "/new_post"})
public int addNewPost(#PathVariable(required = false, name="id") Integer id, #RequestBody Post post) {
return postService.addNewPost(id, post);
}
But the ideal way to handle this is using #RequestParam. #RequestParam is meant exactly for this purpose.
I think you need to do it like this:
#PostMapping(value = {"/new_post/", "/new_post/{id}"})
public int addNewPost(#PathVariable(value = "id", required = false) Integer id, #RequestBody Post post) {
This way you are also handling the URL when ID is null
I Think This is a Better Answer for Two Others :
#PostMapping("/new_post/{id}")
public int addNewPost(#PathVariable("id") Integer id, #RequestBody Post post)
{
if(!ObjectUtils.isEmpty(post))
{
return postService.addNewPost(id, post);
}
else
return null; // You can throws an Exception or any others response
}
id : Not required to check 'id', because with out id the requested method is not call.

Spring GetMapping annotation exception

There is the following method from the controller class:
#GetMapping("{id:" + REGEXP + "}")
#ResponseBody
public SomeObject getById(#PathVariable UUID id) {
return someObjectService.getById(id));
}
REGEXP is a simple regular expression string. In someObjectService getById method handles the case when object cannot be found by id and throws exception. There is also exception handler class for such cases to customize error response:
#ExceptionHandler({ResourceNotFoundException.class})
#ResponseStatus(HttpStatus.NOT_FOUND)
#ResponseBody
public CustomErrorResponse handleNotFoundCase (ResourceNotFoundException exception) {
CustomErrorResponse customerErrorResponse = new CustomErrorResponse();
// filling CustomErrorResponse with specific data using 'exception'
return customerErrorResponse;
}
So, when I test getById with some non-existing id, which passes REGEXP check, expected result = achieved result: 404 and json body of the error has the structure of CustomErrorResponse (from the handler).
However, when I do the same with id, which does NOT pass REGEXP check - 404 occurres, BUT json body of the error is default (bootstrap), it has not CustomErrorResponse structure.
The question is: what kind of exception could be thrown and where (for its further appropriate handling) when id in #GetMapping("{id:" + REGEXP + "}") does not pass the regexp check?
If you want to create regex to check if uuid is proper that this is not necessary and
#GetMapping("/{id}")
public SomeObject getById(#PathVariable UUID id) {
will validate that.
On the other hand if you have more strict requirement on that than you need to use Pattern validator:
#RestController
#Validated
public class Ctrl {
// ...
#GetMapping("/{id}")
public String getById(#Pattern(regexp = REGEXP) #PathVariable String id) {
return someObjectService.getById(UUID.fromString(id)));
}
}
Note, that Pattern validator do not work on UUID type, so you have to convert String to UUID manually.
You can read more about validation in https://docs.spring.io/spring/docs/4.1.x/spring-framework-reference/html/validation.html
Why do you try to post json in your get mapping?
In this case you'll need to use localhost:8080/yourApp/entity/{id:10}
Is that actually what you need instead of localhost:8080/yourApp/entity/10?
Please have a look at this page about how REST Endpoints should be designed:
https://learn.microsoft.com/en-us/azure/architecture/best-practices/api-design
Regarding your question - you can't use validation in such case. You need to add your custom validator for this field
Please find section "Custom Validator" here:
https://www.mkyong.com/spring-boot/spring-rest-validation-example/

Spring Web MVC - validate individual request params

I'm running a webapp in Spring Web MVC 3.0 and I have a number of controller methods whose signatures are roughly as follows:
#RequestMapping(value = "/{level1}/{level2}/foo", method = RequestMethod.POST)
public ModelAndView createFoo(#PathVariable long level1,
#PathVariable long level2,
#RequestParam("foo_name") String fooname,
#RequestParam(value = "description", required = false) String description);
I'd like to add some validation - for example, description should be limited to a certain length or fooname should only contain certain characters. If this validation fails, I want to return a message to the user rather than just throw some unchecked exception (which would happen anyway if I let the data percolate down to the DAO layer). I'm aware of JSR303 but have not worked with it and don't quite understand how to apply it in a Spring context.
From what I understand, another option would be to bind the #RequestBody to an entire domain object and add validation constraints there, but currently my code is set up to accept individual parameters as shown above.
What is the most straightforward way to apply validation to input parameters using this approach?
This seems to be possible now (tried with Spring 4.1.2), see https://raymondhlee.wordpress.com/2015/08/29/validating-spring-mvc-request-mapping-method-parameters/
Extract from above page:
Add MethodValidationPostProcessor to Spring #Configuration class:
#Bean
public MethodValidationPostProcessor methodValidationPostProcessor() {
return new MethodValidationPostProcessor();
}
Add #Validated to controller class
Use #Size just before #RequestParam
#RequestMapping("/hi")
public String sayHi(#Size(max = 10, message = "name should at most 10 characters long") #RequestParam("name") String name) {
return "Hi " + name;
}
Handle ConstraintViolationException in an #ExceptionHandler method
There's nothing built in to do that, not yet anyway. With the current release versions you will still need to use the WebDataBinder to bind your parameters onto an object if you want automagic validation. It's worth learning to do if you're using SpringMVC, even if it's not your first choice for this task.
It looks something like this:
public ModelAndView createFoo(#PathVariable long level1,
#PathVariable long level2,
#Valid #ModelAttribute() FooWrapper fooWrapper,
BindingResult errors) {
if (errors.hasErrors() {
//handle errors, can just return if using Spring form:error tags.
}
}
public static class FooWrapper {
#NotNull
#Size(max=32)
private String fooName;
private String description;
//getset
}
If you have Hibernate Validator 4 or later on your classpath and use the default dispatcher setup it should "Just work."
Editing since the comments were getting kind of large:
Any Object that's in your method signature that's not one of the 'expected' ones Spring knows how to inject, such as HttpRequest, ModelMap, etc, will get data bound. This is accomplished for simple cases just by matching the request param names against bean property names and calling setters. The #ModelAttribute there is just a personal style thing, in this case it isn't doing anything. The JSR-303 integration with the #Valid on a method parameter wires in through the WebDataBinder. If you use #RequestBody, you're using an object marshaller based on the content type spring determines for the request body (usually just from the http header.) The dispatcher servlet (AnnotationMethodHandlerAdapter really) doesn't have a way to 'flip the validation switch' for any arbitrary marshaller. It just passes the web request content along to the message converter and gets back a Object. No BindingResult object is generated, so there's nowhere to set the Errors anyway.
You can still just inject your validator into the controller and run it on the object you get, it just doesn't have the magic integration with the #Valid on the request parameter populating the BindingResult for you.
If you have multiple request parameters that need to be validated (with Http GET or POST). You might as well create a custom model class and use #Valid along with #ModelAttribute to validate the parameters. This way you can use Hibernate Validator or javax.validator api to validate the params. It goes something like this:
Request Method:
#RequestMapping(value="/doSomething", method=RequestMethod.GET)
public Model dosomething(#Valid #ModelAttribute ModelRequest modelRequest, BindingResult result, Model model) {
if (result.hasErrors()) {
throw new SomeException("invalid request params");
}
//to access the request params
modelRequest.getFirstParam();
modelRequest.getSecondParam();
...
}
ModelRequest class:
class ModelRequest {
#NotNull
private String firstParam;
#Size(min = 1, max = 10, message = "You messed up!")
private String secondParam;
//Setters and getters
public void setFirstParam (String firstParam) {
this.firstParam = firstParam;
}
public String getFirstParam() {
return firstParam;
}
...
}
Hope that helps.

Categories

Resources