You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@lucene.apache.org by rm...@apache.org on 2025/02/07 13:49:54 UTC

(lucene) branch branch_10x updated: Deprecate Operations.concat(a1, a2) and Operations.union(a1, a2) (#14209)

This is an automated email from the ASF dual-hosted git repository.

rmuir pushed a commit to branch branch_10x
in repository https://gitbox.apache.org/repos/asf/lucene.git


The following commit(s) were added to refs/heads/branch_10x by this push:
     new 84891a0af8b Deprecate Operations.concat(a1, a2) and Operations.union(a1, a2) (#14209)
84891a0af8b is described below

commit 84891a0af8b2e208a93cd97304ea548c4c2a3042
Author: Robert Muir <rm...@apache.org>
AuthorDate: Fri Feb 7 08:33:27 2025 -0500

    Deprecate Operations.concat(a1, a2) and Operations.union(a1, a2) (#14209)
    
    These algorithms run in linear time: it is trappy to offer two-arg
    options: they encourage users to use them in a loop and create quadratic
    time.
    
    Send a strong signal to the user's editor/IDE to avoid trouble in the
    first place:
    * Deprecate two-arg methods in favor of the List-based versions
    * Rename parameter 'l' to 'list'
    * Add javadoc @param to parameter 'list'
---
 lucene/CHANGES.txt                                 |  3 +
 .../apache/lucene/util/automaton/Operations.java   | 28 ++++++---
 .../lucene/analysis/TestGraphTokenizers.java       | 21 +++----
 .../apache/lucene/search/TestAutomatonQuery.java   |  8 ++-
 .../lucene/util/automaton/TestAutomaton.java       | 69 +++++++++++++---------
 .../lucene/util/automaton/TestDeterminism.java     |  4 +-
 .../util/automaton/TestFiniteStringsIterator.java  |  7 ++-
 .../util/automaton/TestLevenshteinAutomata.java    | 66 +++++++++------------
 .../TestLimitedFiniteStringsIterator.java          |  4 +-
 .../lucene/util/automaton/TestNFARunAutomaton.java |  3 +-
 .../lucene/util/automaton/TestOperations.java      | 35 ++++++-----
 .../lucene/util/automaton/TestRegExpParsing.java   | 46 +++++++++------
 .../search/suggest/document/ContextQuery.java      | 16 ++---
 .../tests/util/automaton/AutomatonTestUtil.java    |  4 +-
 14 files changed, 177 insertions(+), 137 deletions(-)

diff --git a/lucene/CHANGES.txt b/lucene/CHANGES.txt
index 5f1df581302..105ed74f298 100644
--- a/lucene/CHANGES.txt
+++ b/lucene/CHANGES.txt
@@ -13,6 +13,9 @@ API Changes
 * GITHUB#14134: Added Bits#applyMask API to help apply live docs as a mask on a
   bit set of matches. (Adrien Grand)
 
+* GITHUB#14209: Deprecate Operations.union(Automaton,Automaton) and 
+  concatenate(Automaton,Automaton) in favor of the methods taking List.  (Robert Muir)
+
 New Features
 ---------------------
 
diff --git a/lucene/core/src/java/org/apache/lucene/util/automaton/Operations.java b/lucene/core/src/java/org/apache/lucene/util/automaton/Operations.java
index fb255f8a02e..2a1a0086fdc 100644
--- a/lucene/core/src/java/org/apache/lucene/util/automaton/Operations.java
+++ b/lucene/core/src/java/org/apache/lucene/util/automaton/Operations.java
@@ -69,7 +69,10 @@ public final class Operations {
    * Returns an automaton that accepts the concatenation of the languages of the given automata.
    *
    * <p>Complexity: linear in total number of states.
+   *
+   * @deprecated use {@link #concatenate(List)} instead
    */
+  @Deprecated
   public static Automaton concatenate(Automaton a1, Automaton a2) {
     return concatenate(Arrays.asList(a1, a2));
   }
@@ -78,12 +81,14 @@ public final class Operations {
    * Returns an automaton that accepts the concatenation of the languages of the given automata.
    *
    * <p>Complexity: linear in total number of states.
+   *
+   * @param list List of automata to be joined
    */
-  public static Automaton concatenate(List<Automaton> l) {
+  public static Automaton concatenate(List<Automaton> list) {
     Automaton result = new Automaton();
 
     // First pass: create all states
-    for (Automaton a : l) {
+    for (Automaton a : list) {
       if (a.getNumStates() == 0) {
         result.finishState();
         return result;
@@ -98,11 +103,11 @@ public final class Operations {
     // states of A to init state of next A:
     int stateOffset = 0;
     Transition t = new Transition();
-    for (int i = 0; i < l.size(); i++) {
-      Automaton a = l.get(i);
+    for (int i = 0; i < list.size(); i++) {
+      Automaton a = list.get(i);
       int numStates = a.getNumStates();
 
-      Automaton nextA = (i == l.size() - 1) ? null : l.get(i + 1);
+      Automaton nextA = (i == list.size() - 1) ? null : list.get(i + 1);
 
       for (int s = 0; s < numStates; s++) {
         int numTransitions = a.initTransition(s, t);
@@ -127,7 +132,7 @@ public final class Operations {
               if (followA.isAccept(0)) {
                 // Keep chaining if followA accepts empty string
                 followOffset += followA.getNumStates();
-                followA = (upto == l.size() - 1) ? null : l.get(upto + 1);
+                followA = (upto == list.size() - 1) ? null : list.get(upto + 1);
                 upto++;
               } else {
                 break;
@@ -474,7 +479,10 @@ public final class Operations {
    * Returns an automaton that accepts the union of the languages of the given automata.
    *
    * <p>Complexity: linear in number of states.
+   *
+   * @deprecated use {@link #union(Collection)} instead
    */
+  @Deprecated
   public static Automaton union(Automaton a1, Automaton a2) {
     return union(Arrays.asList(a1, a2));
   }
@@ -483,21 +491,23 @@ public final class Operations {
    * Returns an automaton that accepts the union of the languages of the given automata.
    *
    * <p>Complexity: linear in number of states.
+   *
+   * @param list List of automata to be unioned.
    */
-  public static Automaton union(Collection<Automaton> l) {
+  public static Automaton union(Collection<Automaton> list) {
     Automaton result = new Automaton();
 
     // Create initial state:
     result.createState();
 
     // Copy over all automata
-    for (Automaton a : l) {
+    for (Automaton a : list) {
       result.copy(a);
     }
 
     // Add epsilon transition from new initial state
     int stateOffset = 1;
-    for (Automaton a : l) {
+    for (Automaton a : list) {
       if (a.getNumStates() == 0) {
         continue;
       }
diff --git a/lucene/core/src/test/org/apache/lucene/analysis/TestGraphTokenizers.java b/lucene/core/src/test/org/apache/lucene/analysis/TestGraphTokenizers.java
index 4737694702f..0f3a9e6c62c 100644
--- a/lucene/core/src/test/org/apache/lucene/analysis/TestGraphTokenizers.java
+++ b/lucene/core/src/test/org/apache/lucene/analysis/TestGraphTokenizers.java
@@ -454,7 +454,7 @@ public class TestGraphTokenizers extends BaseTokenStreamTestCase {
             });
     final Automaton a1 = join(s2a("a"), SEP_A, HOLE_A, SEP_A, HOLE_A, SEP_A, s2a("b"));
     final Automaton a2 = join(s2a("x"), SEP_A, s2a("b"));
-    assertSameLanguage(Operations.union(a1, a2), ts);
+    assertSameLanguage(Operations.union(List.of(a1, a2)), ts);
   }
 
   // for debugging!
@@ -515,7 +515,7 @@ public class TestGraphTokenizers extends BaseTokenStreamTestCase {
         new CannedTokenStream(new Token[] {token("abc", 1, 1), token("xyz", 0, 1)});
     final Automaton a1 = s2a("abc");
     final Automaton a2 = s2a("xyz");
-    assertSameLanguage(Operations.union(a1, a2), ts);
+    assertSameLanguage(Operations.union(List.of(a1, a2)), ts);
   }
 
   public void testOverlappedTokensLattice() throws Exception {
@@ -527,7 +527,7 @@ public class TestGraphTokenizers extends BaseTokenStreamTestCase {
             });
     final Automaton a1 = s2a("xyz");
     final Automaton a2 = join("abc", "def");
-    assertSameLanguage(Operations.union(a1, a2), ts);
+    assertSameLanguage(Operations.union(List.of(a1, a2)), ts);
   }
 
   public void testSynOverHole() throws Exception {
@@ -537,8 +537,8 @@ public class TestGraphTokenizers extends BaseTokenStreamTestCase {
             new Token[] {
               token("a", 1, 1), token("X", 0, 2), token("b", 2, 1),
             });
-    final Automaton a1 = Operations.union(join(s2a("a"), SEP_A, HOLE_A), s2a("X"));
-    final Automaton expected = Operations.concatenate(a1, join(SEP_A, s2a("b")));
+    final Automaton a1 = Operations.union(List.of(join(s2a("a"), SEP_A, HOLE_A), s2a("X")));
+    final Automaton expected = Operations.concatenate(List.of(a1, join(SEP_A, s2a("b"))));
     assertSameLanguage(expected, ts);
   }
 
@@ -550,7 +550,7 @@ public class TestGraphTokenizers extends BaseTokenStreamTestCase {
               token("xyz", 1, 1), token("abc", 0, 3), token("def", 2, 1),
             });
     final Automaton expected =
-        Operations.union(join(s2a("xyz"), SEP_A, HOLE_A, SEP_A, s2a("def")), s2a("abc"));
+        Operations.union(List.of(join(s2a("xyz"), SEP_A, HOLE_A, SEP_A, s2a("def")), s2a("abc")));
     assertSameLanguage(expected, ts);
   }
 
@@ -563,7 +563,7 @@ public class TestGraphTokenizers extends BaseTokenStreamTestCase {
             });
     final Automaton a1 = s2a("xyz");
     final Automaton a2 = join("abc", "def", "ghi");
-    assertSameLanguage(Operations.union(a1, a2), ts);
+    assertSameLanguage(Operations.union(List.of(a1, a2)), ts);
   }
 
   public void testToDot() throws Exception {
@@ -599,7 +599,7 @@ public class TestGraphTokenizers extends BaseTokenStreamTestCase {
             new Token[] {
               token("a", 1, 1), token("X", 0, 10),
             });
-    assertSameLanguage(Operations.union(s2a("a"), s2a("X")), ts);
+    assertSameLanguage(Operations.union(List.of(s2a("a"), s2a("X"))), ts);
   }
 
   /** Returns all paths */
@@ -659,8 +659,9 @@ public class TestGraphTokenizers extends BaseTokenStreamTestCase {
             });
     assertSameLanguage(
         Operations.union(
-            join(s2a("abc"), SEP_A, s2a("xyz")),
-            join(s2a("abc"), SEP_A, HOLE_A, SEP_A, s2a("def"), SEP_A, s2a("ghi"))),
+            List.of(
+                join(s2a("abc"), SEP_A, s2a("xyz")),
+                join(s2a("abc"), SEP_A, HOLE_A, SEP_A, s2a("def"), SEP_A, s2a("ghi")))),
         ts);
   }
 }
diff --git a/lucene/core/src/test/org/apache/lucene/search/TestAutomatonQuery.java b/lucene/core/src/test/org/apache/lucene/search/TestAutomatonQuery.java
index 4b10562de26..ce38a992893 100644
--- a/lucene/core/src/test/org/apache/lucene/search/TestAutomatonQuery.java
+++ b/lucene/core/src/test/org/apache/lucene/search/TestAutomatonQuery.java
@@ -133,7 +133,8 @@ public class TestAutomatonQuery extends LuceneTestCase {
         1,
         Operations.determinize(
             Automata.makeDecimalInterval(0, 2000, 0), Operations.DEFAULT_DETERMINIZE_WORK_LIMIT));
-    assertAutomatonHits(2, Operations.union(Automata.makeChar('a'), Automata.makeChar('b')));
+    assertAutomatonHits(
+        2, Operations.union(List.of(Automata.makeChar('a'), Automata.makeChar('b'))));
     assertAutomatonHits(0, Operations.intersection(Automata.makeChar('a'), Automata.makeChar('b')));
     assertAutomatonHits(
         1,
@@ -151,7 +152,8 @@ public class TestAutomatonQuery extends LuceneTestCase {
     AutomatonQuery a3 =
         new AutomatonQuery(
             newTerm("foobar"),
-            Operations.concatenate(Automata.makeString("foo"), Automata.makeString("bar")));
+            Operations.concatenate(
+                List.of(Automata.makeString("foo"), Automata.makeString("bar"))));
     // different than a1 (same term, but different language)
     AutomatonQuery a4 = new AutomatonQuery(newTerm("foobar"), Automata.makeString("different"));
     // different than a1 (different term, same language)
@@ -189,7 +191,7 @@ public class TestAutomatonQuery extends LuceneTestCase {
    */
   public void testRewritePrefix() throws IOException {
     Automaton pfx = Automata.makeString("do");
-    Automaton prefixAutomaton = Operations.concatenate(pfx, Automata.makeAnyString());
+    Automaton prefixAutomaton = Operations.concatenate(List.of(pfx, Automata.makeAnyString()));
     AutomatonQuery aq = new AutomatonQuery(newTerm("bogus"), prefixAutomaton);
     assertEquals(3, automatonQueryNrHits(aq));
   }
diff --git a/lucene/core/src/test/org/apache/lucene/util/automaton/TestAutomaton.java b/lucene/core/src/test/org/apache/lucene/util/automaton/TestAutomaton.java
index 11ba6bee79c..b3adaad65d1 100644
--- a/lucene/core/src/test/org/apache/lucene/util/automaton/TestAutomaton.java
+++ b/lucene/core/src/test/org/apache/lucene/util/automaton/TestAutomaton.java
@@ -86,12 +86,14 @@ public class TestAutomaton extends LuceneTestCase {
     Automaton a1 = Automata.makeString("foobar");
     Automaton a2 =
         Operations.removeDeadStates(
-            Operations.concatenate(Automata.makeString("foo"), Automata.makeString("bar")));
+            Operations.concatenate(
+                List.of(Automata.makeString("foo"), Automata.makeString("bar"))));
     assertTrue(AutomatonTestUtil.sameLanguage(a1, a2));
   }
 
   public void testCommonPrefixString() throws Exception {
-    Automaton a = Operations.concatenate(Automata.makeString("foobar"), Automata.makeAnyString());
+    Automaton a =
+        Operations.concatenate(List.of(Automata.makeString("foobar"), Automata.makeAnyString()));
     assertEquals("foobar", Operations.getCommonPrefix(a));
   }
 
@@ -114,31 +116,36 @@ public class TestAutomaton extends LuceneTestCase {
   public void testAlternatives() throws Exception {
     Automaton a = Automata.makeChar('a');
     Automaton c = Automata.makeChar('c');
-    assertEquals("", Operations.getCommonPrefix(Operations.union(a, c)));
+    assertEquals("", Operations.getCommonPrefix(Operations.union(List.of(a, c))));
   }
 
   public void testCommonPrefixLeadingWildcard() throws Exception {
-    Automaton a = Operations.concatenate(Automata.makeAnyChar(), Automata.makeString("boo"));
+    Automaton a =
+        Operations.concatenate(List.of(Automata.makeAnyChar(), Automata.makeString("boo")));
     assertEquals("", Operations.getCommonPrefix(a));
   }
 
   public void testCommonPrefixTrailingWildcard() throws Exception {
-    Automaton a = Operations.concatenate(Automata.makeString("boo"), Automata.makeAnyChar());
+    Automaton a =
+        Operations.concatenate(List.of(Automata.makeString("boo"), Automata.makeAnyChar()));
     assertEquals("boo", Operations.getCommonPrefix(a));
   }
 
   public void testCommonPrefixLeadingKleenStar() throws Exception {
-    Automaton a = Operations.concatenate(Automata.makeAnyString(), Automata.makeString("boo"));
+    Automaton a =
+        Operations.concatenate(List.of(Automata.makeAnyString(), Automata.makeString("boo")));
     assertEquals("", Operations.getCommonPrefix(a));
   }
 
   public void testCommonPrefixTrailingKleenStar() throws Exception {
-    Automaton a = Operations.concatenate(Automata.makeString("boo"), Automata.makeAnyString());
+    Automaton a =
+        Operations.concatenate(List.of(Automata.makeString("boo"), Automata.makeAnyString()));
     assertEquals("boo", Operations.getCommonPrefix(a));
   }
 
   public void testCommonPrefixDeadStates() throws Exception {
-    Automaton a = Operations.concatenate(Automata.makeAnyString(), Automata.makeString("boo"));
+    Automaton a =
+        Operations.concatenate(List.of(Automata.makeAnyString(), Automata.makeString("boo")));
     // reverse it twice, to create some dead states
     // TODO: is it possible to fix reverse() to not create dead states?!
     Automaton withDeadStates = Operations.reverse(Operations.reverse(a));
@@ -152,7 +159,8 @@ public class TestAutomaton extends LuceneTestCase {
   }
 
   public void testCommonPrefixRemoveDeadStates() throws Exception {
-    Automaton a = Operations.concatenate(Automata.makeAnyString(), Automata.makeString("boo"));
+    Automaton a =
+        Operations.concatenate(List.of(Automata.makeAnyString(), Automata.makeString("boo")));
     // reverse it twice, to create some dead states
     // TODO: is it possible to fix reverse() to not create dead states?!
     Automaton withDeadStates = Operations.reverse(Operations.reverse(a));
@@ -201,12 +209,14 @@ public class TestAutomaton extends LuceneTestCase {
   }
 
   public void testCommonPrefixUnicode() throws Exception {
-    Automaton a = Operations.concatenate(Automata.makeString("boo😂😂😂"), Automata.makeAnyChar());
+    Automaton a =
+        Operations.concatenate(List.of(Automata.makeString("boo😂😂😂"), Automata.makeAnyChar()));
     assertEquals("boo😂😂😂", Operations.getCommonPrefix(a));
   }
 
   public void testConcatenate1() throws Exception {
-    Automaton a = Operations.concatenate(Automata.makeString("m"), Automata.makeAnyString());
+    Automaton a =
+        Operations.concatenate(List.of(Automata.makeString("m"), Automata.makeAnyString()));
     assertTrue(Operations.run(a, "m"));
     assertTrue(Operations.run(a, "me"));
     assertTrue(Operations.run(a, "me too"));
@@ -373,23 +383,26 @@ public class TestAutomaton extends LuceneTestCase {
   }
 
   public void testCommonSuffixTrailingWildcard() throws Exception {
-    Automaton a = Operations.concatenate(Automata.makeString("boo"), Automata.makeAnyChar());
+    Automaton a =
+        Operations.concatenate(List.of(Automata.makeString("boo"), Automata.makeAnyChar()));
     assertEquals(newBytesRef(), Operations.getCommonSuffixBytesRef(a));
   }
 
   public void testCommonSuffixLeadingKleenStar() throws Exception {
-    Automaton a = Operations.concatenate(Automata.makeAnyString(), Automata.makeString("boo"));
+    Automaton a =
+        Operations.concatenate(List.of(Automata.makeAnyString(), Automata.makeString("boo")));
     assertEquals(newBytesRef("boo"), Operations.getCommonSuffixBytesRef(a));
   }
 
   public void testCommonSuffixTrailingKleenStar() throws Exception {
-    Automaton a = Operations.concatenate(Automata.makeString("boo"), Automata.makeAnyString());
+    Automaton a =
+        Operations.concatenate(List.of(Automata.makeString("boo"), Automata.makeAnyString()));
     assertEquals(newBytesRef(), Operations.getCommonSuffixBytesRef(a));
   }
 
   public void testCommonSuffixUnicode() throws Exception {
     Automaton a =
-        Operations.concatenate(Automata.makeAnyString(), Automata.makeString("boo😂😂😂"));
+        Operations.concatenate(List.of(Automata.makeAnyString(), Automata.makeString("boo😂😂😂")));
     Automaton binary = new UTF32ToUTF8().convert(a);
     assertEquals(newBytesRef("boo😂😂😂"), Operations.getCommonSuffixBytesRef(binary));
   }
@@ -702,10 +715,10 @@ public class TestAutomaton extends LuceneTestCase {
 
   public void testConcatEmpty() throws Exception {
     // If you concat empty automaton to anything the result should still be empty:
-    Automaton a = Operations.concatenate(Automata.makeEmpty(), Automata.makeString("foo"));
+    Automaton a = Operations.concatenate(List.of(Automata.makeEmpty(), Automata.makeString("foo")));
     assertEquals(new HashSet<IntsRef>(), TestOperations.getFiniteStrings(a));
 
-    a = Operations.concatenate(Automata.makeString("foo"), Automata.makeEmpty());
+    a = Operations.concatenate(List.of(Automata.makeString("foo"), Automata.makeEmpty()));
     assertEquals(new HashSet<IntsRef>(), TestOperations.getFiniteStrings(a));
   }
 
@@ -778,12 +791,12 @@ public class TestAutomaton extends LuceneTestCase {
         if (VERBOSE) {
           System.out.println("  randomNoOp: concat empty string");
         }
-        return Operations.concatenate(a, Automata.makeEmptyString());
+        return Operations.concatenate(List.of(a, Automata.makeEmptyString()));
       case 5:
         if (VERBOSE) {
           System.out.println("  randomNoOp: union empty automaton");
         }
-        return Operations.union(a, Automata.makeEmpty());
+        return Operations.union(List.of(a, Automata.makeEmpty()));
       case 6:
         if (VERBOSE) {
           System.out.println("  randomNoOp: do nothing!");
@@ -881,9 +894,9 @@ public class TestAutomaton extends LuceneTestCase {
             }
             terms = newTerms;
             boolean wasDeterministic = a.isDeterministic();
-            a = Operations.concatenate(Automata.makeString(prefix.utf8ToString()), a);
+            a = Operations.concatenate(List.of(Automata.makeString(prefix.utf8ToString()), a));
             if (wasDeterministic) {
-              assertTrue(a.isDeterministic());
+              assertEquals(wasDeterministic, a.isDeterministic());
             }
           }
           break;
@@ -903,7 +916,7 @@ public class TestAutomaton extends LuceneTestCase {
               newTerms.add(newTerm.toBytesRef());
             }
             terms = newTerms;
-            a = Operations.concatenate(a, Automata.makeString(suffix.utf8ToString()));
+            a = Operations.concatenate(List.of(a, Automata.makeString(suffix.utf8ToString())));
           }
           break;
 
@@ -941,7 +954,7 @@ public class TestAutomaton extends LuceneTestCase {
             }
             terms.addAll(newTerms);
             Automaton newA = unionTerms(newTerms);
-            a = Operations.union(a, newA);
+            a = Operations.union(List.of(a, newA));
           }
           break;
 
@@ -1114,7 +1127,7 @@ public class TestAutomaton extends LuceneTestCase {
               System.out.println(
                   "  op=union interval min=" + min + " max=" + max + " digits=" + digits);
             }
-            a = Operations.union(a, Automata.makeDecimalInterval(min, max, digits));
+            a = Operations.union(List.of(a, Automata.makeDecimalInterval(min, max, digits)));
             StringBuilder b = new StringBuilder();
             for (int i = 0; i < digits; i++) {
               b.append('0');
@@ -1143,7 +1156,7 @@ public class TestAutomaton extends LuceneTestCase {
           if (VERBOSE) {
             System.out.println("  op=add the empty string");
           }
-          a = Operations.union(a, Automata.makeEmptyString());
+          a = Operations.union(List.of(a, Automata.makeEmptyString()));
           terms.add(newBytesRef());
           break;
 
@@ -1170,7 +1183,7 @@ public class TestAutomaton extends LuceneTestCase {
               if (VERBOSE) {
                 System.out.println("  do suffix");
               }
-              a = Operations.concatenate(a, randomNoOp(a2));
+              a = Operations.concatenate(List.of(a, randomNoOp(a2)));
               BytesRefBuilder newTerm = new BytesRefBuilder();
               for (BytesRef term : terms) {
                 for (BytesRef suffix : addTerms) {
@@ -1184,7 +1197,7 @@ public class TestAutomaton extends LuceneTestCase {
               if (VERBOSE) {
                 System.out.println("  do prefix");
               }
-              a = Operations.concatenate(randomNoOp(a2), a);
+              a = Operations.concatenate(List.of(randomNoOp(a2), a));
               BytesRefBuilder newTerm = new BytesRefBuilder();
               for (BytesRef term : terms) {
                 for (BytesRef prefix : addTerms) {
@@ -1707,7 +1720,7 @@ public class TestAutomaton extends LuceneTestCase {
   }
 
   public void testMakeCharSetTwo() {
-    Automaton expected = Operations.union(Automata.makeChar('a'), Automata.makeChar('A'));
+    Automaton expected = Operations.union(List.of(Automata.makeChar('a'), Automata.makeChar('A')));
     Automaton actual = Automata.makeCharSet(new int[] {'a', 'A'});
     assertTrue(AutomatonTestUtil.sameLanguage(expected, actual));
     assertTrue(actual.isDeterministic());
diff --git a/lucene/core/src/test/org/apache/lucene/util/automaton/TestDeterminism.java b/lucene/core/src/test/org/apache/lucene/util/automaton/TestDeterminism.java
index e69568d3873..b9aa90df876 100644
--- a/lucene/core/src/test/org/apache/lucene/util/automaton/TestDeterminism.java
+++ b/lucene/core/src/test/org/apache/lucene/util/automaton/TestDeterminism.java
@@ -18,6 +18,7 @@ package org.apache.lucene.util.automaton;
 
 import static org.apache.lucene.util.automaton.Operations.DEFAULT_DETERMINIZE_WORK_LIMIT;
 
+import java.util.List;
 import org.apache.lucene.tests.util.LuceneTestCase;
 import org.apache.lucene.tests.util.automaton.AutomatonTestUtil;
 
@@ -58,7 +59,8 @@ public class TestDeterminism extends LuceneTestCase {
     // a union a = a
     equivalent =
         Operations.determinize(
-            Operations.removeDeadStates(Operations.union(a, a)), DEFAULT_DETERMINIZE_WORK_LIMIT);
+            Operations.removeDeadStates(Operations.union(List.of(a, a))),
+            DEFAULT_DETERMINIZE_WORK_LIMIT);
     assertTrue(AutomatonTestUtil.sameLanguage(a, equivalent));
 
     // a intersect a = a
diff --git a/lucene/core/src/test/org/apache/lucene/util/automaton/TestFiniteStringsIterator.java b/lucene/core/src/test/org/apache/lucene/util/automaton/TestFiniteStringsIterator.java
index 8070d7af479..b8d16ac9b2e 100644
--- a/lucene/core/src/test/org/apache/lucene/util/automaton/TestFiniteStringsIterator.java
+++ b/lucene/core/src/test/org/apache/lucene/util/automaton/TestFiniteStringsIterator.java
@@ -96,7 +96,8 @@ public class TestFiniteStringsIterator extends LuceneTestCase {
 
   /** Basic test for getFiniteStrings */
   public void testFiniteStringsBasic() {
-    Automaton a = Operations.union(Automata.makeString("dog"), Automata.makeString("duck"));
+    Automaton a =
+        Operations.union(List.of(Automata.makeString("dog"), Automata.makeString("duck")));
     a = MinimizationOperations.minimize(a, DEFAULT_DETERMINIZE_WORK_LIMIT);
     FiniteStringsIterator iterator = new FiniteStringsIterator(a);
     List<IntsRef> actual = getFiniteStrings(iterator);
@@ -117,7 +118,7 @@ public class TestFiniteStringsIterator extends LuceneTestCase {
     TestUtil.randomFixedLengthUnicodeString(random(), chars, 0, chars.length);
     String bigString2 = new String(chars);
     Automaton a =
-        Operations.union(Automata.makeString(bigString1), Automata.makeString(bigString2));
+        Operations.union(List.of(Automata.makeString(bigString1), Automata.makeString(bigString2)));
     FiniteStringsIterator iterator = new FiniteStringsIterator(a);
     List<IntsRef> actual = getFiniteStrings(iterator);
     assertEquals(2, actual.size());
@@ -149,7 +150,7 @@ public class TestFiniteStringsIterator extends LuceneTestCase {
   }
 
   public void testShortAccept() {
-    Automaton a = Operations.union(Automata.makeString("x"), Automata.makeString("xy"));
+    Automaton a = Operations.union(List.of(Automata.makeString("x"), Automata.makeString("xy")));
     a = MinimizationOperations.minimize(a, DEFAULT_DETERMINIZE_WORK_LIMIT);
     FiniteStringsIterator iterator = new FiniteStringsIterator(a);
     List<IntsRef> actual = getFiniteStrings(iterator);
diff --git a/lucene/core/src/test/org/apache/lucene/util/automaton/TestLevenshteinAutomata.java b/lucene/core/src/test/org/apache/lucene/util/automaton/TestLevenshteinAutomata.java
index bc6d268c15e..fc6a04e80c3 100644
--- a/lucene/core/src/test/org/apache/lucene/util/automaton/TestLevenshteinAutomata.java
+++ b/lucene/core/src/test/org/apache/lucene/util/automaton/TestLevenshteinAutomata.java
@@ -16,8 +16,6 @@
  */
 package org.apache.lucene.util.automaton;
 
-import static org.apache.lucene.util.automaton.Operations.DEFAULT_DETERMINIZE_WORK_LIMIT;
-
 import java.util.ArrayList;
 import java.util.List;
 import org.apache.lucene.tests.util.LuceneTestCase;
@@ -134,15 +132,10 @@ public class TestLevenshteinAutomata extends LuceneTestCase {
    * Return an automaton that accepts all 1-character insertions, deletions, and substitutions of s.
    */
   private Automaton naiveLev1(String s) {
-    Automaton a = Automata.makeString(s);
-    a = Operations.union(a, insertionsOf(s));
-    a = MinimizationOperations.minimize(a, DEFAULT_DETERMINIZE_WORK_LIMIT);
-    a = Operations.union(a, deletionsOf(s));
-    a = MinimizationOperations.minimize(a, DEFAULT_DETERMINIZE_WORK_LIMIT);
-    a = Operations.union(a, substitutionsOf(s));
-    a = MinimizationOperations.minimize(a, DEFAULT_DETERMINIZE_WORK_LIMIT);
-
-    return a;
+    return Operations.determinize(
+        Operations.union(
+            List.of(Automata.makeString(s), insertionsOf(s), deletionsOf(s), substitutionsOf(s))),
+        Operations.DEFAULT_DETERMINIZE_WORK_LIMIT);
   }
 
   /**
@@ -150,10 +143,15 @@ public class TestLevenshteinAutomata extends LuceneTestCase {
    * transpositions of s.
    */
   private Automaton naiveLev1T(String s) {
-    Automaton a = naiveLev1(s);
-    a = Operations.union(a, transpositionsOf(s));
-    a = MinimizationOperations.minimize(a, DEFAULT_DETERMINIZE_WORK_LIMIT);
-    return a;
+    return Operations.determinize(
+        Operations.union(
+            List.of(
+                Automata.makeString(s),
+                insertionsOf(s),
+                deletionsOf(s),
+                substitutionsOf(s),
+                transpositionsOf(s))),
+        Operations.DEFAULT_DETERMINIZE_WORK_LIMIT);
   }
 
   /** Return an automaton that accepts all 1-character insertions of s (inserting one character) */
@@ -161,15 +159,13 @@ public class TestLevenshteinAutomata extends LuceneTestCase {
     List<Automaton> list = new ArrayList<>();
 
     for (int i = 0; i <= s.length(); i++) {
-      Automaton a = Automata.makeString(s.substring(0, i));
-      a = Operations.concatenate(a, Automata.makeAnyChar());
-      a = Operations.concatenate(a, Automata.makeString(s.substring(i)));
-      list.add(a);
+      Automaton pre = Automata.makeString(s.substring(0, i));
+      Automaton middle = Automata.makeAnyChar();
+      Automaton post = Automata.makeString(s.substring(i));
+      list.add(Operations.concatenate(List.of(pre, middle, post)));
     }
 
-    Automaton a = Operations.union(list);
-    a = MinimizationOperations.minimize(a, DEFAULT_DETERMINIZE_WORK_LIMIT);
-    return a;
+    return Operations.union(list);
   }
 
   /** Return an automaton that accepts all 1-character deletions of s (deleting one character). */
@@ -177,14 +173,12 @@ public class TestLevenshteinAutomata extends LuceneTestCase {
     List<Automaton> list = new ArrayList<>();
 
     for (int i = 0; i < s.length(); i++) {
-      Automaton a = Automata.makeString(s.substring(0, i));
-      a = Operations.concatenate(a, Automata.makeString(s.substring(i + 1)));
-      list.add(a);
+      Automaton pre = Automata.makeString(s.substring(0, i));
+      Automaton post = Automata.makeString(s.substring(i + 1));
+      list.add(Operations.concatenate(List.of(pre, post)));
     }
 
-    Automaton a = Operations.union(list);
-    a = MinimizationOperations.minimize(a, DEFAULT_DETERMINIZE_WORK_LIMIT);
-    return a;
+    return Operations.union(list);
   }
 
   /**
@@ -194,15 +188,13 @@ public class TestLevenshteinAutomata extends LuceneTestCase {
     List<Automaton> list = new ArrayList<>();
 
     for (int i = 0; i < s.length(); i++) {
-      Automaton a = Automata.makeString(s.substring(0, i));
-      a = Operations.concatenate(a, Automata.makeAnyChar());
-      a = Operations.concatenate(a, Automata.makeString(s.substring(i + 1)));
-      list.add(a);
+      Automaton pre = Automata.makeString(s.substring(0, i));
+      Automaton middle = Automata.makeAnyChar();
+      Automaton post = Automata.makeString(s.substring(i + 1));
+      list.add(Operations.concatenate(List.of(pre, middle, post)));
     }
 
-    Automaton a = Operations.union(list);
-    a = MinimizationOperations.minimize(a, DEFAULT_DETERMINIZE_WORK_LIMIT);
-    return a;
+    return Operations.union(list);
   }
 
   /**
@@ -224,9 +216,7 @@ public class TestLevenshteinAutomata extends LuceneTestCase {
         list.add(Automata.makeString(st));
       }
     }
-    Automaton a = Operations.union(list);
-    a = MinimizationOperations.minimize(a, DEFAULT_DETERMINIZE_WORK_LIMIT);
-    return a;
+    return Operations.union(list);
   }
 
   private void assertBruteForce(String input, Automaton dfa, int distance) {
diff --git a/lucene/core/src/test/org/apache/lucene/util/automaton/TestLimitedFiniteStringsIterator.java b/lucene/core/src/test/org/apache/lucene/util/automaton/TestLimitedFiniteStringsIterator.java
index 3d69bcde4ec..4b0873812f3 100644
--- a/lucene/core/src/test/org/apache/lucene/util/automaton/TestLimitedFiniteStringsIterator.java
+++ b/lucene/core/src/test/org/apache/lucene/util/automaton/TestLimitedFiniteStringsIterator.java
@@ -78,7 +78,7 @@ public class TestLimitedFiniteStringsIterator extends LuceneTestCase {
   }
 
   public void testLimit() {
-    Automaton a = Operations.union(Automata.makeString("foo"), Automata.makeString("bar"));
+    Automaton a = Operations.union(List.of(Automata.makeString("foo"), Automata.makeString("bar")));
 
     // Test without limit
     FiniteStringsIterator withoutLimit = new LimitedFiniteStringsIterator(a, -1);
@@ -90,7 +90,7 @@ public class TestLimitedFiniteStringsIterator extends LuceneTestCase {
   }
 
   public void testSize() {
-    Automaton a = Operations.union(Automata.makeString("foo"), Automata.makeString("bar"));
+    Automaton a = Operations.union(List.of(Automata.makeString("foo"), Automata.makeString("bar")));
     LimitedFiniteStringsIterator iterator = new LimitedFiniteStringsIterator(a, -1);
     List<IntsRef> actual = getFiniteStrings(iterator);
     assertEquals(2, actual.size());
diff --git a/lucene/core/src/test/org/apache/lucene/util/automaton/TestNFARunAutomaton.java b/lucene/core/src/test/org/apache/lucene/util/automaton/TestNFARunAutomaton.java
index c577c1de8de..338916f2f81 100644
--- a/lucene/core/src/test/org/apache/lucene/util/automaton/TestNFARunAutomaton.java
+++ b/lucene/core/src/test/org/apache/lucene/util/automaton/TestNFARunAutomaton.java
@@ -21,6 +21,7 @@ import java.io.IOException;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.HashSet;
+import java.util.List;
 import java.util.Set;
 import org.apache.lucene.document.Document;
 import org.apache.lucene.document.Field;
@@ -172,7 +173,7 @@ public class TestNFARunAutomaton extends LuceneTestCase {
         if (a == null) {
           a = Automata.makeString(term);
         } else {
-          a = Operations.union(a, Automata.makeString(term));
+          a = Operations.union(List.of(a, Automata.makeString(term)));
         }
       }
       if (a.isDeterministic()) {
diff --git a/lucene/core/src/test/org/apache/lucene/util/automaton/TestOperations.java b/lucene/core/src/test/org/apache/lucene/util/automaton/TestOperations.java
index 3de263030d6..c6fde722290 100644
--- a/lucene/core/src/test/org/apache/lucene/util/automaton/TestOperations.java
+++ b/lucene/core/src/test/org/apache/lucene/util/automaton/TestOperations.java
@@ -65,8 +65,8 @@ public class TestOperations extends LuceneTestCase {
 
   /** Test concatenation with empty language returns empty */
   public void testEmptyLanguageConcatenate() {
-    Automaton a = Automata.makeString("a");
-    Automaton concat = Operations.concatenate(a, Automata.makeEmpty());
+    Automaton concat =
+        Operations.concatenate(List.of(Automata.makeString("a"), Automata.makeEmpty()));
     assertTrue(Operations.isEmpty(concat));
   }
 
@@ -111,9 +111,10 @@ public class TestOperations extends LuceneTestCase {
     Automaton singleton = Automata.makeString("");
     Automaton expandedSingleton = singleton;
     // an NFA (two transitions for 't' from initial state)
-    Automaton nfa = Operations.union(Automata.makeString("this"), Automata.makeString("three"));
-    Automaton concat1 = Operations.concatenate(expandedSingleton, nfa);
-    Automaton concat2 = Operations.concatenate(singleton, nfa);
+    Automaton nfa =
+        Operations.union(List.of(Automata.makeString("this"), Automata.makeString("three")));
+    Automaton concat1 = Operations.concatenate(List.of(expandedSingleton, nfa));
+    Automaton concat2 = Operations.concatenate(List.of(singleton, nfa));
     assertFalse(concat2.isDeterministic());
     assertTrue(
         AutomatonTestUtil.sameLanguage(
@@ -167,7 +168,7 @@ public class TestOperations extends LuceneTestCase {
     TestUtil.randomFixedLengthUnicodeString(random(), chars, 0, chars.length);
     String bigString2 = new String(chars);
     Automaton a =
-        Operations.union(Automata.makeString(bigString1), Automata.makeString(bigString2));
+        Operations.union(List.of(Automata.makeString(bigString1), Automata.makeString(bigString2)));
     IllegalArgumentException exc =
         expectThrows(IllegalArgumentException.class, () -> AutomatonTestUtil.isFinite(a));
     assertTrue(exc.getMessage().contains("input automaton is too large"));
@@ -185,27 +186,31 @@ public class TestOperations extends LuceneTestCase {
     Automaton tricky =
         Operations.repeat(
             Operations.union(
-                Automata.makeCharRange(Character.MIN_CODE_POINT, 100),
-                Automata.makeCharRange(101, Character.MAX_CODE_POINT)));
+                List.of(
+                    Automata.makeCharRange(Character.MIN_CODE_POINT, 100),
+                    Automata.makeCharRange(101, Character.MAX_CODE_POINT))));
     assertTrue(Operations.isTotal(tricky));
     // not total, but close
     Automaton tricky2 =
         Operations.repeat(
             Operations.union(
-                Automata.makeCharRange(Character.MIN_CODE_POINT + 1, 100),
-                Automata.makeCharRange(101, Character.MAX_CODE_POINT)));
+                List.of(
+                    Automata.makeCharRange(Character.MIN_CODE_POINT + 1, 100),
+                    Automata.makeCharRange(101, Character.MAX_CODE_POINT))));
     assertFalse(Operations.isTotal(tricky2));
     Automaton tricky3 =
         Operations.repeat(
             Operations.union(
-                Automata.makeCharRange(Character.MIN_CODE_POINT, 99),
-                Automata.makeCharRange(101, Character.MAX_CODE_POINT)));
+                List.of(
+                    Automata.makeCharRange(Character.MIN_CODE_POINT, 99),
+                    Automata.makeCharRange(101, Character.MAX_CODE_POINT))));
     assertFalse(Operations.isTotal(tricky3));
     Automaton tricky4 =
         Operations.repeat(
             Operations.union(
-                Automata.makeCharRange(Character.MIN_CODE_POINT, 100),
-                Automata.makeCharRange(101, Character.MAX_CODE_POINT - 1)));
+                List.of(
+                    Automata.makeCharRange(Character.MIN_CODE_POINT, 100),
+                    Automata.makeCharRange(101, Character.MAX_CODE_POINT - 1))));
     assertFalse(Operations.isTotal(tricky4));
   }
 
@@ -327,7 +332,7 @@ public class TestOperations extends LuceneTestCase {
     assertTrue(AutomatonTestUtil.sameLanguage(abs, Operations.repeat(ab)));
     assertSame(abs, Operations.repeat(abs));
 
-    Automaton absThenC = Operations.concatenate(abs, Automata.makeChar('c'));
+    Automaton absThenC = Operations.concatenate(List.of(abs, Automata.makeChar('c')));
     Automaton absThenCs = new Automaton();
     absThenCs.createState();
     absThenCs.createState();
diff --git a/lucene/core/src/test/org/apache/lucene/util/automaton/TestRegExpParsing.java b/lucene/core/src/test/org/apache/lucene/util/automaton/TestRegExpParsing.java
index e0aec1e5351..51a14761d38 100644
--- a/lucene/core/src/test/org/apache/lucene/util/automaton/TestRegExpParsing.java
+++ b/lucene/core/src/test/org/apache/lucene/util/automaton/TestRegExpParsing.java
@@ -17,6 +17,7 @@
 package org.apache.lucene.util.automaton;
 
 import java.io.IOException;
+import java.util.List;
 import java.util.Map;
 import java.util.Set;
 import org.apache.lucene.tests.util.LuceneTestCase;
@@ -74,7 +75,7 @@ public class TestRegExpParsing extends LuceneTestCase {
     Automaton actual = re.toAutomaton();
     assertTrue(actual.isDeterministic());
 
-    Automaton expected = Operations.union(Automata.makeChar('c'), Automata.makeChar('C'));
+    Automaton expected = Operations.union(List.of(Automata.makeChar('c'), Automata.makeChar('C')));
     assertSameLanguage(expected, actual);
   }
 
@@ -100,7 +101,7 @@ public class TestRegExpParsing extends LuceneTestCase {
     Automaton actual = re.toAutomaton();
     assertTrue(actual.isDeterministic());
 
-    Automaton expected = Operations.union(Automata.makeChar('c'), Automata.makeChar('C'));
+    Automaton expected = Operations.union(List.of(Automata.makeChar('c'), Automata.makeChar('C')));
     assertSameLanguage(expected, actual);
   }
 
@@ -146,7 +147,8 @@ public class TestRegExpParsing extends LuceneTestCase {
 
     Automaton expected =
         Operations.union(
-            Automata.makeCharRange(0, 'b'), Automata.makeCharRange('d', Integer.MAX_VALUE));
+            List.of(
+                Automata.makeCharRange(0, 'b'), Automata.makeCharRange('d', Integer.MAX_VALUE)));
     assertSameLanguage(expected, actual);
   }
 
@@ -196,7 +198,8 @@ public class TestRegExpParsing extends LuceneTestCase {
 
     Automaton expected =
         Operations.union(
-            Automata.makeCharRange(0, 'a'), Automata.makeCharRange('e', Integer.MAX_VALUE));
+            List.of(
+                Automata.makeCharRange(0, 'a'), Automata.makeCharRange('e', Integer.MAX_VALUE)));
     assertSameLanguage(expected, actual);
   }
 
@@ -245,10 +248,13 @@ public class TestRegExpParsing extends LuceneTestCase {
     Automaton actual = re.toAutomaton();
     assertTrue(actual.isDeterministic());
 
-    Automaton expected = Automata.makeChar(' ');
-    expected = Operations.union(expected, Automata.makeChar('\n'));
-    expected = Operations.union(expected, Automata.makeChar('\r'));
-    expected = Operations.union(expected, Automata.makeChar('\t'));
+    Automaton expected =
+        Operations.union(
+            List.of(
+                Automata.makeChar(' '),
+                Automata.makeChar('\n'),
+                Automata.makeChar('\r'),
+                Automata.makeChar('\t')));
     assertSameLanguage(expected, actual);
   }
 
@@ -287,10 +293,13 @@ public class TestRegExpParsing extends LuceneTestCase {
     Automaton actual = re.toAutomaton();
     assertTrue(actual.isDeterministic());
 
-    Automaton expected = Automata.makeCharRange('a', 'z');
-    expected = Operations.union(expected, Automata.makeCharRange('A', 'Z'));
-    expected = Operations.union(expected, Automata.makeCharRange('0', '9'));
-    expected = Operations.union(expected, Automata.makeChar('_'));
+    Automaton expected =
+        Operations.union(
+            List.of(
+                Automata.makeCharRange('a', 'z'),
+                Automata.makeCharRange('A', 'Z'),
+                Automata.makeCharRange('0', '9'),
+                Automata.makeChar('_')));
     assertSameLanguage(expected, actual);
   }
 
@@ -544,11 +553,10 @@ public class TestRegExpParsing extends LuceneTestCase {
     Automaton actual = re.toAutomaton();
     assertTrue(actual.isDeterministic());
 
-    Automaton c1 = Operations.union(Automata.makeChar('b'), Automata.makeChar('B'));
-    Automaton c2 = Operations.union(Automata.makeChar('o'), Automata.makeChar('O'));
+    Automaton c1 = Operations.union(List.of(Automata.makeChar('b'), Automata.makeChar('B')));
+    Automaton c2 = Operations.union(List.of(Automata.makeChar('o'), Automata.makeChar('O')));
 
-    Automaton expected = Operations.concatenate(c1, c2);
-    expected = Operations.concatenate(expected, c2);
+    Automaton expected = Operations.concatenate(List.of(c1, c2, c2));
     assertSameLanguage(expected, actual);
   }
 
@@ -587,7 +595,8 @@ public class TestRegExpParsing extends LuceneTestCase {
     assertTrue(actual.isDeterministic());
 
     Automaton expected =
-        Operations.concatenate(Automata.makeCharRange('b', 'c'), Automata.makeCharRange('e', 'f'));
+        Operations.concatenate(
+            List.of(Automata.makeCharRange('b', 'c'), Automata.makeCharRange('e', 'f')));
     assertSameLanguage(expected, actual);
   }
 
@@ -641,7 +650,8 @@ public class TestRegExpParsing extends LuceneTestCase {
     assertTrue(actual.isDeterministic());
 
     Automaton expected =
-        Operations.union(Automata.makeCharRange('b', 'c'), Automata.makeCharRange('e', 'f'));
+        Operations.union(
+            List.of(Automata.makeCharRange('b', 'c'), Automata.makeCharRange('e', 'f')));
     assertSameLanguage(expected, actual);
   }
 
diff --git a/lucene/suggest/src/java/org/apache/lucene/search/suggest/document/ContextQuery.java b/lucene/suggest/src/java/org/apache/lucene/search/suggest/document/ContextQuery.java
index 496d3b9232d..71418f231c8 100644
--- a/lucene/suggest/src/java/org/apache/lucene/search/suggest/document/ContextQuery.java
+++ b/lucene/suggest/src/java/org/apache/lucene/search/suggest/document/ContextQuery.java
@@ -195,9 +195,10 @@ public class ContextQuery extends CompletionQuery implements Accountable {
     // include the SEP_LABEL in the query as well
     Automaton optionalSepLabel =
         Operations.optional(Automata.makeChar(ConcatenateGraphFilter.SEP_LABEL));
-    Automaton prefixAutomaton = Operations.concatenate(optionalSepLabel, innerAutomaton);
     Automaton contextsAutomaton =
-        Operations.concatenate(toContextAutomaton(contexts, matchAllContexts), prefixAutomaton);
+        Operations.concatenate(
+            List.of(
+                toContextAutomaton(contexts, matchAllContexts), optionalSepLabel, innerAutomaton));
     contextsAutomaton =
         Operations.determinize(contextsAutomaton, Operations.DEFAULT_DETERMINIZE_WORK_LIMIT);
 
@@ -230,18 +231,19 @@ public class ContextQuery extends CompletionQuery implements Accountable {
     final Automaton matchAllAutomaton = Operations.repeat(Automata.makeAnyString());
     final Automaton sep = Automata.makeChar(ContextSuggestField.CONTEXT_SEPARATOR);
     if (matchAllContexts || contexts.size() == 0) {
-      return Operations.concatenate(matchAllAutomaton, sep);
+      return Operations.concatenate(List.of(matchAllAutomaton, sep));
     } else {
       List<Automaton> automataList = new ArrayList<>();
       for (Map.Entry<IntsRef, ContextMetaData> entry : contexts.entrySet()) {
         final ContextMetaData contextMetaData = entry.getValue();
         final IntsRef ref = entry.getKey();
-        Automaton contextAutomaton = Automata.makeString(ref.ints, ref.offset, ref.length);
+        final List<Automaton> contextAutomaton = new ArrayList<>();
+        contextAutomaton.add(Automata.makeString(ref.ints, ref.offset, ref.length));
         if (contextMetaData.exact == false) {
-          contextAutomaton = Operations.concatenate(contextAutomaton, matchAllAutomaton);
+          contextAutomaton.add(matchAllAutomaton);
         }
-        contextAutomaton = Operations.concatenate(contextAutomaton, sep);
-        automataList.add(contextAutomaton);
+        contextAutomaton.add(sep);
+        automataList.add(Operations.concatenate(contextAutomaton));
       }
       Automaton contextsAutomaton = Operations.union(automataList);
       return contextsAutomaton;
diff --git a/lucene/test-framework/src/java/org/apache/lucene/tests/util/automaton/AutomatonTestUtil.java b/lucene/test-framework/src/java/org/apache/lucene/tests/util/automaton/AutomatonTestUtil.java
index 5a308f37133..686c12323a4 100644
--- a/lucene/test-framework/src/java/org/apache/lucene/tests/util/automaton/AutomatonTestUtil.java
+++ b/lucene/test-framework/src/java/org/apache/lucene/tests/util/automaton/AutomatonTestUtil.java
@@ -285,9 +285,9 @@ public class AutomatonTestUtil {
     // combine them in random ways
     switch (random.nextInt(4)) {
       case 0:
-        return Operations.concatenate(a1, a2);
+        return Operations.concatenate(List.of(a1, a2));
       case 1:
-        return Operations.union(a1, a2);
+        return Operations.union(List.of(a1, a2));
       case 2:
         return Operations.intersection(a1, a2);
       default: