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

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")

Related

Validate complex object on GET request

I'm trying to apply input validation on a complex parameter of a get request.
What I have:
data class FilterDTO (
#Valid
#NotNull
val id: Long? = null,
#NotNull
val code: String? = null
)
#Validated
#RestController
#RequestMapping("/foo")
class FooController {
#GetMapping
fun get(
#Valid filter: FilterDTO,
#Valid #NotNull #RequestParam("bar") bar: String?
) {
// ...
}
}
The above endpoint validates correctly the #NotNull on the bar parameter but it seems to ignore the validation on the complex FilterDTO object.
I have tried:
Adding #Valid on FilterDTO's properties (even with Kotlin's #field: and #get:)
Adding #Validated (??) on the FilterDTO whole class
Couldn't make it work.
Is it possible to have a complex get parameter validated?
Thanks
Just found the problem. For anyone getting here, I should be annotating with #field: the validations I want. So simply changing my DTO to this worked:
data class FilterDTO (
#field:NotNull
val id: Long? = null,
#field:NotNull
val code: String? = null
)

RequestBody and camel case request parameters

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;

What does #get:NotNull mean in Kotlin?

I read a code generated by khipster and in one dataclass I found such fragment:
import javax.validation.constraints.NotNull
data class MyDTO(
var id: Long? = null,
#get: NotNull
var name: String? = null,
What does #get:NotNull annotation mean? As far as I understand #get means that I want to annotate the getter of name property and NotNull is a validation annotation which mean that the thing can't be set to null. But how the two work together? It doesn't make any sense to annotate getter with annotation which means this can't be set to null, because getter can't be set. It would make more sens to use NotNull annotation on setter.
#NotNull on a method means it can't return null. So in particular annotating a setter with it makes no sense; annotating the setter's parameter does.
If you use the decompile feature of IntelliJ ( please check this answer )
Kotlin Code:
#get: NotNull
var uid: String? = null
Decompiled Code:
import javax.validation.constraints.NotNull;
import org.jetbrains.annotations.Nullable;
#Nullable
private String uid;
#NotNull
#Nullable
public final String getUid() {
return this.uid;
}
public final void setUid(#Nullable String var1) {
this.uid = var1;
}
What does #get:NotNull annotation mean?
A quick answer: Please put #NotNull annotation on the top of the getter function
!! Please be aware that there is also #Nullable annotation added to the getter function because of the "?" at the end of the variable definition
As you notice from import it is added by IntelliJ
As detailed answer: I could redirect you to "Use-Site Target Declarations"
Bealdung
Blog post
Finally, I would like to express my experience on that, I had both #Nullable and #NotNull annotation on uid field (you could see on decompiled code), I could set that field to null

Kotlin & Spring (Data): custom setters

I currently am working on a project that uses Spring Boot, written in Kotlin. It has got to be mentioned that I am still relatively new to Kotlin, coming from Java. I have a small database consisting of a single table that is used to look up files. In this database, I store the path of the file (that for this testing purpose is simply stored in the the resources of the project).
The object in question looks as follows:
#Entity
#Table(name = "NOTE_FILE")
class NoteFile {
#Id
#GeneratedValue
#Column(name = "id")
var id: Int
#Column(name = "note")
#Enumerated(EnumType.STRING)
var note: Note
#Column(name = "instrument")
var instrument: String
#Column(name = "file_name")
var fileName: String
set(fileName) {
field = fileName
try {
file = ClassPathResource(fileName).file
} catch (ignored: Exception) {
}
}
#Transient
var file: File? = null
private set
constructor(id: Int, instrument: String, note: Note, fileName: String) {
this.id = id
this.instrument = instrument
this.note = note
this.fileName = fileName
}
}
I have created the following repository for retrieving this object from the database:
#Repository
interface NoteFileRepository : CrudRepository<NoteFile, Int>
And the following service:
#Service
class NoteFileService #Autowired constructor(private val noteFileRepository: NoteFileRepository) {
fun getNoteFile(id: Int): NoteFile? {
return noteFileRepository.findById(id).orElse(null)
}
}
The problem that I have is when I call the getNoteFile function, neither the constructor nor the setter of the constructed NoteFile object are called. As a result of this, the file field stays null, whereas I expect it to contain a value. A workaround this problem is to set the fileName field with the value of that field, but this looks weird and is bound to cause problems:
val noteFile: NoteFile? = noteFileService.getNoteFile(id)
noteFile.fileName = noteFile.fileName
This way, the setter is called and the file field gets the correct value. But this is not the way to go, as mentioned above. The cause here could be that with the Spring Data framework, a default constructor is necessary. I am using the necessary Maven plugins described here to get Kotlin and JPA to work together to begin with.
Is there some way that the constructor and/or the setter does get called when the object is constructed by the Spring (Data) / JPA framework? Or maybe should I explicitly call the setter of fileName in the service that retrieves the object? Or is the best course of action here to remove the file field as a whole and simply turn it into a function that fetches the file and returns it like that?

Doing JSR-303 validation in logical order

I have such field in my domain model class validation constraints:
#Column(nullable = false, name = "name")
#NotEmpty(groups = {Envelope.Insert.class, Envelope.Update.class})
#Size(min = 3, max = 32)
private String name;
When this field is empty ("") or null, validator produces both "cannot be empty" and "size must be between..." error messages. I understand it, but when I show this validation error to the client, it seems quite odd (because when something is null / empty it cannot fulfill size requirement, it's not a logical).
Is there some way how to tell Spring to do validation in proper order? If is not #NotEmpty then do not check #Size, and when #NotEmpty is fulfilled check #Size.
According to Hibernate official document:
By default, constraints are evaluated in no particular order and this
regardless of which groups they belong to. In some situations,
however, it is useful to control the order of the constraints
evaluation. In order to implement such an order one would define a new
interface and annotate it with #GroupSequence defining the order in
which the groups have to be validated.
At first, create two interface FirstOrder.class and SecondOrder.class and then define a group sequence inside OrderedChecks.java using #GroupSequence annotation.
public interface FirstOrder {
}
public interface SecondOrder {
}
#GroupSequence({FirstOrder.class, SecondOrder.class})
public interface OrderedChecks {
}
Finally, add groups in your bean constraints annotations.
#Column(nullable = false, name = "name")
#NotEmpty(groups = {FirstOrder.class, Envelope.Insert.class, Envelope.Update.class})
#Size(min = 3, max = 32, groups=SecondOrder.class)
private String name;
The following example is taken from the JSR-303 docs
public class Address {
#NotEmpty(groups = Minimal.class)
#Size(max=50, groups=FirstStep.class)
private String street1;
#NotEmpty(groups=SecondStep.class)
private String city;
#NotEmpty(groups = {Minimal.class, SecondStep.class})
private String zipCode;
...
public interface FirstStep {}
public interface SecondStep {}
#GroupSequence({Firststep.class, SecondStep.class})
public interface Total {}
}
and calling the validator like this
validator.validate(address, Minimal.class, Total.class);

Resources