spring-projects / spring-projects/spring-ai

Versions 2.0.0-M3 and later have problems parsing @ToolParam LocalTime with leading zero

Open
#5,696 2 comments 0 reactions 1 assignee View on GitHub

@sdeleuze is already working on this.

Since Mar 27, 2026.

status: waiting-for-triage
Dominant language
Java
Stars
9.5k
Forks
2.9k
Avg merge
1d 7h
Merged PRs (30d)
6

Description

Given following code:

package be.vdab.localtimetest;

import org.springframework.ai.chat.client.ChatClient;
import org.springframework.ai.tool.annotation.Tool;
import org.springframework.ai.tool.annotation.ToolParam;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.stereotype.Component;

import java.time.LocalTime;

@SpringBootApplication
public class LocalTimeTestApplication {

    public static void main(String[] args) {
        SpringApplication.run(LocalTimeTestApplication.class, args);
    }

}

@Component
class MyRunner implements CommandLineRunner {
    private final ChatClient chatClient;

    public MyRunner(ChatClient.Builder builder) {
        this.chatClient = builder.build();
    }

    @Override
    public void run(String... args) throws Exception {
        IO.println(chatClient.prompt("Is the shop open at 08:00 ?").tools(this).call().content());
    }

    @Tool(description = "Returns true if the shop is open at a certain time.")
    boolean open(
            @ToolParam(description = "The time for which you want to know if the shop is open.") LocalTime time) {
        var hour = time.getHour();
        return hour >= 10 && hour <= 18;
    }
}

This works on version 2.0.0-M2,

but does not work anymore on versions 2.0.0-M3 and 2.0.0-M4, giving an exception:
java.lang.IllegalStateException: Conversion from JSON to java.time.LocalTime failed
at org.springframework.ai.util.json.JsonParser.fromJson(JsonParser.java:70) ~[spring-ai-model-2.0.0-M4.jar:2.0.0-M4]
at org.springframework.ai.util.json.JsonParser.toTypedObject(JsonParser.java:177) ~[spring-ai-model-2.0.0-M4.jar:2.0.0-M4]
at org.springframework.ai.tool.method.MethodToolCallback.buildTypedArgument(MethodToolCallback.java:157) ~[spring-ai-model-2.0.0-M4.jar:2.0.0-M4]
at org.springframework.ai.tool.method.MethodToolCallback.lambda$buildMethodArguments$0(MethodToolCallback.java:147) ~[spring-ai-model-2.0.0-M4.jar:2.0.0-M4]
at java.base/java.util.stream.ReferencePipeline$3$1.accept(ReferencePipeline.java:214) ~[na:na]
at java.base/java.util.Spliterators$ArraySpliterator.forEachRemaining(Spliterators.java:1024) ~[na:na]
at java.base/java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:570) ~[na:na]
at java.base/java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:560) ~[na:na]
at java.base/java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:635) ~[na:na]
at java.base/java.util.stream.AbstractPipeline.evaluateToArrayNode(AbstractPipeline.java:291) ~[na:na]
at java.base/java.util.stream.ReferencePipeline.toArray(ReferencePipeline.java:652) ~[na:na]
at java.base/java.util.stream.ReferencePipeline.toArray(ReferencePipeline.java:658) ~[na:na]
at org.springframework.ai.tool.method.MethodToolCallback.buildMethodArguments(MethodToolCallback.java:148) ~[spring-ai-model-2.0.0-M4.jar:2.0.0-M4]
at org.springframework.ai.tool.method.MethodToolCallback.call(MethodToolCallback.java:107) ~[spring-ai-model-2.0.0-M4.jar:2.0.0-M4]
at org.springframework.ai.model.tool.DefaultToolCallingManager.lambda$executeToolCall$3(DefaultToolCallingManager.java:228) ~[spring-ai-model-2.0.0-M4.jar:2.0.0-M4]
at io.micrometer.observation.Observation.observe(Observation.java:634) ~[micrometer-observation-1.16.4.jar:1.16.4]
at org.springframework.ai.model.tool.DefaultToolCallingManager.executeToolCall(DefaultToolCallingManager.java:225) ~[spring-ai-model-2.0.0-M4.jar:2.0.0-M4]
at org.springframework.ai.model.tool.DefaultToolCallingManager.executeToolCalls(DefaultToolCallingManager.java:144) ~[spring-ai-model-2.0.0-M4.jar:2.0.0-M4]
at org.springframework.ai.ollama.OllamaChatModel.internalCall(OllamaChatModel.java:295) ~[spring-ai-ollama-2.0.0-M4.jar:2.0.0-M4]
at org.springframework.ai.ollama.OllamaChatModel.call(OllamaChatModel.java:237) ~[spring-ai-ollama-2.0.0-M4.jar:2.0.0-M4]
at org.springframework.ai.chat.client.advisor.ChatModelCallAdvisor.adviseCall(ChatModelCallAdvisor.java:58) ~[spring-ai-client-chat-2.0.0-M4.jar:2.0.0-M4]
at org.springframework.ai.chat.client.advisor.DefaultAroundAdvisorChain.lambda$nextCall$1(DefaultAroundAdvisorChain.java:115) ~[spring-ai-client-chat-2.0.0-M4.jar:2.0.0-M4]
at io.micrometer.observation.Observation.observe(Observation.java:634) ~[micrometer-observation-1.16.4.jar:1.16.4]
at org.springframework.ai.chat.client.advisor.DefaultAroundAdvisorChain.nextCall(DefaultAroundAdvisorChain.java:114) ~[spring-ai-client-chat-2.0.0-M4.jar:2.0.0-M4]
at org.springframework.ai.chat.client.DefaultChatClient$DefaultCallResponseSpec.lambda$doGetObservableChatClientResponse$1(DefaultChatClient.java:529) ~[spring-ai-client-chat-2.0.0-M4.jar:2.0.0-M4]
at io.micrometer.observation.Observation.observe(Observation.java:634) ~[micrometer-observation-1.16.4.jar:1.16.4]
at org.springframework.ai.chat.client.DefaultChatClient$DefaultCallResponseSpec.doGetObservableChatClientResponse(DefaultChatClient.java:527) ~[spring-ai-client-chat-2.0.0-M4.jar:2.0.0-M4]
at org.springframework.ai.chat.client.DefaultChatClient$DefaultCallResponseSpec.content(DefaultChatClient.java:507) ~[spring-ai-client-chat-2.0.0-M4.jar:2.0.0-M4]
at be.vdab.localtimetest.MyRunner.run(LocalTimeTestApplication.java:32) ~[main/:na]
at org.springframework.boot.SpringApplication.lambda$callRunner$1(SpringApplication.java:792) ~[spring-boot-4.0.5.jar:4.0.5]
at org.springframework.util.function.ThrowingConsumer$1.acceptWithException(ThrowingConsumer.java:82) ~[spring-core-7.0.6.jar:7.0.6]
at org.springframework.util.function.ThrowingConsumer.accept(ThrowingConsumer.java:60) ~[spring-core-7.0.6.jar:7.0.6]
at org.springframework.util.function.ThrowingConsumer$1.accept(ThrowingConsumer.java:86) ~[spring-core-7.0.6.jar:7.0.6]
at org.springframework.boot.SpringApplication.callRunner(SpringApplication.java:800) ~[spring-boot-4.0.5.jar:4.0.5]
at org.springframework.boot.SpringApplication.callRunner(SpringApplication.java:791) ~[spring-boot-4.0.5.jar:4.0.5]
at org.springframework.boot.SpringApplication.lambda$callRunners$0(SpringApplication.java:776) ~[spring-boot-4.0.5.jar:4.0.5]
at java.base/java.util.stream.ForEachOps$ForEachOp$OfRef.accept(ForEachOps.java:186) ~[na:na]
at java.base/java.util.stream.SortedOps$SizedRefSortingSink.end(SortedOps.java:357) ~[na:na]
at java.base/java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:571) ~[na:na]
at java.base/java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:560) ~[na:na]
at java.base/java.util.stream.ForEachOps$ForEachOp.evaluateSequential(ForEachOps.java:153) ~[na:na]
at java.base/java.util.stream.ForEachOps$ForEachOp$OfRef.evaluateSequential(ForEachOps.java:176) ~[na:na]
at java.base/java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:265) ~[na:na]
at java.base/java.util.stream.ReferencePipeline.forEach(ReferencePipeline.java:632) ~[na:na]
at org.springframework.boot.SpringApplication.callRunners(SpringApplication.java:776) ~[spring-boot-4.0.5.jar:4.0.5]
at org.springframework.boot.SpringApplication.run(SpringApplication.java:328) ~[spring-boot-4.0.5.jar:4.0.5]
at org.springframework.boot.SpringApplication.run(SpringApplication.java:1365) ~[spring-boot-4.0.5.jar:4.0.5]
at org.springframework.boot.SpringApplication.run(SpringApplication.java:1354) ~[spring-boot-4.0.5.jar:4.0.5]
at be.vdab.localtimetest.LocalTimeTestApplication.main(LocalTimeTestApplication.java:17) ~[main/:na]
Caused by: tools.jackson.core.exc.StreamReadException: Invalid numeric value: Leading zeroes not allowed

You find the code on
https://github.com/hansdesmet/LocalTimeTest

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.