Adriano Todaro
Adriano Todaro

Reputation: 351

Kotlin Map type and the return value of JdbcTemplate.queryForMap()

I have recently come across a Kotlin/Spring Boot showing a behaviour that I don't fully understand.

Let's start from a very textbook-like pure Kotlin example:

val map: Map<String, Any> = mapOf(
   "name": "John",
   "age": 21,
   "job": null
)

The code snippet above will lead, very understandably, to a compiler error, reminding us that because of that ("job", null) pair we are in fact assigning something that is a Map<String, Any?> to a variable of Map<String,Any> type, which is not allowed.

Now let's suppose we are using Kotlin with Spring Boot. Let's also suppose we have a database (for instance, MySQL) table called PERSON with three columns:

NAME of type varchar (not nullable)
AGE of type int (not nullable)
JOB of type varchar (nullable)

Let's also suppose that we have a row in our table whose data match the example data we mentioned earlier:

'John', 21, NULL

using a JdbcTemplate instance we can do the following:

val map: Map<String, Any> = jdbcTemplate.queryForMap("SELECT * FROM PERSON WHERE NAME=John")
println(map)

The surprising result, for me, is that this last snippet of code brings to no errors and prints out

{NAME=John, AGE=21, JOB=null}

So it looks like we are successfully including a Pair<String, Any?> in a Map<String, Any>

Is this intended? And what's the logic behind it?

Upvotes: 0

Views: 1017

Answers (1)

gidds
gidds

Reputation: 18557

This is a Java interoperability issue.

The JVM itself doesn't have non-nullable types.  The Kotlin compiler implements these by keeping track of the types when compiling.

This works really well within pure Kotlin code, but there are corner cases when interoperating with code written in Java or other JVM languages.  The Kotlin compiler has some workarounds: it uses and recognises standard annotations to indicate nullability; some Java code uses these (and IDEs can use them to show warnings).  It inserts checks when nulls could be passed from non-Kotlin code.  And it uses platform types to indicate when it can't determine the nullability of values from non-Kotlin code.  However, it can't cope with every situation.

In particular, generics have all sorts of awkward corner cases already, due to type erasure.  And that's what we have here: the org.springframework.jdbc.core.JdbcTemplate class is written in Java, so it too knows nothing about non-nullable types.  And the problem is in type parameters, which are erased so that the compiled code only knows about the raw Map.  So the Kotlin compiler can't tell whether the type parameters of the returned object should be nullable or not.

This is compounded because you specify the return type in your code.  If you let the compiler infer it:

val map = jdbcTemplate.queryForMap("SELECT * FROM PERSON WHERE NAME='John'")

…then it would infer the type parameters as <String!, Any!> — the !s indicating platform types, where it can't tell the nullability.  But because you've specified them, the compiler assumes you know what you're doing, and doesn't give any further warnings or checks.

I guess the moral of this is not to specify a return type unless you're sure it's correct, because when interoperating with non-Kotlin code the compiler can't always check you're right!

Upvotes: 1

Related Questions