Getting JSON parse error on JSON property createdAt when not included in request body - spring-boot

I am recently learning to build REST APIs with Kotlin and Spring Boot. I was trying to create my first domain model and controller end-point. Here is the implementation of the Country model and CountryController countroller.
/model/Country.kt
package com.example.example.model
import jakarta.persistence.Column
import jakarta.persistence.Entity
import jakarta.persistence.EntityListeners
import jakarta.persistence.GeneratedValue
import jakarta.persistence.GenerationType
import jakarta.persistence.Id
import jakarta.persistence.Table
import org.springframework.data.annotation.CreatedDate
import org.springframework.data.annotation.LastModifiedDate
import org.springframework.data.jpa.domain.support.AuditingEntityListener
import java.sql.Timestamp
#Entity
#Table(name = "country")
class Country (
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "country_id")
val id: Int,
val code: String,
val displayName: String,
#CreatedDate
#Column(name = "created_at", nullable = false, updatable = false)
val createdAt: Timestamp,
#LastModifiedDate
#Column(name = "updated_at", nullable = false)
val updatedAt: Timestamp
)
/controller/CountryCountroller.kt
package com.example.example.controller
import com.example.example.model.Country
import com.example.example.repository.CountryRepository
import jakarta.validation.Valid
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.web.bind.annotation.RequestBody
import org.springframework.web.bind.annotation.RestController
import org.springframework.http.MediaType
import org.springframework.web.bind.annotation.PostMapping
#RestController
class CountryController {
#Autowired
lateinit var countryRepository: CountryRepository
#PostMapping("/countries", consumes = arrayOf(MediaType.APPLICATION_JSON_VALUE))
fun createCountry(#Valid #RequestBody country: Country): Country {
return countryRepository.save(country);
}
}
Schema of the Country table:
country_id INT(10) UNSIGNED NOT NULL AUTO_INCREMENT,
code VARCHAR(3) NOT NULL,
displayName VARCHAR(40) NOT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (country_id)
When I started running the Spring Boot application and use Postman to call the POST /countries API, I received a 400 Bad Request error. The application error log says:
2023-01-12T14:09:15.423Z WARN 90037 --- [nio-8080-exec-3] .w.s.m.s.DefaultHandlerExceptionResolver : Resolved [org.springframework.http.converter.HttpMessageNotReadableException: JSON parse error: Instantiation of [simple type, class com.example.example.model.Country] value failed for JSON property createdAt due to missing (therefore NULL) value for creator parameter createdAt which is a non-nullable type]
The request body is a json and the request header has include the correct Content-Type information:
{
"code": "FRA",
"displayName": "France"
}
I suspected that the Spring Boot did not ignore the two fields createdAt and updatedAt, therefore I have tried to add #JsonIgnore above createdAt and updatedAt. Unfortunately the same 400 Bad Request error was still returned.
Do I actually have to include createdAt and updatedAt in the JSON request body? However, what I wanted to achieve is that the createdAt and updatedAt should be added/updated either by the Spring Boot application putting the createdAt and updatedAt data when calling the repository methods or by MySQL DB when the query is being run. Is there any way to do so?

You are mixing up two concepts here
whether on not createdAt and updatedAt are allowed be null in your data model
serialization:
whether you want the RestController to deserialize the fields when you receive them in the POST
whether you want the RestController to serialize the fields when you implement a GET request
By using #JsonIgnore (or the Kotlin variation #set:JsonIgnore you are just avoiding the data deserialization but still the data model requires a value. The JPA annotations are going to get executed when you put the data in the database, but in the time that the object is created by the MVC infrastructure, and before the database save happens, the data does not match the model contract.
There are two solutions to what you want to do:
Relax the data model
Change your data model to allow nulls in the two fields:
#CreatedDate
#Column(name = "created_at", nullable = false, updatable = false)
val createdAt: Timestamp?,
#LastModifiedDate
#Column(name = "updated_at", nullable = false)
val updatedAt: Timestamp?
BTW I would expect you to get the error on id as well, perhaps an Exception would be given for that once you dealt with the two dates, because again, your POST data does not supply id and this not nullable.
Use a Request object
Create a simpler data model that is used to receive your POSTed data and then create the Domain/Database model object from it.
I use a mix of the two techniques depending on circumstances. The latter approach has benefit, e.g. when you have large objects which may only be partially sent by the client (think PATCH).

Related

When does an autogenerated UUID Id get generated with spring data JPA and hibernate?

Suppose you have a class with the following UUID primary key:
import javax.persistence.*;
import org.hibernate.annotations.GenericGenerator;
#Entity
public class MyEntity {
#Id
#GeneratedValue(generator = "UUID")
#GenericGenerator(name = "UUID", strategy = "org.hibernate.id.UUIDGenerator")
private UUID id;
// other fields and constructor omitted for brevity
}
When you create a new MyEntity(), the id property is null. That's something I'm used to when the database is responsible for generating the id. In those cases, you must save the entity first before the id is populated. In this case the underlying database is a postgres database with id column type UUID, but the application should be able to generate the Id.
Is there a reason it wouldn't generate automatically and immediately?
Reference: https://thorben-janssen.com/generate-uuids-primary-keys-hibernate/

Spring Data Cassandra Naming Strategy

Kotlin + Spring Boot Cassandra App is ignoring #Column("column_name") annotations. The below annotation
#Column("order_details")
val orderDetails: String
Is being mapped to orderdetails instead of order_details. As the column orderdetails doesn't exist in the table, it causes a run time error.
Why doesn't spring data Cassandra's #Column map to snake case by default?
Even after the value is being passed to #Column mapping, why is it being ignored?
Is there any other property that needs to be set in the app properties so that spring data Cassandra uses snake case for physical mapping?
#Table("order_by_customer")
data class CustomerOrder(
#PrimaryKeyColumn(name = "customer_id", ordinal = 0, type = PrimaryKeyType.PARTITIONED)
val customerId: UUID,
#Column("order_details")
val orderDetails: String,
#Column("ordered_at")
val orderedAt: LocalDate
)
Generated insert query
INSERT INTO order_by_customer (customer_id,orderdetails,orderedat)
While annotation for PK work the ones for Column are being ignored
Per the resolution provided by spring-data-cassandra team. Data class need to be coded as shown below
#Table("order_by_customer")
data class CustomerOrder(
#PrimaryKeyColumn(name = "customer_id", ordinal = 0, type = PrimaryKeyType.PARTITIONED)
val customerId: UUID,
#field:Column("order_details")
val orderDetails: String,
#field:Column("ordered_at")
val orderedAt: LocalDate
)
https://github.com/spring-projects/spring-data-cassandra/issues/1146
https://kotlinlang.org/docs/annotations.html#annotation-use-site-targets

NotNull constraints aren't being enforced when not set

NotNull constraints aren't being enforced, when the attribute is not set. Is there anything which needs to be additionally set to enforce null values
import javax.validation.constraints.NotNull;
#NotNull(message = "PaymentType is required")
#JsonProperty(PAYMENT_TYPE)
#Field(type = FieldType.Keyword, name = PAYMENT_TYPE)
private PaymentType paymentType;
As you are having a #JsonProperty(PAYMENT_TYPE) annotation on that property I assume you are using this entity not only to store data in Elasticsearch, but also are sending it to and receiving from some client application? Then the validation should happen when you get this data in. Spring Data Elasticsearch does not support validation on properties.

Spring validation for Kotlin primitives

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]; ...

Spring WebClient setting some fields to null when they are not null in the response body

I have domain class
import org.springframework.data.mongodb.core.mapping.Document;
import lombok.Data;
#Data
#Document
public class Bar {
#Id
private String id;
private List<String> owners;
private List<String> cFeatures;
private Integer age;
private String color;
}
I am using below code to invoke API to get data in Bar object:
import org.springframework.web.reactive.function.client.WebClient;
Mono<Bar> prop = webClient.get()
.uri("/bars/"+id)
.header("Authorization", "Bearer " + access_token)
.accept(MediaType.APPLICATION_JSON)
.retrieve()
.bodyToMono(Bar.class).log("find by id")
The problem is that I get cFeatures as null even though original JSON response
has:
"cFeatures":["custom feature one", ""]
but owners list gets correct value even though owners also has empty string value in the list (not sure if thats the source of this bug)
so Bar object has:
cFeatures: null
Is this a bug in Webclient or am I missing something ? I spent whole day on this but no fix yet.
The problem was with lombok. Lombok was generating setter method:
setCFeatures
but jackson expects setter:
setcFeatures which it does not find and hence null value for cFeatures.
It can be helpful if you make sure your POJO has the correct annotation style. For example, use jsonscheme2pojo.org and choose "Json" as your source type and "Jackson 2.x" as your annotation style. That should make the problem disappear. I was stuck at first by the same problem because I used a Gson-annotated POJO.

Resources