Skip to content

[BUG] InMemoryMemoryService.searchMemory throws NoSuchElementException for events with non-text parts #1464

Description

@1wos

🔴 Required Information

Describe the Bug:

InMemoryMemoryService.searchMemory calls part.text().get() without checking whether the Optional holds a value:

// InMemoryMemoryService.java:108
for (Part part : event.content().get().parts().get()) {
  if (!Strings.isNullOrEmpty(part.text().get())) {   // NoSuchElementException for a non-text part
    Matcher matcher = WORD_PATTERN.matcher(part.text().get());

Part.text() is empty for functionCall, functionResponse, inlineData and fileData parts. addSessionToMemory stores those events — it only drops events whose parts list is empty. searchMemory then scans every session stored under the same app and user, so one tool call in any one session makes every later search for that app and user throw, whatever the query.

Steps to Reproduce:

  1. Store a session holding an event with a functionCall part. No model or network is involved.
  2. Search that app and user.
InMemoryMemoryService memoryService = new InMemoryMemoryService();

Session session =
    Session.builder("session")
        .appName("app")
        .userId("user")
        .events(
            ImmutableList.of(
                Event.builder()
                    .author("agent")
                    .content(
                        Content.fromParts(
                            Part.builder()
                                .functionCall(FunctionCall.builder().name("get_weather").build())
                                .build()))
                    .build()))
        .build();

memoryService.addSessionToMemory(session).blockingAwait();
memoryService.searchMemory("app", "user", "weather").blockingGet();

Expected Behavior:

Parts without text are skipped and the search returns the entries whose text matched.

Observed Behavior:

java.util.NoSuchElementException: No value present
	at java.base/java.util.Optional.get(Optional.java:143)
	at com.google.adk.memory.InMemoryMemoryService.lambda$searchMemory$5(InMemoryMemoryService.java:108)
	at io.reactivex.rxjava3.internal.operators.single.SingleFromCallable.subscribeActual(SingleFromCallable.java:43)
	at io.reactivex.rxjava3.core.Single.subscribe(Single.java:4855)
	at io.reactivex.rxjava3.core.Single.blockingGet(Single.java:3644)

Environment Details:

  • ADK Library Version: 1.8.1-SNAPSHOT (main), also present in 1.8.0
  • OS: macOS 26.4.1
  • TS Version: N/A

Model Information:

  • N/A

🟡 Optional Information

Regression:

No. The unguarded .get() has been there since the class was added in c8fed2d (2025-06-28).

Additional Context:

Reachable from an agent: LoadMemoryToolToolContext.searchMemoryBaseMemoryService.searchMemory. The trigger is an app that saves a tool-using session with addSessionToMemory and later lets the agent call load_memory. InMemoryMemoryService is the default in InMemoryRunner and in the dev server, so this is the path most people are on.

Every other part.text() read in core guards with isPresent(), filter or orElse (e.g. LlmAgent, Claude, LoggingPlugin, CodeExecutionUtils; LlmEventSummarizer uses part.text().filter(not(String::isEmpty)) for the same job). This is the one that does not.

adk-python filters the same loop on if part.text, and adk-go skips with if part.Text == "".

Same class of bug as #1279 and #1280.

How often has this issue occurred?:

  • Always (100%) once a stored session holds a part without text.

I have a fix and tests ready and will open a PR.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions