others-how to solve `java.lang.ClassCastException: class java.util.LinkedHashMap cannot be cast to class xxx` when trying to run an kotlin app?

1. Purpose

In this post, I will demonstrate how to solve the error java.lang.ClassCastException: class java.util.LinkedHashMap cannot be cast to class xxx when using kotlin to consume a springboot restful webservice.

The error

Here is the detailed error stacktrace of my error:

14:08:08.403 [main] DEBUG org.springframework.web.client.RestTemplate - HTTP GET http://localhost:8081/msg/
14:08:08.437 [main] DEBUG org.springframework.web.client.RestTemplate - Accept=[application/json, application/*+json]
14:08:08.482 [main] DEBUG org.springframework.web.client.RestTemplate - Response 200 OK
14:08:08.494 [main] DEBUG org.springframework.web.client.RestTemplate - Reading to [java.util.List<?>]
got 3 messages as follows:
Exception in thread "main" java.lang.ClassCastException: class java.util.LinkedHashMap cannot be cast to class com.example.domain.Message (java.util.LinkedHashMap is in module java.base of loader 'bootstrap'; com.example.domain.Message is in unnamed module of loader 'app')
    at com.example.web.WebApplicationKt.test1(WebApplication.kt:22)
    at com.example.web.WebApplicationKt.main(WebApplication.kt:14)

> Task :api-web:bootRun FAILED

Execution failed for task ':api-web:bootRun'.
> Process 'command '/Library/Java/JavaVirtualMachines/jdk-11.0.17.jdk/Contents/Home/bin/java'' finished with non-zero exit value 1

* Try:
> Run with --stacktrace option to get the stack trace.
> Run with --info or --debug option to get more log output.
> Run with --scan to get full insights.

The JDK

We are using the JDK 11:

java 11

The codes that caused the error

The codes that caused the above error are as follows:

1) The rest controller that provide the restful service:

@RestController
class MessageController {

    @GetMapping
    fun index(): List<Message> {
        var result = listOf(
            Message("1","hello1"),
            Message("2","Object"),
            Message("3","Privot"),
        )
        return result
    }
}

You can see that this is a very simple rest service that just return a list of objects when you send a http get request to the ‘/’ context.

After start the service, we can test the service via curl:

➜  blog-backend-apis git:(master) ✗ curl http://localhost:8081/msg/
[{"id":"1","text":"hello1"},{"id":"2","text":"Object"},{"id":"3","text":"Privot"}]

You can see that the test is fine.

2) The consumer or client of the restful web service

import com.example.domain.Message
import org.springframework.web.client.RestTemplate

fun test1() {
    val msgs:List<Message>? =
        RestTemplate().getForObject("http://localhost:8081/msg/", List::class.java) as List<Message>
    if(msgs!=null) {
        println("got ${msgs.size} messages as follows:")
        for (msg in msgs) { //this line cause the error
            println(msg)
        }
    }
}

You can see that we just use the RestTemplate to consume the webservice, and convert the result to List<Message>.

Run the consumer test function, we got this error:

14:08:08.403 [main] DEBUG org.springframework.web.client.RestTemplate - HTTP GET http://localhost:8081/msg/
14:08:08.437 [main] DEBUG org.springframework.web.client.RestTemplate - Accept=[application/json, application/*+json]
14:08:08.482 [main] DEBUG org.springframework.web.client.RestTemplate - Response 200 OK
14:08:08.494 [main] DEBUG org.springframework.web.client.RestTemplate - Reading to [java.util.List<?>]
got 3 messages as follows:
Exception in thread "main" java.lang.ClassCastException: class java.util.LinkedHashMap cannot be cast to class com.example.domain.Message (java.util.LinkedHashMap is in module java.base of loader 'bootstrap'; com.example.domain.Message is in unnamed module of loader 'app')
    at com.example.web.WebApplicationKt.test1(WebApplication.kt:22)
    at com.example.web.WebApplicationKt.main(WebApplication.kt:14)

> Task :api-web:bootRun FAILED

Execution failed for task ':api-web:bootRun'.
> Process 'command '/Library/Java/JavaVirtualMachines/jdk-11.0.17.jdk/Contents/Home/bin/java'' finished with non-zero exit value 1

* Try:
> Run with --stacktrace option to get the stack trace.
> Run with --info or --debug option to get more log output.
> Run with --scan to get full insights.

The core error message is:

Exception in thread "main" java.lang.ClassCastException: class java.util.LinkedHashMap cannot be cast to class com.example.domain.Message (java.util.LinkedHashMap is in module java.base of loader 'bootstrap'; com.example.domain.Message is in unnamed module of loader 'app')
    at com.example.web.WebApplicationKt.test1(WebApplication.kt:22)
    at com.example.web.WebApplicationKt.main(WebApplication.kt:14)

Debug the error

You can see that the following line caused the error:

And I add a breakpoint at this line to check the real type of msgs, I got this:

You can see that the real data type of msgs is an ArrayList , whose element’s type is LinkedHashMap, not the expected Message object. So the error happened.

What does this error mean?

I think this is an error that caused by the JSON reader(parser) used in the RestTemplate, by default, the springboot framework use the Jackson as the default JSON reader(parser).

Here we tried to convert json array to List<Message>:

val msgs:List<Message>? =
        RestTemplate().getForObject("http://localhost:8081/msg/", List::class.java) as List<Message>

But this does not work, because we can not specify the real type List<Message> to getForObject().

Jackson reader tried to read the JSON, and then convert to a list, but the generic type is missing, it just use the LinkedHashMap as the element type of the list.

Other useful information about this error

Here is an answer from stackoverflow:

The issue’s coming from Jackson. When it doesn’t have enough information on what class to deserialize to, it uses LinkedHashMap.

Since you’re not informing Jackson of the element type of your ArrayList, it doesn’t know that you want to deserialize into an ArrayList of Accounts. So it falls back to the default.

Instead, you could probably use as(JsonNode.class), and then deal with the ObjectMapper in a richer manner than rest-assured allows. Something like this:



2. Solution

Solution #1

We can just solve the problem by lazy converting the JSON to list, let’t do this job in two steps:

  • First, get the json string
  • Second, convert the json string to List<Message>

Here is the code:

import com.example.domain.Message
import com.fasterxml.jackson.core.type.TypeReference
import com.fasterxml.jackson.databind.ObjectMapper
import org.springframework.web.client.RestTemplate

fun test() {}
    val mapper = ObjectMapper() //jackson parser
    val msgstr:String? =
        RestTemplate().getForObject("http://localhost:8081/msg/", String::class.java) // get json string
    val msgs: List<Message> = mapper.readValue(msgstr, object : TypeReference<List<Message>>() {}) //convert json string to List<Message>
    if(msgs!=null) {
        println("got ${msgs.size} messages as follows:")
        for (msg in msgs) {
            println(msg)
        }
    }
}

run the code, we got this:

11:08:17.698 [main] DEBUG org.springframework.web.client.RestTemplate - HTTP GET http://localhost:8081/msg/
11:08:17.713 [main] DEBUG org.springframework.web.client.RestTemplate - Accept=[text/plain, application/json, application/*+json, */*]
11:08:17.866 [main] DEBUG org.springframework.web.client.RestTemplate - Response 200 OK
11:08:17.869 [main] DEBUG org.springframework.web.client.RestTemplate - Reading to [java.lang.String] as "application/json"
got 3 messages as follows:
Message(id=1, text=hello1)
Message(id=2, text=Object)
Message(id=3, text=Privot)

The above solution’s key point is to use the ObjectMapper of jackson to convert a JSON string to a kotlin list of messages:

val msgs: List<Message> = mapper.readValue(msgstr, object : TypeReference<List<Message>>() {}) //convert json string to List<Message>

Pay attention to the parameter object : TypeReference<List<Message>>() {}, it’s an anonymous object with type TypeReference<List<Message>>. If you are java developers, you can understand this by reading the following example:

Anonymous object in java:

Thread thread = new Thread() {
  @Override
  public void run() {
    System.out.println("Hello World");
  }
};
thread.start();

Anonymous object in kotlin:

val thread = object : Thread() {
  override fun run() {
    println("Hello World")
  }
}
thread.start()

Solution #2

For another working solution ,you can use the resttemplate’s getForEntity instead of the getForObject, the detailed steps are as follows:

  • Use restTemplate’s getForEntity to get the restful service response
  • Get the Array<Message> object from the response
  • Get the List<Message> from Array<Message>

Here is the code:

fun test_success_2() {
    val responseEntity: ResponseEntity<Array<Message>> = RestTemplate().getForEntity("http://localhost:8081/msg/",
        Array<Message>::class.java)
    val msgsArray: Array<Message> = responseEntity.body!!
    val msgs: List<Message> =  Arrays.stream(msgsArray)
        .collect(Collectors.toList())
    if(msgs!=null) {
        println("got ${msgs.size} messages as follows:")
        for (msg in msgs) {
            println(msg)
        }
    }
}

run the code, we get this:

11:29:10.500 [main] DEBUG org.springframework.web.client.RestTemplate - HTTP GET http://localhost:8081/msg/
11:29:10.540 [main] DEBUG org.springframework.web.client.RestTemplate - Accept=[application/json, application/*+json]
11:29:10.569 [main] DEBUG org.springframework.web.client.RestTemplate - Response 200 OK
11:29:10.578 [main] DEBUG org.springframework.web.client.RestTemplate - Reading to [com.example.domain.Message[]]
got 3 messages as follows:
Message(id=1, text=hello1)
Message(id=2, text=Object)
Message(id=3, text=Privot)

Process finished with exit code 0

Solution #3

For the third solution, we can use the RestTemplate’s exchange method, which takes a ParameterizedTypeReference as an parameter to specify the targeting type you want.

The steps are :

  • Use RestTemplate’s exchange method to get the reponse, specifying the ParameterizedTypeReference to our type
  • Get List<Message> from the response body
  • Use the result

Here is the code:

fun test_success_3() {
    val responseEntity: ResponseEntity<List<Message>> = RestTemplate().exchange(
        "http://localhost:8081/msg/",
        HttpMethod.GET,
        null,
        object : ParameterizedTypeReference<List<Message>>() {}
    )
    val msgs: List<Message>? = responseEntity.body
    if(msgs!=null) {
        println("got ${msgs.size} messages as follows:")
        for (msg in msgs) {
            println(msg)
        }
    }
}

You can see that the third solution is very concise and intuitive, the type conversion is happened inside the jackson reader.

3. The code example

The above codes has been uploaded to github, here is the link:

https://github.com/bswen/bswen-blog-backend-apis/blob/main/api-web/src/test/kotlin/tester.kt



4. Summary

In this post, I demonstrated how to solve the java.lang.ClassCastException: class java.util.LinkedHashMap cannot be cast to class xxx when trying to run an kotlin restful client or consumer, the key point is to use the correct JSON to List converter to convert from the JSON to our List. That’s it, thanks for your reading.