RequestBody and camel case request parameters - spring

I have an endpoint with a request body:
#PostMapping("/v1/message")
#PreAuthorize("#oauth2.isAnyClientId(#authorizeConfig.getIds())")
public void receive(
#RequestBody #NotNull #Valid final Message message,
#RequestHeader final HttpHeaders headers) {
...
}
The Message class has several parameters:
#Builder
#Getter
#Setter
#EqualsAndHashCode
#NoArgsConstructor
#AllArgsConstructor
#JsonPropertyOrder(alphabetic = true)
public class Message {
#NotBlank(message = "tenant is empty")
private String tenant;
#NotBlank(message = "messageId is empty")
private String messageId;
#NotBlank(message = "payload is empty")
private String payload;
#NotNull(message = "messageType is empty")
private MessageType messageType;
private String xSchemaVersion;
}
When I perform a request to that endpoint:
{
"tenant":"client1",
"messageId":"670f13e15d554b2bba56f6d76d33b79c",
"payload":"",
"messageType":"MessageType.Email",
"xSchemaVersion":"2.7.3"
}
I get the following error:
{
"status": 400,
"date": "2022-07-18T08:21:21.430+0000",
"exception": "missing parameter: xSchemaVersion",
"description": "uri=/v1/message"
}
But if I do the following instead:
{
"tenant":"client1",
"messageId":"670f13e15d554b2bba56f6d76d33b79c",
"payload":"",
"messageType":"MessageType.Email",
"xschemaVersion":"2.7.3"
}
I get 200 OK.
Notice that the only thing I changed is the lowercase s in xschemaVersion instead of xSchemaVersion.
Why is this necessary?

This is happening because you are using lomboks getter & setter for your Message entity.
If you check, lombok will generate get & set for your xSchemaVersion as
getXSchemaVersion() & setXSchemaVersion(..), which is quite different than what any IDE would automatically generate for us.
If I use eclipse for generating getters & setters for xSchemaVersion it would be getxSchemaVersion() & setxSchemaVersion(..); and this is what spring is also looking for as valid getter & setter for field xSchemaVersion (based on reflaction).
Because lombok is making your x & s as an upper case, spring is unable to map your xSchemaVersion with input if you use xSchemaVersion.
The reason it works with xschemaVersion because, spring is able to map field as it does lowecase conversion for words after get & set( getXSchemaVersion() & setXSchemaVersion(..).)
Workaround -- create separate getter & setter in your Message for xSchemaVersion as getxSchemaVersion & setxSchemaVersion(..) which acts as overriding methods of default generated getter /setter of lombok.
Once you do this, you can successfully able to use xSchemaVersoin in your input.

According to Project Lombok https://projectlombok.org/features/GetterSetter
you can add AccessLevel.PRIVATE to the GETTER SETTER for not used field.
#Getter(AccessLevel.PRIVATE) #Setter(AccessLevel.PRIVATE) String xSchemaVersion;

Related

#NotNull not working on nested object properties in a Kotlin class even though #Valid is added

I have a spring-boot app in Kotlin. I have a model called MyModel with just two field : a, b
NotNull validation works fine on a but it doesn't work on b although I've added #valid on top of b.
So when I don't pass b in my api request, it doesn't invalidate the model, although it's invalid (since b is missing).
I really don't know why it doesn't work. if I remove ? to enforce not nullness, then Jackson would throw exception when trying to deserializing the model. So I cannot make it nun-nullable (by removing ? from b). Here is my model.
data class MyModel(
#NotNull(message = "a is mandatory")
lateinit var a: String
#Valid
#NotNull(message = "b is mandatory")
var b: MyNestedModel?
)
data class MyNestedModel(
#NotNull(message = "MyNestedModel.a is mandatory")
var a: Date,
#NotNull(message = "MyNestedModel.b is mandatory")
var b: String,
#NotNull(message = "MyNestedModel.c is mandatory")
var c: String
)
And here is the my controller code :
#RestController
class MyController {
#Autowired
lateinit var validator: Validator
#PostMapping("/aa")
fun sortingRequest(#RequestBody req: MyModel): ResponseEntity<Any> {
val validationResult = validator.validate(req)
return if (validationResult.isEmpty()) {
ResponseEntity<Any>(null, HttpStatus.CREATED)
} else {
ResponseEntity<Any>(HttpErrorResponse("Bad input parameter", validationResult.map {
it.messageTemplate
}), HttpStatus.CREATED)
}
}
}
Any help would be greatly appreciated.
I've found the answer myself. Posting it in case might be useful for others.
from here I learned that I should prefix my annotation with get so that they can work :
To make the Spring validation work, you should explicitly mention the location to apply the annotation like #field:annotation or #get:annotation in the constructor. There’re many locations where the annotation could be placed in this context: Kotlin property, backing field, getter method, constructor parameter, setter method, or setter parameter.
The field: makes it explicit the annotation is to be applied to the backing field of the property.
So I changed #NotNull(message = "b is mandatory") to #get:NotNull(message = "b is mandatory") and it worked.
data class MyModel(
#NotNull(message = "a is mandatory")
lateinit var a: String
#Valid
#get:NotNull(message = "b is mandatory")
var b: MyNestedModel?
)
data class MyNestedModel(
#NotNull(message = "MyNestedModel.a is mandatory")
var a: Date,
#NotNull(message = "MyNestedModel.b is mandatory")
var b: String,
#NotNull(message = "MyNestedModel.c is mandatory")
var c: String
)
If you want to have it nullable on the class level and just not allow the nullability from an annotation perspective, I would recommend using #RequestEntity instead of #RequestBody and parse the request body that way. This way you can leave you class non-nullable and handle the parsing error that Jackson throws gracefully if no b is supplied.
I had something similar recently, which was caused by forgetting to include the validator library in my dependencies. The annotations were there, but they were just completely ignored.
This may not be applicable in your situation, but I fixed it by including this into my Gradle file:
implementation("org.springframework.boot:spring-boot-starter-validation")

Spring boot rest requestbody and #valid not working when object is null/empty

I am trying to apply not null validation on an attribute of my request which is instructedAmount but it is not working. I have a Spring Boot (V2.3.0.RELEASE) application with the following endpoints:
#Validated
public class TestController {
#PostMapping(value = "/test/pay")
public ResponseEntity<IPSPaymentResponse> validatePayt(#Valid #RequestBody InstantPaymentRequest instantPaymentRequest) {
log.debug("start validatePayment method {}", instantPaymentRequest);
....
The InstantPaymentRequest is as follows:
#Data
#Validated
public class InstantPaymentRequest {
#Valid
private PaymentIdentificationRequest paymentIdentification;
#NotBlank(message = "transactionTypeCode.required")
private String transactionTypeCode;
#Valid
private InstructedAmount instructedAmount;
#Valid
private CustomerRequest debtor;
The instructed amount is as follows:
#Data
public class InstructedAmount {
#NotBlank(message = "currency.required")
private String currency;
#NotBlank(message = "value.required")
private String value;
}
Basically when the instructedAmount is provided in the payload but for example I miss currency attribute in payload, the validation is working fine, the non null error message is displayed correctly and my rest controller endpoint is not called.
However when instructedAmount is not provided in the payload, no mandatory error message is displayed and my rest endpoint is called, it this the correct way or I am missing something?
I thought since attribute of InstructedAmount cannot be null then InstructedAmount also cannot be null/empty.
How to add InstructedAmount not null validation in the above scenario with annotation?
Use #NotNull together with #Valid:
#NotNull
#Valid
private InstructedAmount instructedAmount;
From https://docs.jboss.org/hibernate/stable/validator/reference/en-US/html_single/#section-object-graph-validation:
Note that null values are getting ignored during cascaded validation.

Throw error when properties marked with #JsonIgnore are passed

I have a requirement to mark certain properties in my REST beans as ignored using #JsonIgnore. (I am using Spring Boot). This helps in avoiding these properties in my Swagger REST documentation.
I also would like to ensure that if the client passes these properties, an error is sent back. I tried setting spring.jackson.deserialization.fail-on-unknown-properties=true, but that works only for properties that are truly unknown. The properties marked with #JsonIgnore passes through this check.
Is there any way to achieve this?
I think I found a solution -
If I add #JsonProperty(access = Access.READ_ONLY) to the field that is marked as #JsonIgnore, I get back a validation error. (I have also marked the property with #Null annotation. Here is the complete solution:
#JsonInclude(JsonInclude.Include.NON_NULL)
public class Employee {
#Null(message = "Id must not be passed in request")
private String id;
private String name;
//getters and setters
}
#JsonInclude(JsonInclude.Include.NON_NULL)
public class EmployeeRequest extends Employee {
#Override
#JsonIgnore
#JsonProperty(access = Access.READ_ONLY)
public void setId(String id) {
super.setId(id);
}
}
PS: By adding #JsonProperty(access = Access.READ_ONLY), the property started showing up in Swagger model I had to add #ApiModelProperty(hidden = true) to hide it again.
The create method takes EmployeeRequest as input (deserialization), and the get method returns Employee as response (serialization). If I pass id in create request, with the above solution, it gives me back a ConstraintViolation.
PS PS: Bummer. None of these solutions worked end-to-end. I ended up creating separate request and response beans - with no hierarchical relationship between them.

#Valid for long data type is not working for mandatory check

I have the below input class and when i trigger the api without 'interactionId' param in the input,
I expect validation error message "interactionId cannot be empty" but the validation passes through which i guess could be due to the fact that interactionId has a default value of 0.
Can someone pls. help to enforce this validation on the 'long' parameter when its not given in input?
with #NotEmpty for the customerId param, its working as expected. Using #NotEmpty for the long param "interactionId" is throwing a different error that #notempty cannot be used for long.
public class Input {
#NotEmpty(message = "customerId cannot be empty")
private String customerId;
#Valid
#NotNull(message = "interactionId cannot be empty")
private long interactionId;
// setters and getters
}
my controller class:
#RestController
public class Controller {
#PostMapping(value="/detailed-customer-transaction", produces =
MediaType.APPLICATION_JSON_VALUE)
#ResponseBody
public ResponseEntity<Object> detailTransactions(#Valid #RequestBody Input
params)
{
return new ResponseEntity<>(Dao.detailTransactions(params), HttpStatus.OK);
}
Above issues is resolved after changing to Long instead of long.
Query #2
I need another help. I have a String input param which takes date-time format in below format. Given its a string parameter, how can i validate for the pattern="yyyy-MM-dd'T'HH:mm:ss.SSS'Z'"
long should be Long, because long is a primary type in java, not an object, so Long is an object that can be checked whether it is null

How to remove some fields of an Object in Spring Boot response control?

this is one of my REST controller,
#RestController
#RequestMapping("/users/Ache")
public class Users {
#GetMapping
public User getUser() {
User user = new User();
return user;
}
}
As response, Spring boot will translate my Object to JSON,
this is response:
{
"username": "Ache",
"password": "eee",
"token": "W0wpuLAUQCwIH1r2ab85gWdJOiy2cp",
"email": null,
"birthday": null,
"createDatetime": "2019-03-15T01:39:11.000+0000",
"updateDatetime": null,
"phoneNumber": null
}
I want to remove password and token fields, How can I do?
I know two hard ways:
create a new hash map
and add some necessary fields, but it too complex
set those two fields to null
but it still leaves two null valued fields, it is too ugly.
Any better solution?
Spring leverages Jackson library for JSON marshalling by default. The easiest solution that comes to mind is making use of Jackson's #JsonIgnore but that would ignore the property on both serialization and de-serialization. So the right approach would be annotating the field with #JsonProperty(access = Access.WRITE_ONLY).
For instance, inside a hypothetical User class:
#JsonProperty(access = Access.WRITE_ONLY)
private String password;
#JsonProperty(access = Access.WRITE_ONLY)
private String token;
An alternative would be using #JsonIgnore only on the getter:
#JsonIgnore
public String getPassword() {
return this.password;
}
You can also create another class, for instance UserResponse with all the fields except password and token, and make it your return type. Of course it involves creating an object and populating it, but you leave your User class clean without Jackson annotations, and de-couples your model from your representation.
Keep the getter and setter but add the WRITE_ONLY JsonProperty. This way password validations will work when you use the entity as the request body.
#NotBlank
#JsonProperty(access = Access.WRITE_ONLY)
private String password;

Resources