I have created a controller with an update (PUT) request and applied regex on the id field as shown below but the pattern doesn't get executed and returning back SUCCESS instead of BAD_REQUEST.
#PutMapping("users/{id}")
public Mono<ResponseEntity<UsersApiDTO>> update(#PathVariable #Pattern(regexp = "^[A-Za-z]{1,20}$") String id, #RequestBody #Valid UsersApiDTO usersApiDTO) {
// return response
}
when I pass the wrong 'id' path field value "T001223242345" it is working. It should give me BAD_REQUEST as it works for the request body like if I pass any invalid value for the body parameter it gives BAD_REQUEST.
I have gone through many links and blogs but no luck yet.
You need to add the #Validated annotation onto your RestController in order for path variables to get validated:
#Validated
#RestController
public class YourController {
[...]
I would also suggest reviewing Validating RequestParams and PathVariables in Spring.
Try adding #Validated to your Controller class, and that should work. If it isn't please try to use the latest hibernate validator:
<dependency>
<groupId>org.hibernate.validator</groupId>
<artifactId>hibernate-validator</artifactId>
<version>6.0.10.Final</version>
</dependency>
Related
I am using spring boot for creating rest services. I need to validate the parameter passed. I have a service like below,
#GetMapping(value="/employee/{Id}")
public EmployeeDTO getEmployeeDetails(#PathVariable String Id) {
...
}
I need to throw error if Id is not passed in url. Like "Missing Id in request". I was able to achieve using below,
#GetMapping(value={"/employee", "/employee/{Id}"})
public EmployeeDTO getEmployeeDetails(#PathVariable String Id) {
...
}
And handled MissingPathVariableException in ExceptionHandler annotated with #ControllerAdvise.
But I wanted to know is this the right way to check ?
You can use #ControllerAdvise to handle exceptions that are generated while executing your actual code.
For Path variable validation, you can make use of spring-boot-starter-validation.
Add this maven dependency:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
Then your controller will look like:
#GetMapping(value={"/employee", "/employee/{Id}"})
public EmployeeDTO getEmployeeDetails(
#NotBlank(message = "Missing Id in request")
#PathVariable String Id) {
...
}
I recommend you to read this: Validating Form Input
I have defined a rest endpoint method as:
#GetMapping("/get")
public ResponseEntity getObject(#Valid MyObject myObject){....}
This maps request parameters to MyObject.
MyObject is defined as(with lombok, javax.validation annotations):
#Value
#AllArgsConstructor
public class MyObject {
#Min(-180) #Max(180)
private double x;
#Min(-90) #Max(90)
private double y;
}
But validations are not working. Even with values out of prescribed range, request doesn't throw error and goes well.
If you on a version of Spring Boot > 2.3 it now states
Validation Starter no longer included in web starters
... you’ll need to add the starter yourself.
i.e.
For Maven builds, you can do that with the following:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
For Gradle, you will need to add something like this:
dependencies {
...
implementation 'org.springframework.boot:spring-boot-starter-validation'
}
Please refer to https://github.com/spring-projects/spring-boot/wiki/Spring-Boot-2.3-Release-Notes#validation-starter-no-longer-included-in-web-starters
Annotate your controller with org.springframework.validation.annotation.Validated
I see a couple of things here that you should fix. Let's start talking about the REST standard, the first rule is to think in endpoints as representation of resources, not operations, for example, in your code, I presume the MyObject class represents a Point (you should refactor the class to have a proper name), then the path value for the getObject can be "/point". The operations are mapped on the HTTP method, accordingly:
GET: Obtain info about a resource.
POST: Create a resource.
PUT: Update a resource.
DELETE: Delete a resource.
In getObject you're expecting to receive an object. The get method according to the REST standards means you want to retrieve some data, and usually you send some data included in the url like ../app-context/get/{id}, here the id is a parameter that tells your controller you want some info belonging to an id, so if you would invoke the endpoint like as ../app-context/get/1 to get info of some domain object identified by the number 1.
If you want to send data to the server, the most common HTTP method is a POST.
According to this, at design level you should:
Give a meaningful name to the MyObject class.
Check the operation you want to make in the getObject.
Assign a path to getObject representing a resource.
At code level, with the above comments, you could change this as:
#Data
#AllArgsConstructor
#NoArgsConstructor
public class MyObject {
#Min(-180) #Max(180)
private double x;
#Min(-90) #Max(90)
private double y;
}
#PostMapping("/point")
public ResponseEntity savePoint(#RequestBody #Valid MyObject myObject) {...}
I will explain the changes:
Add #PostMapping to fulfill the REST standard.
Add #RequestBody, this annotation take the info sent to the server and use it to create a MyObject object.
Add #NoArgsConstructor to MyObject, by default, the deserialisation use a default constructor (with no arguments). You could write some specialised code to make the things work without the default constructor, but thats up to you.
I just had to add the following dependency to get the validations working.
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
I have validation working for the beans and request parameters, however, my path variables fail to get validated:
#PathVariable #Pattern(regexp = "[A-Za-z0-9]+") String protocol
When I provide path var as ab!ab it doesn't fail the request with 400 status code but lets it pass with the value assigned to the argument.
I have also validated my regex online and it is valid and should be working fine.
Also, my rest controller does have #Validated annotation.
What am I missing here?
================UPDATE=============
I have tried other constraint annotations and none of them work, so it must something to do with the path variable validation. But what??
Make sure you have
hibernate-validator
dependency and add following bean:
#Bean
public MethodValidationPostProcessor methodValidationPostProcessor() {
return new MethodValidationPostProcessor();
}
Is it possible to do integration test for null check.
I passed null value.
HttpEntity<Employee> entity = new HttpEntity<Employee>(null, headers);
restTemplate.exchange(url, httpMethod, entity, String.class);
I got the below error.
{"timestamp":"2018-10-06T14:33:52.113+0000","status":400,"error":"Bad Request","message":"Required request body is missing:"}
#RestController
public class EmployeeController {
#PostMapping(value = "/employee/save", produces = "application/json")
public Employee save(#RequestBody Employee employee){
if(employee==null){
throw new RuntimeException("Employee is null");
}
}
}
class Employee {
}
#RequestBody(required=false) Employee employee
please try with required option in #RequestBody.
The problem here is the mapping in spring mvc.
required
Default is true, leading to an exception thrown in case there is no body content. Switch this to false if you prefer null to be passed when the body content is null.
#RequestBody Employee employee
Your method only is processed the request if employee is not null. Then it considered mapping correctly and pass request to this method and handle it. So the check null condition will be needless here.
I am not sure whether it's doable, but I will say it is a bad practice.
According to RFC 7231:
The POST method is used to request that the origin server accept the
representation enclosed in the request as data to be processed by the
target resource.
Since you have annotated your controller PostMapping, there should be request body to server. I don't see the value of writing integration test for null or empty request body.
If we look into the HTTP request structure,
POST /your_url HTTP/1.1
HOST your_host
ContentType ...
ContentLength ...
Body line 1
What's the difference between null and empty?
You can use javax validation framework to check whether #requestbody is null or not
please use below approach: It'll definitely resolve your concern.
Maven Dependency:
<dependency>
<groupId>javax.validation</groupId>
<artifactId>validation-api</artifactId>
<version>1.1.0.Final</version>
</dependency>
#RestController
public class EmployeeController {
#PostMapping(value = "/employee/save", produces = "application/json")
public Employee save(#NotNull #RequestBody Employee employee){
}
}
You are getting this error message because #RequestBody annotation has default value and you have to send valid request to your method.
You don't need to null check inside of method for request object because #RequestBody has required field and this field's default value is true. If you check the inside of #RequestBody interface you are going to see what I am saying. Also you can see the below;
If you set #RequestBody(required = false) in method's parameter you are not going to get error message, but you need to check your request object is null or not... If you don't check-out of object is null when you use that object you will get "Null Pointer Excepiton"...
your choise...
good luck
You can use #NonNull either with the #RequestBody annotation. Or, even better if you want the variables of the Object to always be non-null after the constructor is called, you can use this annotation in the class itself when defining the variables/attributes.
I'm trying to write a method which takes a #PathVariable parameter and redirects user to a jsp file.
#Controller
public class MainController
{
#RequestMapping("/user/{customerId}")
// http://localhost:8080/Test/user/5
public String getCustomerById(#PathVariable("customerId") String customerId, Model model)
{
model.addAttribute("customer_id", customerId);
// this is the user_details.jsp file, I need to show this jsp file to visitor
return "user_details";
}
}
When I try to navigate http://localhost:8080/SpringBlog/user/5 It's showing me an empty response. (Nothing Even In Page Source)
When I looked into Spring output console, it's showing me the following message when I'm trying to navigate :
2017-07-19 13:24:56.191 ERROR 6772 --- [io-8080-exec-75]
o.s.boot.web.support.ErrorPageFilter
Cannot forward to error page for request [/user/5] as the response has already been committed. As a result, the response may have the wrong status code. If your application is running on WebSphere Application Server you may be able to resolve this problem by setting com.ibm.ws.webcontainer.invokeFlushAfterService to false
I've already tried following parameter descriptions as the followings :
#PathVariable(value="customerId") String customerId
#PathVariable(name="customerId") String customerId
#PathVariable("customerId") String customerId
#PathVariable String customerId
None of them worked, always empty response with same error message.
I'm sure that all files are in correct place, in my MainController Class
I have several methods with No Parameters, RequestParams, etc.. all of them working as expected. But if I want to create a RequestMapping with #PathVariable, it always returns empty response and same error message in the output console.
But if I try same approach with #RestController it's working as expected:
#RestController
public class RestApi
{
// http://localhost:8080/Test/api/user/56
// Is Working, Returns Hello 56 As Response
#RequestMapping("api/user/{customerId}")
public String apiTest(#PathVariable("customerId") String customerId)
{
return "Hello "+customerId;
}
}
What am I missing ?
Application Details :
<packaging>war</packaging>
...
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.5.4.RELEASE</version>
<relativePath/>
<!-- lookup parent from repository -->
</parent>
...
Apache Tomcat/7.0.56
JVM 1.8.0_131-b11
Thanks for your help.
The annotation #RestController automatically adds the #ResponseBody to your methods.
What #ResponseBody does is to bind the outgoing returned value to the HTTP response body using a HttpMessageConverter. If you don't add either the #RestController or #ResponseBody annotation then Spring will try to resolve that to a view, commonly a JSP page.
So in your case Spring is trying to find the view matchin "Hello"+customerId instead of printing out the result of "Hello"+customerId.
So you're using the #PathVariable annotation correctly. :)
You can read more here
Please , check your application.properties file ...
prefix and suffix
spring.mvc.view.prefix: (Where are jsp files) Example /WEB-INF/ or /
spring.mvc.view.suffix: .jsp
If you are using #Controller annotation then you need to add #ResponseBody annotation for binding the outgoing returned value to the HTTP response body. So your code with #Controller should look like:
#Controller
public class MainController
{
#RequestMapping("/user/{customerId}")
#ResponseBody
// http://localhost:8080/Test/user/5
public ModelAndView getCustomerById(#PathVariable("customerId") String customerId, ModelAndView model)
{
model.addAttribute("customer_id", customerId);
// this is the user_details.jsp file, I need to show this jsp file to visitor
model.setViewName("user_details");
return model;
}
}