jackson-module-kotlin: MissingKotlinParameterException: due to missing (therefore NULL) value for creator parameter … which is a non-nullable type

We ran into the following issue when we tried to deserialize a JSON string coming from our Spring rest controller.

 FATAL EXCEPTION: main
    Process: com.example.driver, PID: 28305
    java.lang.IllegalStateException: Failed to load DataModel: com.fasterxml.jackson.module.kotlin.MissingKotlinParameterException: Instantiation of [simple type, class com.example.model.StorageDevice] value failed for JSON property storageDeviceId due to missing (therefore NULL) value for creator parameter storageDeviceId which is a non-nullable type
        at [Source: (okhttp3.ResponseBody$BomAwareReader); line: 1, column: 1764] (through reference chain: com.example.model.DataModel["storageDevices"]->java.util.ArrayList[0]->com.example.model.cargospace.StorageDevice["storageDeviceId"])
        at com.example.model.RestTourRepository$updateDataModel$1.onFailure(RestTourRepository.kt:38)
        at retrofit2.ExecutorCallAdapterFactory$ExecutorCallbackCall$1$2.run(ExecutorCallAdapterFactory.java:79)
        at android.os.Handler.handleCallback(Handler.java:751)
        at android.os.Handler.dispatchMessage(Handler.java:95)
        at android.os.Looper.loop(Looper.java:154)
        at android.app.ActivityThread.main(ActivityThread.java:6121)
        at java.lang.reflect.Method.invoke(Native Method)
        at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:889)
        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:779)

The object we try to construct looks like this:

@JsonIdentityInfo(
        generator = ObjectIdGenerators.PropertyGenerator::class,
        property = "storageDeviceId"
)
data class StorageDevice (
    val storageDeviceId: String,
    val configuration: AbstractStorageDeviceConfiguration,
    val storageDeviceLocation: StorageDeviceLocation
)

And the JSON string we receive is the following:

{
    "storageDeviceId": "71ba49f3-d8c8-425f-b7e4-29b8c49f417d",
    "id": 166,
    "configuration": {
        "storageDeviceConfigurationId": "c2c926b3-de70-491a-9f8d-514830d56a4b",
         …
    },
    "storageDeviceLocation": "1f64613b-6e96-4825-a489-faa00608516e",
    "new": false
}, …

We solved the issue by changing the val storageDeviceId: String to val storageDeviceId: String?. The value was then properly set after deserialization.

The question arises why we had to set it to nullable in the first place?

Versions: jackson-module-kotlin:2.9.0 jackson-annotations:2.9.0

About this issue

  • Original URL
  • State: closed
  • Created 7 years ago
  • Reactions: 14
  • Comments: 27 (8 by maintainers)

Commits related to this issue

Most upvoted comments

@asarkar Please do note that this is a fully volunteer-based open source project. We all have our lives, and participation here has to compete with many other things which generally are more important (family for example). So although I trust you did not mean your comment to sound like it, it came across bit strong as if there was some entitlement to quick service, solely based on the fact that you would like to see it fixed. There is no service expectation, and asking for status updates is typically not very helpful in getting things to move.

Of course there are no service guarantees, let alone entitlements. However, what is lacking here is the acknowledgement that the reported issue is a bug, and will be addressed in a future release. “this is open source” argument is too much of a cliche, isn’t it? I work with and contribute to open sources myself, and one thing I often see is a strong resistance and/or obliviousness to problem reports, and putting the onus squarely on the reporter as if it’s their fault having found an issue with the code.

@apatrida this issue is open for several months, and more than one person has reported it. Is there something else we need to do to get your attention and get it fixed?

Branch 2.10 now allows null to be treated as “use the default” value, but requires that you set a flag when creating the Kotlin module, otherwise the behavior stays as it was as the default behavior.

ObjectMapper().registerModule(KotlinModule(nullisSameAsDefault = true)

@apatrida if it is the kotlin module, it needs to be done the kotlin way. Nullable type should not be forced to use to solve the problem here. You can argue using default value explicitly can solve my problem but it would be much nicer the kotlin module can do it for us, as there are default convention values like “”, emptyMap() that one would usually use when declaring a member variable. So we can type less characters and be more productive

@asarkar And yet you are asking for specific actions, trying to read motivations into lack of updates, most easily explained by lack of time to spend.

As to “this is open source”… It is literally something maintainers do for free, and has to compete with other priorities. As you probably know well given your work in field of OSS.

I’m having a similar problem with Map<k,v> type. The exception will be thrown when the map property doesn’t exist in the json string.

For deserilization In kotlin, probably it is reasonable to have an default value assigned to none-nullable types as the none-nullable types are encouraged at the kotlin language level. It should be acceptable.

A set of defaults in my opinion:

String: ""
Map<k,v>: emptyMap()
MutableMap<k,v>: mutableMapOf()
Number: 0
...

Leaving a comment here because this is the first result on Google for this issue. I was having a problem on Android where I would get these errors only at runtime. I had unit tests to verify that jackson and jackson-module-kotlin could handle the types I was using, so I was very confused.

In the end, I needed to add additional proguard rules. Apparently something was getting stripped out during the Android build that jackson needs.

# Without these lines, jackson will fail to create any immutable/nonnull kotlin classes
-keep class kotlin.** { *; }
-keep class kotlin.Metadata { *; }
-dontwarn kotlin.**
-keepclassmembers class **$WhenMappings {
    <fields>;
}
-keepclassmembers class kotlin.Metadata {
    public <methods>;
}
-assumenosideeffects class kotlin.jvm.internal.Intrinsics {
    static void checkParameterIsNotNull(java.lang.Object, java.lang.String);
}

I’d be strongly against Jackson inventing defaults for properties and covering up for them being missing in the input. If a property defaults to emptyMap() when not specified, a constructor parameter default is a precise way of specifying that, no magic “implicit defaults” needed or desired.

@apatrida this works fine as long as we create a new instance of ObjectMapper. However, if ObjectMapper is autowired, it still gives that error.

In my case, I have:

@Component
class Demo(val objectMapper: ObjectMapper) {
  init {
    objectMapper.registerModule(KotlinModule(nullisSameAsDefault = true)
  }
}

in a jar where objectMapper is autowired and I’m calling this jar from another Spring application and I’m running into this error. However, if I do not autowire:

@Component
class Demo {
  val objectMapper = ObjectMapper()
  init {
    objectMapper.registerModule(KotlinModule(nullisSameAsDefault = true)
  }
}

it works as expected.

Same here. This is a bug; if the property has a default value, Jackson should use that and not treat it as null. An empty collection is not null.

@cowtowncoder no. And I’m still against this issue as a default. It would be the same in Java as saying “A Java property marked with @NotNull of type Map should be created with an empty map when null is present in the JSON” … that would not be the case, correct? Defaults are only applied to primitives in a way that Java would default them, not to object types.

The creator of the class can easily set a default of their own. The original message from @steffenkolb that started this issue could easily have set a default value for the storageDeviceId fields as storageDeviceId: String = "" but they chose to instead change an important default behavior of null checking in this module. And maybe it is because null is present in their JSON that they cannot work around, that they want the module to change and loosen its type/value checking. Instead it would be better to have a configuration of treating null as absent when a defaultable vaue is available, but then still require the author of the code to choose that default and not try to guess what would work for everyone.

And not as a default behavior, but something as an option.

Quick question: is this same as #130 – fix for it was just merged.

Have same issue.

data class SignUpReq(
        @get:NotNull
        @get:NotEmpty
        @get:Email
        val email: String,

        @get:NotNull
        @get:NotEmpty
        @get:Size(min = 6, max = 20)
        val username: String,

        @get:NotNull
        val password: String
)

This is a request dto which should be validated in the controller and passed down to the service layer.

It’s bad idea to make fields nullable since they are actually required, so we loose null-safety of kotlin here.

On the other hand, if we assign default values to fields (like val password: String = "") and we do not provide password in the request body, dto will be constructed with empty password field, which is not what we want.

So neither of solutions work.

One workaround is to create 2 dtos. One for the controller with nullable fields and another with same but not-nullable fields for the service. Then create service dto from controller’s dto in the controller like this: val serviceDto = ServiceSignUpReq(email = ctrlDto.email!!, ...)

But it is ugly and requires extra boilerplate .

So yes, it is a problem and it needs to be solved ASAP.

If this issue appears with Spring Controllers(or another controllers) its semantically correct to setup ControllerAdvice for this issue with 400 status and representative error message, when DeserializationFeature.FAIL_ON_NULL_FOR_PRIMITIVES is on in configuration(maybe other options should be included)

Because - when jackson got field:null and try to set it - its correct (default value should be used when value is absent - it event called default =) ) image So when request try to put null to not null its bad request

So the point - is it bug or missconfiguration/missunderstanding?

@zdevwu you can set default values in your constructor or property setting, and the module will see it and let it be set without complaint.

@asarkar do you have a specific case that is failing where you set a default value and it is ignored? Or are you wanting implicit defaults (implicit defaults are not going to happen, that would not allow for missing value checking)