Programming

Fix Kotlin Deserialization Errors in Spring AI MCP Server

Resolve 'Cannot construct instance' deserialization errors for complex Kotlin data classes like List<Person> in Spring AI MCP Server with streamable-http. Configure jackson-module-kotlin, kotlin-reflect, and custom ObjectMapper bean using Jackson2ObjectMapperBuilder.

1 answer 1 view

How can I resolve deserialization errors for complex Kotlin data class parameters (e.g., List<MyDataClass>) when using Spring AI’s MCP Server with streamable-http transport?

I’m encountering the following error when calling a tool with such parameters:

Cannot construct instance of `com.test.Person` (no Creators, like default constructor, exist): cannot deserialize from Object value (no delegate- or property-based Creator)

My setup includes Spring AI spring-ai-starter-mcp-server-webmvc, Kotlin 2.2.0, and JDK 21. I’ve attempted to register a custom Jackson ObjectMapper with jackson-module-kotlin enabled, which works for direct deserialization tests but not within the MCP tool call context. What is the correct approach to ensure proper deserialization of these complex types within the Spring AI MCP Server environment?

To fix deserialization errors for complex Kotlin data classes like List<Person> in Spring AI MCP Server with streamable-http transport, add jackson-module-kotlin and kotlin-reflect dependencies to your build.gradle.kts. Then, create a custom ObjectMapper bean using Jackson2ObjectMapperBuilder to register the KotlinModule, ensuring the MCP server picks it up for @McpToolParam handling. This approach overrides the default Jackson setup that lacks Kotlin support, resolving the “Cannot construct instance” error during tool calls.

Contents

Understanding the Deserialization Error

You’re hitting a classic Jackson issue with Kotlin data classes in Spring AI’s MCP Server. The error—“Cannot construct instance of com.test.Person (no Creators, like default constructor, exist)”—pops up because Jackson’s default ObjectMapper doesn’t know how to instantiate Kotlin data classes without a no-arg constructor. Kotlin data classes rely on generated constructors and properties, but plain Jackson treats them like regular POJOs.

This bites harder in MCP tool calls with @McpToolParam, where the server deserializes JSON-RPC payloads over streamable-http. The Spring AI GitHub issue #4481 nails your exact setup: spring-ai-starter-mcp-server-webmvc:1.1.0-M2, Kotlin, and complex params like UserSearchRequest. Even if direct tests with your custom ObjectMapper work, the MCP layer uses its own Jackson instance unless you override it properly.

Why streamable-http specifically? It handles bidirectional JSON-RPC over HTTP POST/GET, amplifying deserialization demands for lists or nested objects. The Spring AI MCP docs confirm the transport layer manages this, but it needs Kotlin-aware Jackson.

Add Essential Dependencies

Start with your build.gradle.kts. Spring Boot pulls in Jackson, but skips Kotlin magic without these:

kotlin
dependencies {
    implementation("org.springframework.ai:spring-ai-starter-mcp-server-webmvc")
    implementation("com.fasterxml.jackson.module:jackson-module-kotlin")  // Core fix for data classes
    implementation("org.jetbrains.kotlin:kotlin-reflect")  // Reflection for constructors/properties
    // Your other deps: Kotlin 2.2.0, JDK 21 fine
}

A Kotlin Spring AI MCP demo repo uses this exact combo for a working server. Rebuild and refresh your IDE—kotlin-reflect enables Jackson to find creators, while the module adds Kotlin-specific serializers.

Don’t forget: If you’re on Kotlin 2.0+, test compatibility, as older Jackson modules had edge cases with inline classes or value classes.

Configure Custom ObjectMapper

Your direct tests worked because you controlled the ObjectMapper. For MCP, Spring needs a global bean it respects. Ditch raw ObjectMapper instantiation; use Jackson2ObjectMapperBuilder for Spring Boot magic:

kotlin
@Configuration
class JacksonConfig {

    @Bean
    @Primary
    @Order(Ordered.HIGHEST_PRECEDENCE)
    fun mcpObjectMapper(jackson2ObjectMapperBuilder: Jackson2ObjectMapperBuilder): ObjectMapper {
        return jackson2ObjectMapperBuilder
            .modules(KotlinModule.Builder().build())  // Registers Kotlin support
            .visibility(PropertyAccessor.FIELD, JsonAutoDetect.Visibility.ANY)
            .disable(SerializationFeature.FAIL_ON_EMPTY_BEANS)
            .disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES)
            .featuresToDisable(DeserializationFeature.FAIL_ON_INVALID_SUBTYPE)
            .build()
    }
}

This builder auto-detects modules on the classpath and tweaks visibility for Kotlin’s private primaries. The @Primary and high precedence ensure MCP’s transport layer grabs it over defaults. Stack Overflow threads on Spring Boot + Kotlin Jackson swear by this for “no Creators” errors.

Pro tip: If you have LocalDate or other Java time types in Person, add JavaTimeModule() too.

Set Up Spring AI MCP Server

Wire your tools with @McpTool and complex params:

kotlin
@McpTool("search-persons")
fun searchPersons(@McpToolParam request: PersonSearchRequest): String {
    // Your logic for List<Person>
    return "Found ${request.persons.size} persons"
}

data class PersonSearchRequest(val persons: List<Person>)
data class Person(val name: String, val age: Int)

Spring AI’s MCP annotations module generates JSON schemas automatically once Jackson is fixed. Register via McpServerToolCallbackProvider or autoconfig.

Enable stateless mode if needed: spring.ai.mcp.server.protocol=STATELESS. The official MCP server starter docs cover this.

Configure Streamable-HTTP Transport

For streamable-http, add the WebFlux starter if missing:

kotlin
dependencies {
    implementation("org.springframework.ai:spring-ai-starter-mcp-server-webflux")
}

In application.yml:

yaml
spring:
  ai:
    mcp:
      server:
        protocol: STREAMABLE
        streamable-http:
          mcp-endpoint: /mcp

This sets up SSE and Streamable-HTTP as per Spring AI streamable-http docs. Clients connect via spring.ai.mcp.client.streamable-http.connections.my-server.url=http://localhost:8080/mcp. A Spring blog post demos client-side matching.

Run with spring-boot:run—watch logs for clean JSON-RPC without Maven noise, as noted in issue #3472.

Define and Test Tool Parameters

Mock a client call:

kotlin
// Client side example
val client = // Your MCP client
val response = client.call("""
    {"jsonrpc":"2.0","id":1,"method":"search-persons","params":{"persons":[{"name":"Alice","age":30}]}}
""")

Hit your endpoint with curl: curl -X POST http://localhost:8080/mcp -d '{"jsonrpc":"2.0","method":"search-persons","params":{"persons":[{"name":"Bob","age":25}]}}'. No more deserialization fails.

Use tools like Postman for Streamable-HTTP testing. Baeldung’s MCP with Spring AI guide walks through full client-server flows.

Troubleshooting Common Issues

  • Module not picked up? Verify classpath with ./gradlew dependencies | grep jackson. Explicit @Bean order matters.
  • Still fails on lists/nested? Add @JsonCreator to data class constructors: @JsonCreator constructor(val persons: List<Person>).
  • Native/ProGuard? Jackson Kotlin issues flag Kotlin 1.5+ quirks—update to latest.
  • Logs polluted? Run clean: mvn clean spring-boot:run.
  • Version mismatches? Pin Jackson to 2.17+ for Kotlin 2.2.0.

Check Spring Cloud Function issue for builder tips. If stuck, file under spring-projects/spring-ai with your ObjectMapper debug logs.

Sources

Conclusion

With jackson-module-kotlin, kotlin-reflect, and a Jackson2ObjectMapperBuilder-backed bean, your Spring AI MCP Server handles complex Kotlin data class deserialization flawlessly—even for List<Person> over streamable-http. Restart, test tool calls, and scale your AI agents confidently. This setup mirrors production demos and sidesteps framework gaps until upstream fixes land.

Authors
Verified by moderation
Moderation
Fix Kotlin Deserialization Errors in Spring AI MCP Server