[JAVA-5134] Kotlin driver : Data class with nullable type throw BsonInvalidOperationException when value is explicitly null in the DB Created: 02/Sep/23  Updated: 28/Oct/23  Resolved: 04/Oct/23

Status: Closed
Project: Java Driver
Component/s: BSON, Codecs, Kotlin
Affects Version/s: 4.10.2
Fix Version/s: 4.11.0

Type: Bug Priority: Major - P3
Reporter: Sébastien Pérochon Assignee: Ross Lawley
Resolution: Fixed Votes: 1
Labels: None
Remaining Estimate: Not Specified
Time Spent: Not Specified
Original Estimate: Not Specified

Documentation Changes: Not Needed
Documentation Changes Summary:

1. What would you like to communicate to the user about this feature?
2. Would you like the user to see examples of the syntax and/or executable code and its output?
3. Which versions of the driver/connector does this apply to?


 Description   

Summary

Hello,

I’m using :

-> Kotlin driver => org.mongodb:mongodb-driver-kotlin-sync:4.10.2
(the same issue exists in org.mongodb:mongodb-driver-kotlin-coroutine:4.10.1 too)

-> MongoDB Atlas Dedicated Cluster M10 v6.1

 

I have a data class with a nullable String field

{{data class MyData( val myfield: String?
)}}

When I read the value from the DB, here are what is printed on console :

  1. if myfield does NOT exist in the DB => it successfully prints “MyData(myfield=null)”
  1. if myfield exists and contains “myvalue” => it successfully prints “MyData(myfield=myvalue)”
  1. if myfield exists and contains null value (BSONType is NULL) => I would expect that it prints “MyData(myfield=null)” but it is not!? It throws the BsonInvalidOperationExceptionreadString can only be called when CurrentBSONType is STRING, not when CurrentBSONType is NULL.
    Could you please tell me what’s wrong? Thanks!

How to Reproduce

Here is a very simple code to reproduce (replace XXX, YYY, ZZZ with your values)

 

data class MyData(
  val myfield: String?
)
fun main(args: Array<String>) {
  val uri = "XXXXXXXXX"
  val databaseName = "YYYYYYYY"
  val collectionName = "ZZZZZZZ"
  val mongoClient = MongoClient.create(uri)
  val db = mongoClient.getDatabase(databaseName)
  val collection = db.getCollection<MyData>(collectionName)
  val doc = collection.find().firstOrNull()
  if (doc != null) {
    println(doc)
  } else {
    println("No matching documents found.")
  }
  mongoClient.close()
}

 

Additional Background

Here is the exception trace

 

Exception in thread "main" org.bson.codecs.configuration.CodecConfigurationException: Unable to decode myfield for MyData data class.	at org.bson.codecs.kotlin.DataClassCodec.decode(DataClassCodec.kt:92)	at com.mongodb.internal.operation.CommandResultArrayCodec.decode(CommandResultArrayCodec.java:52)	at com.mongodb.internal.operation.CommandResultDocumentCodec.readValue(CommandResultDocumentCodec.java:60)	at org.bson.codecs.BsonDocumentCodec.decode(BsonDocumentCodec.java:87)	at org.bson.codecs.BsonDocumentCodec.decode(BsonDocumentCodec.java:42)	at org.bson.internal.LazyCodec.decode(LazyCodec.java:53)	at org.bson.codecs.BsonDocumentCodec.readValue(BsonDocumentCodec.java:104)	at com.mongodb.internal.operation.CommandResultDocumentCodec.readValue(CommandResultDocumentCodec.java:63)	at org.bson.codecs.BsonDocumentCodec.decode(BsonDocumentCodec.java:87)	at org.bson.codecs.BsonDocumentCodec.decode(BsonDocumentCodec.java:42)	at com.mongodb.internal.connection.ReplyMessage.<init>(ReplyMessage.java:48)	at com.mongodb.internal.connection.InternalStreamConnection.getCommandResult(InternalStreamConnection.java:565)	at com.mongodb.internal.connection.InternalStreamConnection.receiveCommandMessageResponse(InternalStreamConnection.java:455)	at com.mongodb.internal.connection.InternalStreamConnection.sendAndReceive(InternalStreamConnection.java:370)	at com.mongodb.internal.connection.UsageTrackingInternalConnection.sendAndReceive(UsageTrackingInternalConnection.java:114)	at com.mongodb.internal.connection.DefaultConnectionPool$PooledConnection.sendAndReceive(DefaultConnectionPool.java:719)	at com.mongodb.internal.connection.CommandProtocolImpl.execute(CommandProtocolImpl.java:76)	at com.mongodb.internal.connection.DefaultServer$DefaultServerProtocolExecutor.execute(DefaultServer.java:203)	at com.mongodb.internal.connection.DefaultServerConnection.executeProtocol(DefaultServerConnection.java:115)	at com.mongodb.internal.connection.DefaultServerConnection.command(DefaultServerConnection.java:83)	at com.mongodb.internal.connection.DefaultServerConnection.command(DefaultServerConnection.java:74)	at com.mongodb.internal.connection.DefaultServer$OperationCountTrackingConnection.command(DefaultServer.java:287)	at com.mongodb.internal.operation.CommandOperationHelper.createReadCommandAndExecute(CommandOperationHelper.java:245)	at com.mongodb.internal.operation.FindOperation.lambda$execute$1(FindOperation.java:324)	at com.mongodb.internal.operation.OperationHelper.lambda$withSourceAndConnection$0(OperationHelper.java:345)	at com.mongodb.internal.operation.OperationHelper.withSuppliedResource(OperationHelper.java:370)	at com.mongodb.internal.operation.OperationHelper.lambda$withSourceAndConnection$1(OperationHelper.java:344)	at com.mongodb.internal.operation.OperationHelper.withSuppliedResource(OperationHelper.java:370)	at com.mongodb.internal.operation.OperationHelper.withSourceAndConnection(OperationHelper.java:343)	at com.mongodb.internal.operation.FindOperation.lambda$execute$2(FindOperation.java:321)	at com.mongodb.internal.operation.CommandOperationHelper.lambda$decorateReadWithRetries$3(CommandOperationHelper.java:192)	at com.mongodb.internal.async.function.RetryingSyncSupplier.get(RetryingSyncSupplier.java:67)	at com.mongodb.internal.operation.FindOperation.execute(FindOperation.java:332)	at com.mongodb.internal.operation.FindOperation.execute(FindOperation.java:72)	at com.mongodb.client.internal.MongoClientDelegate$DelegateOperationExecutor.execute(MongoClientDelegate.java:153)	at com.mongodb.client.internal.FindIterableImpl.first(FindIterableImpl.java:213)	at com.mongodb.kotlin.client.MongoIterable.firstOrNull(MongoIterable.kt:38)	at MainKt.main(Main.kt:20)Caused by: org.bson.BsonInvalidOperationException: readString can only be called when CurrentBSONType is STRING, not when CurrentBSONType is NULL.	at org.bson.AbstractBsonReader.verifyBSONType(AbstractBsonReader.java:689)	at org.bson.AbstractBsonReader.checkPreconditions(AbstractBsonReader.java:721)	at org.bson.AbstractBsonReader.readString(AbstractBsonReader.java:456)	at org.bson.codecs.StringCodec.decode(StringCodec.java:80)	at org.bson.codecs.StringCodec.decode(StringCodec.java:31)	at org.bson.codecs.DecoderContext.decodeWithChildContext(DecoderContext.java:96)	at org.bson.codecs.kotlin.DataClassCodec.decode(DataClassCodec.kt:90)	... 37 more 

 

Here is my build.gradle.kts

plugins {
    kotlin("jvm") version "1.9.10"
    application
}
 
group = "org.example"
version = "1.0-SNAPSHOT"
 
repositories {
    mavenCentral()
}
 
dependencies {
    testImplementation(kotlin("test"))
    implementation("org.mongodb:mongodb-driver-kotlin-sync:4.10.2")
    //implementation("org.mongodb:mongodb-driver-kotlin-coroutine:4.10.1")
}
 
tasks.test {
    useJUnitPlatform()
}
 
kotlin {
    jvmToolchain(8)
}
 
application {
    mainClass.set("MainKt")
}

Thanks for your help!

 



 Comments   
Comment by Githook User [ 04/Oct/23 ]

Author:

{'name': 'Ross Lawley', 'email': 'ross@mongodb.com', 'username': 'rozza'}

Message: Kotlin. Support stored nulls for nullable fields (#1212)

JAVA-5134
Branch: master
https://github.com/mongodb/mongo-java-driver/commit/6a20fb6bc7c95edd1731774722dfe3644361e772

Comment by Jeffrey Yemin [ 13/Sep/23 ]

Thanks for sharing your workaround sebastien.perochon@mobiquite.fr. We do plan to address this soon.

Comment by Ben Hammond [ 13/Sep/23 ]

I have also been wrestling with this problem.

I have devised a workaroud by 

    • take note of the `propertyModel.param.type.isMarkedNullable` value
    • if the call to `decoderContext.decodeWithChildContext()`
      • chucks a BsonInvalidOperationException
      • AND is marked nullable
      • AND we successfully call `reader.readNull()`
    • then return null
    • other wise rethrow the exception 

 

This workaround seems to be good enough for me for the moment.

although it would be nicer to NOT have a bunch of BsonInvalidOperationExceptions flying around as part of 'normal' function

 

Comment by Sébastien Pérochon [ 07/Sep/23 ]

Hi tom.selander@mongodb.com thanks for your reply.

Here is another issue very very close to this one (don't know if I had to create a new one...)
This time we have a NON nullable type that receive a null value without problem!

data class MyData(
  val myfield: List<String>,
)

if myfield exists in DB and is an Array (BSONType is Array) containing one null value (BSONType is NULL) => it prints

MyData(myfield=[null])

which is impossible!???

Thanks!

Comment by Tom Selander [ 05/Sep/23 ]

Hi sebastien.perochon@mobiquite.fr this does look like a bug, we'll investigate this and look to get it fixed. Thanks for reporting!

Comment by PM Bot [ 02/Sep/23 ]

Hi sebastien.perochon@mobiquite.fr, thank you for reporting this issue! The team will look into it and get back to you soon.

Generated at Thu Feb 08 09:03:50 UTC 2024 using Jira 9.7.1#970001-sha1:2222b88b221c4928ef0de3161136cc90c8356a66.