Spring validation for Kotlin primitives - spring-boot

I have created simple Spring Boot project with using Kotlin 1.4.10.
I have simple DTO in the project:
data class TestRequest(
#field:NotNull val id: Int,
val optionalId: Int?,
val name: String,
val optionalName: String?,
#field:NotNull val value: Double,
val optionalValue: Double?,
val nested: NestedRequest,
val optionalNested: NestedRequest?
)
data class NestedRequest(
#field:NotNull val nestedId: Long,
val nestedOptionalId: Long?,
val nestedName: String,
val optionalNestedName: String?
)
I am wondering, what is best practice to write Kotlin DTO's and validate them?
From one side, Kotlin allows to mark fields as not-null, which seems to be convenient for validation.
From another, in case of Kotlin numeric types (Int, Long, Double etc), which seems to have default value, as Java primitives do, so checking of nullability does not work for such fields unlike string ones.
If I use #JsonProperty(required = true), nullability will be checked by Jackson and not by validator, so this approach is also incorrect.
As a result I've got a question - is there a proper way of validating Kotlin DTO's at all?

As you have noticed, it is hard to validate kotlin primitive types for nulability, because they have default values.
I would say that using a combination of Jackson (for nullability of primitive types) and Javax validation (stuff like min/max value) is fine.
However, if you don't want to use Jackson validation, you can validate primtive types by setting the type of the variable as nullable but annotating it as #NotNull.
For example:
import javax.validation.Valid
import javax.validation.constraints.NotNull
data class MyClass(
#get:Valid
#get:NotNull
val someInt: Int?,
val someText: String
)
Now, because the type is nullable (in this example Int?) Jackson won't insert a default value for someInt, therefore someInt is going to have a value of null. After that, when the object gets validated, an error will be thrown because the value of someInt is null.
For example, if we have the following #PostMapping:
#PostMapping("/test")
fun testFunction(#RequestBody #Valid data: MyClass) {
print(data)
}
Sending a POST request with body:
{
"someText": "wow"
}
Will return an error like this one:
"timestamp": "2020-10-02T15:22:53.361+00:00",
"status": 400,
"error": "Bad Request",
"trace": "org.springframework.web.bind.MethodArgumentNotValidException: Validation failed for argument [0] in public void main.api.TestPublicController.myObject(main.api.MyClass): [Field error in object 'myClass' on field 'someInt': rejected value [null]; ...

Related

Bean Validation on complex objects - Kotlin Spring Boot

I have a data class that is being validated except for the contents of the lists
data class EmailDto(
#get:Valid
#get:NotEmpty
val recipients: List<#Email String>,
#get:NotBlank
val subject: String,
#get:Valid
val cc: List<#Email String> = listOf(),
#get:Valid
val bcc: List<#Email String> = listOf(),
)
So my problem is if I use a value of listOf() for recipients the validation fires and gives me an exception, whereas listOf("test.com") does not cause an exception when it should since the string value isn't a valid email address.
I've tried all different kinds of targets on the annotations and omitted them but I can't seem to get any validation to work on complex fields. Does anyone have any insight on this? I haven't been able to really track anything down searching online.

Kotlin Type Mismatch: Taking String from URL path variable and using it as an ID

My Spring Boot Application in Kotlin has a POST endpoint defined like this:
fun postTermin( #PathVariable("pathID") pathID: String, #Validated #RequestBody termin: RequestBody): ResponseEntity<Appointment> {
return ResponseEntity(HttpStatus.NOT_IMPLEMENTED)
}
I'd like to take that "pathID" and use it to find an entity from a repository like so:
myRepository.findById(pathID)
The CRUDRepository I'm calling there is for an Entity "Dealer" where the ID is defined as:
#Id
#Column(name = "id", length = 10, nullable = false)
open var id: String = ""
The Problem: I get this compile error:
Kotlin: Type mismatch: inferred type is Optional<Dealer!> but Dealer?
was expected
What's the problem here? Why the "Optional"?
The comment by Slaw really helped.
As described in Spring Data JPA How to use Kotlin nulls instead of Optional there's also a "findByIdOrNull" function for CRUDRepository, i had missed that.
For my use case, that works.

Return client error if part of request body in HTTP POST is null in Spring Boot in Kotlin

Spring Boot + Kotlin:
When a required Boolean in request body is not present (or it is null), then it is mapped to false.
How should I improve validation so that client error is returned? I even tried #field:NonNull and #field:Valid but they don't help..
Here's an example:
data class MyRequestBody(
val value1: String,
val value2: Boolean
)
#PostMapping("/blabla")
fun someMethod(
#RequestBody requestBody: MyRequestBody,
): CompletableFuture<MyResponse> = unwrappedAsync {
...
}
For this request:
sample request body: { "value1": "abc", "value2": null }
MyRequestBody contains value1='abc', value2=false
In other words, how to I prevent this null -> false mapping and make it a client error instead?
Why don't you use the java validation with NotNull annotation?
data class MyRequestBody(
val value1: String,
#get:NotNull
val value2: Boolean
)
Even you can use the annotation with the non-null boolean type.

Kotlin - Using data class with type from an interface

I have an overrides interface which I created in order to combine 2 data classes - like so:
interface Overrides
data class SoOverrides(
val soId: String,
val freeInterval: String?
) : Overrides
data class CoOverrides(
val coId: String,
val pubType: String,
) : Overrides
I'm then trying to set the type of an item in my main data class to Overrides like so:
#Document(collection = Campaign.COLLECTION)
data class Campaign(
#Id
val id: String,
val title: String,
val overrides: List<Overrides>? = null
) {
companion object {
const val COLLECTION: String = "campaigns"
}
}
However whenever I go to run this, I get the error:
Caused by: org.springframework.beans.BeanInstantiationException: Failed to instantiate [co.....models.Overrides]: Specified class is an interface
Could someone please explain what I need to do to use that type Overrides. I'm extending the interface that way so I can have multiple classes, under one name, but not sure why it doesnt work?
I guess I could just use val overrides: List<*>? = null
Any help appreciated.
Thanks.

How to configure Spring MVC to validate constructor parameters of controller method arguments

I'm writing a project in Kotlin and have this in a controller:
#PostMapping("/token")
fun generateToken(#RequestBody #Valid credentials: Credentials) { /* something */ }
data class Credentials(#Email #NotBlank val email: String,
#NotBlank val password: String)
By default #Valid annotation tells Spring to validate object fields. But Kotlin places constraint annotations on the constructor parameters, so validation doesn't work. To make it work I have to define use-site targets for annotations:
data class Credentials(#field:Email #field:NotBlank val email: String,
#field:NotBlank val password: String)
which is annoying and adds visual garbage. Is it possible to configure Spring to validate constructor parameters?
There isn't a whole lot you can do. You can make it look a little better by combining annotations for each field, e.g.:
data class Credentials(#field:[Email NotBlank] val email: String,
#field:NotBlank val password: String)
Other than that, your only other options are:
Manually configured Spring validation classes
Validating the data within your code body

Resources