Spring boot2 path variable validation - spring-boot

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

Related

Spring Boot validation throws BindException insted of MethodArgumentNotValid Exception

I switched to Spring Boot 3 and setup a simple project.
This contains input validation. I created a simple model and annotated the fields
#Data
#NoArgsConstructor
public class DemoUserCreateDto {
#Email(message = "email must be valid")
private String emailContentCreator;
#NotNull(message = "must not be null")
private String emailContentConsumer;
}
On my Controller level I have a simple endpoint:
#PostMapping(value = "/users", consumes = MediaType.APPLICATION_JSON_VALUE)
public String createUsers(#RequestBody #Valid DemoUserCreateDto demoUserCreateDto) {
return "DONE";
}
pom.xml:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
PROBLEM:
The validation is done on the #NotNull field. The #Email field does not have any effect.
import is:
import jakarta.validation.constraints.Email;
import jakarta.validation.constraints.NotNull;
The exception thrown on the NotNull validation is:
org.springframework.validation.BindException
but I was expecting a
MethodArgumentNotValidException
WHAT I'VE TRIED:
I tried to annotate my controller with #Validated. No success.
The thing is that I've set this up in previous projects with spring boot 2.x.
I've seen that in termins of validator libraries, jakarta is used instead of javax.
Has something changed in the implementation? I've also read that there is a change in the latest lombok version, where they force you to use the validation statement on the getter method of the attribute. I've reset to a minor version, but nothing changed.

#Pattern not working on controller method with path variable

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>

#Valid not working for spring rest controller

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>

How to get specific headers (spring-boot application) and pass to controller?

I have a controller in spring boot. I want to get the formId from Form Data (see the image above). #RequestHeader(value="formId") doesn't work. How to get the value?
formId is not from the header but the from form data which is the request body.
You can get it like in this example:
#GetMapping("foo)
public String foo(#RequestBody MultiValueMap<String, String> formData) {
String formId = formData.get("formId");
// your code
}
First you need below dependency,
<dependency>
<groupId>javax.ws.rs</groupId>
<artifactId>javax.ws.rs-api</artifactId>
<version>2.1</version>
</dependency>
Then you can get Form data value using below example code,
#PostMapping("/foo")
#ResponseBody
public ResponseEntity<?> getData(#FormParam("formId") String formId) {
System.out.println(formId);
}
In here formParam variable name and parameter name should be equal.

Method With #PathVariable In SpringBoot Returns Empty Response

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;
}
}

Resources