`FilterExpressionTextParser` shares mutable error state across instances

Open Beginner friendly
#6,807 1 comment 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Assessment

Difficulty
2/5
Estimated time
1-3 hours
Newbie friendliness
72/100
Issue type
Bug
Clarity
Mostly clear
Activity status
Quiet
Tech stack
java
Domain
backend, testing

Research direction

Start with FilterExpressionTextParser, its constructor, parse() flow, and DescriptiveErrorListener.INSTANCE usage. Use the provided JUnit regression test as the first check, then verify that separate parser instances collect diagnostics independently during concurrent invalid parses.

Written by the indexing model from the issue text.

Description

status: waiting-for-triage

Bug description

FilterExpressionTextParser assigns the same mutable DescriptiveErrorListener.INSTANCE to every parser instance.

Each call to parse() clears, modifies, and reads the listener's shared errorMessages list. Concurrent parsing through separate parser instances can therefore interfere with diagnostic collection performed by another invocation.

CopyOnWriteArrayList makes individual list operations thread-safe, but it does not isolate the compound clear → parse → join sequence.

If the maintainers agree that this is a bug, I would be happy to work on the fix and submit a pull request with regression tests.

Environment

  • Spring AI: 2.0.1-SNAPSHOT
  • Tested artifact: spring-ai-vector-store-2.0.1-20260808.133500-105.jar
  • Java: 17.0.17
  • ANTLR runtime: 4.13.2
  • Vector store: none required

Steps to reproduce

  1. Create two separate FilterExpressionTextParser instances.
  2. Retrieve the errorListener assigned to each instance.
  3. Observe that both parser instances use the same listener.
  4. Run invalid parse operations concurrently and observe that both calls operate
    on the same errorMessages collection.

Relevant implementation:

public FilterExpressionTextParser(ANTLRErrorStrategy handler) {
      this.errorListener = DescriptiveErrorListener.INSTANCE;
      this.errorHandler = handler;
}
parser.removeErrorListeners();
this.errorListener.errorMessages.clear();
parser.addErrorListener(this.errorListener);
var msg = String.join("", this.errorListener.errorMessages);

Expected behavior

Each parser invocation, or at least each parser instance, should collect its syntax error messages independently. Concurrent parsing should not clear or modify diagnostics belonging to another invocation.

Minimal Complete Reproducible example

The following JUnit test fails because both parser instances receive the same
mutable error listener:

import java.lang.reflect.Field;

import org.junit.jupiter.api.Test;

import org.springframework.ai.vectorstore.filter.FilterExpressionTextParser;

import static org.assertj.core.api.Assertions.assertThat;

class FilterExpressionTextParserConcurrencyTests {

      @Test
      void parserInstancesShouldNotShareMutableErrorListener() throws Exception {
              var firstParser = new FilterExpressionTextParser();
              var secondParser = new FilterExpressionTextParser();

              Field listenerField =
                              FilterExpressionTextParser.class.getDeclaredField("errorListener");
              listenerField.setAccessible(true);

              Object firstListener = listenerField.get(firstParser);
              Object secondListener = listenerField.get(secondParser);

              assertThat(firstListener).isNotSameAs(secondListener);
      }

}

Actual result:

Expecting actual:
  org.springframework.ai.vectorstore.filter.FilterExpressionTextParser$DescriptiveErrorListener@...
not to be the same as:
  org.springframework.ai.vectorstore.filter.FilterExpressionTextParser$DescriptiveErrorListener@...
Dominant language
Java
Stars
9.5k
Forks
2.9k
Avg merge
1d 10h
Merged PRs (30d)
5

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.

More from spring-projects/spring-ai

All issues in spring-projects/spring-ai

Similar issues

More Java issues

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.