micronaut-data icon indicating copy to clipboard operation
micronaut-data copied to clipboard

micronaut-data-r2dbc|Kotlin Coroutines: Non-null string "NullValue[]" returned rather than null

Open stevenlmcgraw opened this issue 1 month ago • 1 comments

Expected Behavior

APP SETUP

A Kotlin application uses micronaut-data-r2dbc with a Postgresql database.

The application uses Netty/Reactor and Kotlin coroutines (suspend functions).

An entity is defined using @MappedEntity/@MappedProperty/etcetera io.micronaut.data.annotation.* annotations.

A @R2dbcRepository abstract class is defined for the entity that extends io.micronaut.data.repository.kotlin.CoroutineCrudRepository.

EXPECTATION

A query is intended to fetch the value of an individual entity field representing a non-nullable column if the record exists.

If a record exists for the given identifier then the value of the column is returned as expected from the repository method.

If a record does not exist for the given identifier then null is returned from the repository method.

Actual Behaviour

OBSERVED BEHAVIOR

Rather than returning null when a record does not exist for the given identifier, the non-null string "NullValue[]" is returned by the repository method.

This subverts any null checks like the Kotlin elvis operator (?:), leading to unexpected behavior in the application.

The NullValue record class was introduced in this commit.

Older versions of micronaut-data-r2dbc return null as expected in this scenario.

Steps To Reproduce

Example project can be found here. Our production applications that are prevented from upgrading to the most current micronaut-platform use r2dbc-pool and also use the R2DBC workaround for avoiding thread colocation - I have included this configuration in the example to get as close to our actual apps as possible while still keeping the reproducer small.

Example failed build.

^^^logs in this build demonstrate the behavior; search for "ARGS" to see log message from application code. The io.kotest.assertions.AssertionFailedError can also be searched to see the non-null "NullValue[]" string as well.

Otherwise, to run locally:

  • JDK 17
  • Needs Docker environment for Testcontainers Postgresql container to run
  • ./gradlew clean test

Environment Information

Operating system:

  • MacOS (local runs)
  • Ubuntu (GitHub runner)

JDK 17

Kotlin -> 2.1.21 KotlinX -> 1.10.2

Micronaut Platform -> 4.10.2

Looks to have come about when NullValue record class was introduced in this commit

Example Application

https://github.com/stevenlmcgraw/coroutine-r2dbc-null-value

Version

micronaut-platform:4.10.2

stevenlmcgraw avatar Nov 25 '25 20:11 stevenlmcgraw

https://github.com/micronaut-projects/micronaut-data/pull/3621

^^^when I build off this locally and run the reproducer the issue described here is not observed.

stevenlmcgraw avatar Nov 26 '25 01:11 stevenlmcgraw