we are developing a JSON web service to receive data via the #RequestBody annotation. In case a property is included in the request that does not match with the deserialized bean, we expect an HTTP 400 (Bad request) response, but instead the property is simply ignored. Here is an example:
#RestController
#Slf4j
public class TestController {
#RequestMapping(method = RequestMethod.POST, value = "/query")
public void parse(#RequestBody Query query) {
log.info("Received query: {}", query.toString());
}
}
#Data
class Query {
private String from;
private String to;
}
When posting
{ "from" : "123", "to": "456", "foo" : "bar" }
we get a HTTP 200 response. How can we make Spring MVC return HTTP 400 in this case?
Any help or pointers are highly appreciated.
Note that this is different from this question: How to return 400 HTTP error code when some property of a RequestBody parameter is null?.
Since that question asks how to return 400 when an expected property is absent.
Put this into application.properties:
spring.jackson.deserialization.FAIL_ON_UNKNOWN_PROPERTIES=true
Here are the relevant docs: Customize the Jackson ObjectMapper
You can reconfigure your Jackson (assuming you are using it) ObjectMapper to fail on unknown properties.
ObjectMapper mapper = new ObjectMapper().configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, true);
In this case your unknown property will throw JsonMappingException and you can introduce custom exception handler to return in this case Response 400.
How about this -
private String to;
comparing against
{ "from" : "123", "foo" : "bar" }
I think you are comparing to against foo. So deserialization fails.
You can use Validation for this Job , the validator will check if these fields are not null , if they are it spring will return 400 error bad request so here is how you do it:
In your Bean:
public class A {
#NotNull
private String firstName;
public String getFirstName() {
return firstName;
}
public void setFirstName(String firstName) {
this.firstName = firstName;
}
}
In your Controler:
#ResponseBody
#RequestMapping(path = "/rest")
public B treatRequest(#RequestBody #Validated A a) {...}
In your Configuration File you should activate the validation like this(probably you allready have it):
<mvc:annotation-driven/>
Related
I want to validate my request body with #Valid annotation, but it's not working in Spring Boot
I have a Request class within JAR file which I can't modify with two fields. One field is of type Object. My controller class accept this class object as a request body. When I pass my below JSON to the controller, validation is not working. Below are code samples.
Request Class:
public class Request {
Object data;
Map<String, Object> meta;
public <T> T getData() throws ClassCastException {
return (T) this.data;
}
}
Another Class:
public class StudentSignUpRequest {
#NotNull(message = "First Name should not be empty")
#Size(max = 64, message = "FirstName should not exceed 64 characters")
private String firstName;
#NotNull(message = "Last Name should not be empty")
#Size(max = 64, message = "LastName should not exceed 64 characters")
private String lastName;
#NotNull(message = "Email cannot be empty")
#Size(max = 50, message = "Email cannot exceed 50 characters")
#Pattern(regexp = EMAIL_REGEX_PATTERN, message = "Email should contain a valid email address.")
private String email;
// other fields
}
Controller Class:
#PostMapping(value = Constants.STUDENT_SIGN_UP)
public Response signUpStudent(#Valid #RequestBody Request request, HttpServletRequest servletRequest) {
// retrieving the actual resource from request payload
StudentSignUpRequest signUpRequest = request.getData(StudentSignUpRequest.class);
// call service to sign-up student
return loginRegistrationService.signUpStudent(signUpRequest);
}
Calling code sets request as below:
StudentSignUpRequest studentSignUpRequest = new StudentSignUpRequest();
//setter methods
Request payload = new Request();
payload.setData(studentSignUpRequest);
This is the request I am sending:
For more than 64 chars for firstName:
Sample JSON:
{
"data": {
"firstName": "student111111111111111111111111111111111111111111111111111111111111",
"lastName": "somesurname",
"email": "developer#gmail.com"
}
}
Where first name not included:
{
"data": {
"lastName": "somesurname",
"email": "developer#gmail.com"
}
}
Here both #Size as well as #NotNull annotation not working.
Any solution?
Validation would've worked if the Request class was like;
public class Request {
#Valid
StudentSignUpRequest data;
// other stuff
}
The fact that you have no class type for data makes it impossible for validation to be applied on it, ignoring the fact that there isn't even a #Valid annotation on the field. The #Valid annotation is used to propagate the validation cascade.
But since you cannot modify Request object, let's continue with another way to handle validation without doing it manually.
Another way is to trigger validation after you get the StudentSignUpRequest from request object;
StudentSignUpRequest signUpRequest = request.getData(StudentSignUpRequest.class);
loginRegistrationService.signUpStudent(signUpRequest) // validation will trigger with this call
What you can do is as follows;
#Service
#Validated
public class LoginRegistrationService {
public void signUpStudent(#Valid StudentSignUpRequest signUpRequest) {
// some logic
}
}
with #Validated annotation, you will activate the validation check for any #Valid annotated args in public methods within that class.
Can be used with method level validation, indicating that a
specific class is supposed to be validated at the method level (acting
as a pointcut for the corresponding validation interceptor)
This can be costly since you'd want to get any constraint violation as soon as possible without doing any costly jobs for an already doomed request.
No validation will work the way you are using it, you need to put #valid on the object inside your request object, but since you dont have control on that class the other way around is extend Request object and override getData method and apply #valid on that method, it should work that way.
First use #NotEmpty, #Notblank for Strings. Then ensure you import javax.validation.constraints not that of hibernate. If you are using a custom validator, you will need (final BindingResult bindingResult) as part of your controller method variable.
So you can use below code for validating the same.
public <T> T getData() throws ClassCastException, SomeCustomValidationException {
ValidatorFactory factory = Validation.buildDefaultValidatorFactory();
validator = factory.getValidator();
Set s = validator.validate(this.data);
//throw SomeCustomValidationException if set is not empty else return this.data
}
A couple of things here:
The type Object for data in Request class makes it impossible for the validator to know that it is of type StudentSignUpRequest. So change the data type.
public class Request {
StudentSignUpRequest data;
Map<String, Object> meta;
}
Secondly, though you have added #Valid in the controller method, in order to validate fields in StudentSignUpRequest you have to add #Valid here as well. Now, data will be validated if passed in the API request. In case it is absent validation won't take place. If you want to make data to be mandatorily passed add #NotNull as well.
public class Request {
#Valid
#NotNull
StudentSignUpRequest data;
Map<String, Object> meta;
}
did you add following dependency?
spring-boot-starter-validation
also check https://www.baeldung.com/spring-boot-bean-validation
I have a simple controller:
#RequestMapping(method = { RequestMethod.POST })
public ResponseEntity<MyResponse> print(#RequestBody final RequestModel request) throw ApiException {
return null;
}
And in my RequestModel:
class RequestModel {
private String name;
private CustomData data;
}
CustomData:
class CustomData {
private String data;
}
When I make POST request without the "data" field, it works. But if I add the "data" field, I'm getting a 400, The request sent by the client was syntatically incorrect.
O dont know If you wrote all the code, but tou should implements serializable and write setters and getters.
But, answering your question, you should annotate your fields with #JsonProperty to specify the required flag.
Your posted JSON should be something like this :
{
"name":"Luke",
"data": {
"data":"I am your father"
}
}
OBS: if you are using Postman, please set the header : key: Content-Type, value: application/json
You should specify an endpoint:
Example :
#PostMapping("/data")
Instead of
#RequestMapping(method = { RequestMethod.POST })
If you are using default port, try again the post to :
http://localhost:8080/data
OBS: RequestModel and CustomerData must have getters and setters.
I have spring boot application which used spring rest controller .
This is the controller , below is the response an getting. Am using postman tool for sending request to this controller. And am sending content type as application/json
#RequestMapping(value = "/test", method = RequestMethod.POST)
public String test(#RequestBody WebApp webapp, #RequestBody String propertyFiles, #RequestBody String) {
System.out.println("webapp :"+webapp);
System.out.println("propertyFiles :"+propertyFiles);
System.out.println("propertyText :"+propertyText);
return "ok good";
}
2018-03-21 12:18:47.732 WARN 8520 --- [nio-8099-exec-3] .w.s.m.s.DefaultHandlerExceptionResolver : Resolved exception caused by Handler execution: org.springframework.http.converter.HttpMessageNotReadableException: I/O error while reading input message; nested exception is java.io.IOException: Stream closed
This is my postman request
{
"webapp":{"webappName":"cavion17","path":"ud1","isQA":true},
"propertyFiles":"vchannel",
"propertytText":"demo property"}
I tried by removing the RequestBody annotation, then able to hit the service , but param objects are received as null.
So please suggest how to retrieve objects in the restcontroller?
You cannot use multiple #RequestBody annotations in Spring. You need to wrap all these in an object.
Some like this
// some imports here
public class IncomingRequestBody {
private Webapp webapp;
private String propertryFiles;
private String propertyText;
// add getters and setters here
}
And in your controller
#RequestMapping(value = "/test", method = RequestMethod.POST)
public String test(#RequestBody IncomingRequestBody requestBody) {
System.out.println(requestBody.getPropertyFiles());
// other statement
return "ok good";
}
Read more here
Passing multiple variables in #RequestBody to a Spring MVC controller using Ajax
Based on the sample postman payload you gave, you will need:
public class MyObject {
private MyWebapp webapp;
private String propertyFiles;
private String propertytText;
// your getters /setters here as needed
}
and
public class MyWebapp {
private String webappName;
private String path;
private boolean isQA;
// getters setters here
}
Then on your controller change it to:
#RequestMapping(value = "/test", method = RequestMethod.POST)
public String test(#RequestBody MyObject payload) {
// then access the fields from the payload like
payload.getPropertyFiles();
return "ok good";
}
I'm using Spring boot 1.4.0, Consider below code in a #RestController, what I expect is, the server side will receive a http body with form_urlencoded content type, but unfortunately it demands me a query parameter type with email and token. What's the problem here and how to fix?
#DeleteMapping(consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE)
#ResponseStatus(HttpStatus.NO_CONTENT)
public void removeAdmin(#RequestParam(value = "email") String email, #RequestParam(value = "token") String token) {
//...
}
#DeleteMapping is only a convenience extension the provides #RequestMapping(method=DELETE) It will not handle request paramters. You will still have to map those in the controllers method signature if you need the data to perform the work.
Since you want a body, You could create an object and mark it as #RequestBody:
public class DeleteBody {
public String email;
public String token;
}
public void removeAdmin(#RequestBody DeleteBody deleteBody) {
...
}
I am building a REST service using spring boot. My controller is annotated with #RestController. For debugging purposes I want to intercept the ResponseEntity generated by each of the controller methods (if possible). Then I wish to construct a new ResponseEntity that is somewhat based on the one generated by the controller. Finally the new generated ResponseEntity will replace the one generated by the controller and be returned as part of the response.
I only want to be able to do this when debugging the application. Otherwise I want the standard response generated by the controller returned to the client.
For example I have the controller
#RestController
class SimpleController
#RequestMapping(method=RequestMethod.GET, value="/getname")
public NameObject categories()
{
return new NameObject("John Smith");
}
}
class NameObject{
private String name;
public NameObject(name){
this.name = name;
}
public String getName(){ return name; }
}
This will generate the response:
{"name" : "John Smith"}
But I would like to change the response to include status info of the actual response e.g:
{"result": {"name" : "John Smith"}, "status" : 200 }
Any pointers appreciated.
The way I would try to achieve such a functionality is first by creating an Interceptor. And example can be found here
Second, I would employ Spring profiles to ensure that interceptor is loaded only in profile that I needed it in. Detail here. It's not exaclty debugging, but might do the trick.
You can do this with spring AOP, something like:
#Aspect
#Component
public class ResponseEntityTamperer {
#Around("execution(* my.package.controller..*.*(..))")
public Object tamperWithResponseEntity(ProceedingJoinPoint joinPoint)
throws Throwable {
Object retVal = joinPoint.proceed();
boolean isDebug = java.lang.management.ManagementFactory.getRuntimeMXBean()
.getInputArguments().toString()
.contains("jdwp");
if(isDebug && retVal instanceof ReponseEntity) {
// tamper with the entity or create a new one
}
return retVal;
}
}
The "find out if we're in debug mode" code is from this answer.