You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@lucene.apache.org by br...@apache.org on 2024/05/27 08:51:28 UTC

(lucene) 01/02: Replace List by IntArrayList and List by LongArrayList. (#13406)

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

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

commit a8def94339110d7f18e0abc026e211e4eebcd46f
Author: Bruno Roustant <33...@users.noreply.github.com>
AuthorDate: Sat May 25 19:53:42 2024 +0200

    Replace List<Integer> by IntArrayList and List<Long> by LongArrayList. (#13406)
---
 lucene/CHANGES.txt                                 |   2 +
 .../lucene/analysis/core/FlattenGraphFilter.java   |  16 +-
 .../lucene/analysis/hunspell/Dictionary.java       |  20 +-
 .../lucene/analysis/hunspell/WordStorage.java      |   3 +-
 .../path/ReversePathHierarchyTokenizer.java        |   7 +-
 .../apache/lucene/analysis/synonym/SynonymMap.java |   4 +-
 .../lucene/analysis/ko/dict/UserDictionary.java    |  15 +-
 .../lucene/analysis/cn/smart/hhmm/BiSegGraph.java  |   8 +-
 .../lucene/benchmark/byTask/utils/Config.java      |   7 +-
 .../lucene/analysis/AutomatonToTokenStream.java    |  12 +-
 .../Lucene99ScalarQuantizedVectorsWriter.java      |   5 +-
 .../org/apache/lucene/index/SegmentDocValues.java  |  24 +-
 .../lucene/index/SegmentDocValuesProducer.java     |   5 +-
 .../org/apache/lucene/index/SegmentReader.java     |   4 +-
 .../org/apache/lucene/search/MultiPhraseQuery.java |  16 +-
 .../java/org/apache/lucene/search/PhraseQuery.java |  13 +-
 .../lucene/util/automaton/CompiledAutomaton.java   |   7 +-
 .../apache/lucene/util/automaton/UTF32ToUTF8.java  |   7 +-
 .../java/org/apache/lucene/util/bkd/BKDWriter.java |   3 +-
 .../src/java/org/apache/lucene/util/fst/Util.java  |   8 +-
 .../util/graph/GraphTokenStreamFiniteStrings.java  |   9 +-
 .../org/apache/lucene/util/hnsw/HnswGraph.java     |   9 +-
 .../apache/lucene/util/hnsw/OnHeapHnswGraph.java   |   9 +-
 .../org/apache/lucene/util/hppc/IntArrayList.java  | 454 ++++++++++++++++++++
 .../org/apache/lucene/util/hppc/LongArrayList.java | 454 ++++++++++++++++++++
 .../apache/lucene/util/hppc/TestIntArrayList.java  | 466 +++++++++++++++++++++
 .../apache/lucene/util/hppc/TestLongArrayList.java | 466 +++++++++++++++++++++
 .../facet/range/OverlappingLongRangeCounter.java   |  14 +-
 .../directory/DirectoryTaxonomyReader.java         |   8 +-
 .../DiversifyingNearestChildrenKnnCollector.java   |   2 +-
 .../search/join/PointInSetIncludingScoreQuery.java |  18 +-
 .../document/FloatPointNearestNeighbor.java        |   5 +-
 .../search/LatLonPointPrototypeQueries.java        |   3 +-
 .../lucene/sandbox/search/NearestNeighbor.java     |   3 +-
 .../lucene/sandbox/search/QueryProfilerTree.java   |  22 +-
 lucene/spatial3d/build.gradle                      |   3 +
 lucene/spatial3d/src/java/module-info.java         |   2 +
 .../lucene/spatial3d/geom/GeoStandardPath.java     |   7 +-
 .../lucene/spatial3d/geom/StandardObjects.java     |   2 +-
 39 files changed, 1996 insertions(+), 146 deletions(-)

diff --git a/lucene/CHANGES.txt b/lucene/CHANGES.txt
index 5ceabcd7c59..77def8c32cc 100644
--- a/lucene/CHANGES.txt
+++ b/lucene/CHANGES.txt
@@ -128,6 +128,8 @@ Optimizations
 
 * GITHUB#13400: Replace Set<Integer> by IntHashSet and Set<Long> by LongHashSet. (Bruno Roustant)
 
+* GITHUB#13406: Replace List<Integer> by IntArrayList and List<Long> by LongArrayList. (Bruno Roustant)
+
 Bug Fixes
 ---------------------
 
diff --git a/lucene/analysis/common/src/java/org/apache/lucene/analysis/core/FlattenGraphFilter.java b/lucene/analysis/common/src/java/org/apache/lucene/analysis/core/FlattenGraphFilter.java
index 9f3a93e00e2..b72a3aa9a8a 100644
--- a/lucene/analysis/common/src/java/org/apache/lucene/analysis/core/FlattenGraphFilter.java
+++ b/lucene/analysis/common/src/java/org/apache/lucene/analysis/core/FlattenGraphFilter.java
@@ -19,7 +19,6 @@ package org.apache.lucene.analysis.core;
 
 import java.io.IOException;
 import java.util.ArrayList;
-import java.util.Collections;
 import java.util.List;
 import org.apache.lucene.analysis.TokenFilter;
 import org.apache.lucene.analysis.TokenStream;
@@ -29,6 +28,8 @@ import org.apache.lucene.analysis.tokenattributes.PositionIncrementAttribute;
 import org.apache.lucene.analysis.tokenattributes.PositionLengthAttribute;
 import org.apache.lucene.util.AttributeSource;
 import org.apache.lucene.util.RollingBuffer;
+import org.apache.lucene.util.hppc.IntArrayList;
+import org.apache.lucene.util.hppc.IntCursor;
 
 /**
  * Converts an incoming graph token stream, such as one from {@link SynonymGraphFilter}, into a flat
@@ -90,7 +91,7 @@ public final class FlattenGraphFilter extends TokenFilter {
    * "frontier" of nodes we've seen but can't yet output because they are not frozen.
    */
   private static final class OutputNode implements RollingBuffer.Resettable {
-    private final List<Integer> inputNodes = new ArrayList<>();
+    private final IntArrayList inputNodes = new IntArrayList();
 
     /** Node ID for this output, or -1 if we haven't been assigned yet. */
     int node = -1;
@@ -175,8 +176,8 @@ public final class FlattenGraphFilter extends TokenFilter {
       }
 
       int maxToNode = -1;
-      for (int inputNodeID : output.inputNodes) {
-        InputNode inputNode = inputNodes.get(inputNodeID);
+      for (IntCursor inputNodeID : output.inputNodes) {
+        InputNode inputNode = inputNodes.get(inputNodeID.value);
         assert inputNode.outputNode == outputFrom;
         maxToNode = Math.max(maxToNode, inputNode.maxToNode);
       }
@@ -280,7 +281,7 @@ public final class FlattenGraphFilter extends TokenFilter {
     Related tests testShingledGap, testShingledGapWithHoles
     */
     outputFrom++;
-    int freeBefore = Collections.min(output.inputNodes);
+    int freeBefore = output.inputNodes.stream().min().orElseThrow();
     // This will catch a node being freed early if it is input to the next output.
     // Could a freed early node be input to a later output?
     assert outputNodes.get(outputFrom).inputNodes.stream().filter(n -> freeBefore > n).count() == 0
@@ -349,7 +350,7 @@ public final class FlattenGraphFilter extends TokenFilter {
              * The last node in the alt path didn't arrive to remove this reference.
              */
             assert inputNodes.get(inputFrom).tokens.isEmpty() : "about to remove non empty edge";
-            outSrc.inputNodes.remove(Integer.valueOf(inputFrom));
+            outSrc.inputNodes.removeElement(inputFrom);
             src.outputNode = -1;
             int prevEndOffset = outSrc.endOffset;
 
@@ -381,8 +382,7 @@ public final class FlattenGraphFilter extends TokenFilter {
 
         if (outputEndNode > dest.outputNode) {
           if (dest.outputNode != -1) {
-            boolean removed =
-                outputNodes.get(dest.outputNode).inputNodes.remove(Integer.valueOf(inputTo));
+            boolean removed = outputNodes.get(dest.outputNode).inputNodes.removeElement(inputTo);
             assert removed;
           }
           // System.out.println("    increase output node: " + dest.outputNode + " vs " +
diff --git a/lucene/analysis/common/src/java/org/apache/lucene/analysis/hunspell/Dictionary.java b/lucene/analysis/common/src/java/org/apache/lucene/analysis/hunspell/Dictionary.java
index 3be870efd36..74594ebdbcb 100644
--- a/lucene/analysis/common/src/java/org/apache/lucene/analysis/hunspell/Dictionary.java
+++ b/lucene/analysis/common/src/java/org/apache/lucene/analysis/hunspell/Dictionary.java
@@ -60,6 +60,8 @@ import org.apache.lucene.util.fst.FST;
 import org.apache.lucene.util.fst.FSTCompiler;
 import org.apache.lucene.util.fst.IntSequenceOutputs;
 import org.apache.lucene.util.fst.Util;
+import org.apache.lucene.util.hppc.IntArrayList;
+import org.apache.lucene.util.hppc.IntCursor;
 
 /** In-memory structure for the dictionary (.dic) and affix (.aff) data of a hunspell dictionary. */
 public class Dictionary {
@@ -330,8 +332,8 @@ public class Dictionary {
    */
   private void readAffixFile(InputStream affixStream, CharsetDecoder decoder, FlagEnumerator flags)
       throws IOException, ParseException {
-    TreeMap<String, List<Integer>> prefixes = new TreeMap<>();
-    TreeMap<String, List<Integer>> suffixes = new TreeMap<>();
+    TreeMap<String, IntArrayList> prefixes = new TreeMap<>();
+    TreeMap<String, IntArrayList> suffixes = new TreeMap<>();
     Set<Character> prefixContFlags = new HashSet<>();
     Set<Character> suffixContFlags = new HashSet<>();
     Map<String, Integer> seenPatterns = new HashMap<>();
@@ -643,17 +645,17 @@ public class Dictionary {
     return new Breaks(starting, ending, middle);
   }
 
-  private FST<IntsRef> affixFST(TreeMap<String, List<Integer>> affixes) throws IOException {
+  private FST<IntsRef> affixFST(TreeMap<String, IntArrayList> affixes) throws IOException {
     IntSequenceOutputs outputs = IntSequenceOutputs.getSingleton();
     FSTCompiler<IntsRef> fstCompiler =
         new FSTCompiler.Builder<>(FST.INPUT_TYPE.BYTE4, outputs).build();
     IntsRefBuilder scratch = new IntsRefBuilder();
-    for (Map.Entry<String, List<Integer>> entry : affixes.entrySet()) {
+    for (Map.Entry<String, IntArrayList> entry : affixes.entrySet()) {
       Util.toUTF32(entry.getKey(), scratch);
-      List<Integer> entries = entry.getValue();
+      IntArrayList entries = entry.getValue();
       IntsRef output = new IntsRef(entries.size());
-      for (Integer c : entries) {
-        output.ints[output.length++] = c;
+      for (IntCursor c : entries) {
+        output.ints[output.length++] = c.value;
       }
       fstCompiler.add(scratch.get(), output);
     }
@@ -670,7 +672,7 @@ public class Dictionary {
    * @throws IOException Can be thrown while reading the rule
    */
   private void parseAffix(
-      TreeMap<String, List<Integer>> affixes,
+      TreeMap<String, IntArrayList> affixes,
       Set<Character> secondStageFlags,
       String header,
       LineNumberReader reader,
@@ -792,7 +794,7 @@ public class Dictionary {
         affixArg = new StringBuilder(affixArg).reverse().toString();
       }
 
-      affixes.computeIfAbsent(affixArg, __ -> new ArrayList<>()).add(currentAffix);
+      affixes.computeIfAbsent(affixArg, __ -> new IntArrayList()).add(currentAffix);
       currentAffix++;
     }
   }
diff --git a/lucene/analysis/common/src/java/org/apache/lucene/analysis/hunspell/WordStorage.java b/lucene/analysis/common/src/java/org/apache/lucene/analysis/hunspell/WordStorage.java
index b05ec27a8b4..5bf9585f719 100644
--- a/lucene/analysis/common/src/java/org/apache/lucene/analysis/hunspell/WordStorage.java
+++ b/lucene/analysis/common/src/java/org/apache/lucene/analysis/hunspell/WordStorage.java
@@ -29,6 +29,7 @@ import org.apache.lucene.util.CharsRef;
 import org.apache.lucene.util.IntsRef;
 import org.apache.lucene.util.IntsRefBuilder;
 import org.apache.lucene.util.fst.IntSequenceOutputs;
+import org.apache.lucene.util.hppc.IntArrayList;
 
 /**
  * A data structure for memory-efficient word storage and fast lookup/enumeration. Each dictionary
@@ -262,7 +263,7 @@ class WordStorage {
 
     private final IntsRefBuilder currentOrds = new IntsRefBuilder();
     private final List<char[]> group = new ArrayList<>();
-    private final List<Integer> morphDataIDs = new ArrayList<>();
+    private final IntArrayList morphDataIDs = new IntArrayList();
     private String currentEntry = null;
     private final int wordCount;
     private final double hashFactor;
diff --git a/lucene/analysis/common/src/java/org/apache/lucene/analysis/path/ReversePathHierarchyTokenizer.java b/lucene/analysis/common/src/java/org/apache/lucene/analysis/path/ReversePathHierarchyTokenizer.java
index 7b1f60f51c4..a5a144a561f 100644
--- a/lucene/analysis/common/src/java/org/apache/lucene/analysis/path/ReversePathHierarchyTokenizer.java
+++ b/lucene/analysis/common/src/java/org/apache/lucene/analysis/path/ReversePathHierarchyTokenizer.java
@@ -17,14 +17,13 @@
 package org.apache.lucene.analysis.path;
 
 import java.io.IOException;
-import java.util.ArrayList;
-import java.util.List;
 import org.apache.lucene.analysis.Tokenizer;
 import org.apache.lucene.analysis.tokenattributes.CharTermAttribute;
 import org.apache.lucene.analysis.tokenattributes.OffsetAttribute;
 import org.apache.lucene.analysis.tokenattributes.PositionIncrementAttribute;
 import org.apache.lucene.util.AttributeFactory;
 import org.apache.lucene.util.IgnoreRandomChains;
+import org.apache.lucene.util.hppc.IntArrayList;
 
 /**
  * Tokenizer for domain-like hierarchies.
@@ -99,7 +98,7 @@ public class ReversePathHierarchyTokenizer extends Tokenizer {
     this.skip = skip;
     resultToken = new StringBuilder(bufferSize);
     resultTokenBuffer = new char[bufferSize];
-    delimiterPositions = new ArrayList<>(bufferSize / 10);
+    delimiterPositions = new IntArrayList(bufferSize / 10);
   }
 
   private static final int DEFAULT_BUFFER_SIZE = 1024;
@@ -119,7 +118,7 @@ public class ReversePathHierarchyTokenizer extends Tokenizer {
   private int skipped = 0;
   private StringBuilder resultToken;
 
-  private List<Integer> delimiterPositions;
+  private IntArrayList delimiterPositions;
   private int delimitersCount = -1;
   private char[] resultTokenBuffer;
 
diff --git a/lucene/analysis/common/src/java/org/apache/lucene/analysis/synonym/SynonymMap.java b/lucene/analysis/common/src/java/org/apache/lucene/analysis/synonym/SynonymMap.java
index 29ab08e895b..7c23cfd2378 100644
--- a/lucene/analysis/common/src/java/org/apache/lucene/analysis/synonym/SynonymMap.java
+++ b/lucene/analysis/common/src/java/org/apache/lucene/analysis/synonym/SynonymMap.java
@@ -19,7 +19,6 @@ package org.apache.lucene.analysis.synonym;
 import java.io.IOException;
 import java.io.Reader;
 import java.text.ParseException;
-import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.HashMap;
 import java.util.Set;
@@ -38,6 +37,7 @@ import org.apache.lucene.util.fst.ByteSequenceOutputs;
 import org.apache.lucene.util.fst.FST;
 import org.apache.lucene.util.fst.FSTCompiler;
 import org.apache.lucene.util.fst.Util;
+import org.apache.lucene.util.hppc.IntArrayList;
 import org.apache.lucene.util.hppc.IntHashSet;
 
 /**
@@ -91,7 +91,7 @@ public class SynonymMap {
     private static class MapEntry {
       boolean includeOrig;
       // we could sort for better sharing ultimately, but it could confuse people
-      ArrayList<Integer> ords = new ArrayList<>();
+      IntArrayList ords = new IntArrayList();
     }
 
     /**
diff --git a/lucene/analysis/nori/src/java/org/apache/lucene/analysis/ko/dict/UserDictionary.java b/lucene/analysis/nori/src/java/org/apache/lucene/analysis/ko/dict/UserDictionary.java
index 64a06cd1abe..5614451df64 100644
--- a/lucene/analysis/nori/src/java/org/apache/lucene/analysis/ko/dict/UserDictionary.java
+++ b/lucene/analysis/nori/src/java/org/apache/lucene/analysis/ko/dict/UserDictionary.java
@@ -88,8 +88,9 @@ public final class UserDictionary implements Dictionary {
 
     String lastToken = null;
     List<int[]> segmentations = new ArrayList<>(entries.size());
-    List<Short> rightIds = new ArrayList<>(entries.size());
+    short[] rightIds = new short[entries.size()];
     long ord = 0;
+    int entryIndex = 0;
     for (String entry : entries) {
       String[] splits = entry.split("\\s+");
       String token = splits[0];
@@ -99,12 +100,12 @@ public final class UserDictionary implements Dictionary {
       char lastChar = entry.charAt(entry.length() - 1);
       if (charDef.isHangul(lastChar)) {
         if (charDef.hasCoda(lastChar)) {
-          rightIds.add(RIGHT_ID_T);
+          rightIds[entryIndex++] = RIGHT_ID_T;
         } else {
-          rightIds.add(RIGHT_ID_F);
+          rightIds[entryIndex++] = RIGHT_ID_F;
         }
       } else {
-        rightIds.add(RIGHT_ID);
+        rightIds[entryIndex++] = RIGHT_ID;
       }
 
       if (splits.length == 1) {
@@ -140,10 +141,8 @@ public final class UserDictionary implements Dictionary {
     this.fst =
         new TokenInfoFST(FST.fromFSTReader(fstCompiler.compile(), fstCompiler.getFSTReader()));
     this.segmentations = segmentations.toArray(new int[segmentations.size()][]);
-    this.rightIds = new short[rightIds.size()];
-    for (int i = 0; i < rightIds.size(); i++) {
-      this.rightIds[i] = rightIds.get(i);
-    }
+    assert entryIndex == rightIds.length;
+    this.rightIds = rightIds;
   }
 
   public TokenInfoFST getFST() {
diff --git a/lucene/analysis/smartcn/src/java/org/apache/lucene/analysis/cn/smart/hhmm/BiSegGraph.java b/lucene/analysis/smartcn/src/java/org/apache/lucene/analysis/cn/smart/hhmm/BiSegGraph.java
index a57f6cfc20d..92cff58002a 100644
--- a/lucene/analysis/smartcn/src/java/org/apache/lucene/analysis/cn/smart/hhmm/BiSegGraph.java
+++ b/lucene/analysis/smartcn/src/java/org/apache/lucene/analysis/cn/smart/hhmm/BiSegGraph.java
@@ -19,6 +19,7 @@ package org.apache.lucene.analysis.cn.smart.hhmm;
 import java.util.ArrayList;
 import java.util.List;
 import org.apache.lucene.analysis.cn.smart.Utility;
+import org.apache.lucene.util.hppc.IntArrayList;
 import org.apache.lucene.util.hppc.IntObjectHashMap;
 import org.apache.lucene.util.hppc.ObjectCursor;
 
@@ -197,19 +198,18 @@ class BiSegGraph {
     int preNode, lastNode;
     lastNode = path.size() - 1;
     current = lastNode;
-    List<Integer> rpath = new ArrayList<>();
+    IntArrayList rpath = new IntArrayList();
     List<SegToken> resultPath = new ArrayList<>();
 
     rpath.add(current);
     while (current != 0) {
       PathNode currentPathNode = path.get(current);
       preNode = currentPathNode.preNode;
-      rpath.add(Integer.valueOf(preNode));
+      rpath.add(preNode);
       current = preNode;
     }
     for (int j = rpath.size() - 1; j >= 0; j--) {
-      Integer idInteger = rpath.get(j);
-      int id = idInteger.intValue();
+      int id = rpath.get(j);
       SegToken t = segTokenList.get(id);
       resultPath.add(t);
     }
diff --git a/lucene/benchmark/src/java/org/apache/lucene/benchmark/byTask/utils/Config.java b/lucene/benchmark/src/java/org/apache/lucene/benchmark/byTask/utils/Config.java
index 8fed1010c9f..36de0c5ffca 100644
--- a/lucene/benchmark/src/java/org/apache/lucene/benchmark/byTask/utils/Config.java
+++ b/lucene/benchmark/src/java/org/apache/lucene/benchmark/byTask/utils/Config.java
@@ -27,6 +27,7 @@ import java.util.List;
 import java.util.Map;
 import java.util.Properties;
 import java.util.StringTokenizer;
+import org.apache.lucene.util.hppc.IntArrayList;
 
 /**
  * Perf run configuration properties.
@@ -338,15 +339,15 @@ public class Config {
       return new int[] {Integer.parseInt(s)};
     }
 
-    ArrayList<Integer> a = new ArrayList<>();
+    IntArrayList a = new IntArrayList();
     StringTokenizer st = new StringTokenizer(s, ":");
     while (st.hasMoreTokens()) {
       String t = st.nextToken();
-      a.add(Integer.valueOf(t));
+      a.add(Integer.parseInt(t));
     }
     int[] res = new int[a.size()];
     for (int i = 0; i < a.size(); i++) {
-      res[i] = a.get(i).intValue();
+      res[i] = a.get(i);
     }
     return res;
   }
diff --git a/lucene/core/src/java/org/apache/lucene/analysis/AutomatonToTokenStream.java b/lucene/core/src/java/org/apache/lucene/analysis/AutomatonToTokenStream.java
index 0d73a07e4eb..fa0db8de798 100644
--- a/lucene/core/src/java/org/apache/lucene/analysis/AutomatonToTokenStream.java
+++ b/lucene/core/src/java/org/apache/lucene/analysis/AutomatonToTokenStream.java
@@ -28,6 +28,8 @@ import org.apache.lucene.analysis.tokenattributes.PositionLengthAttribute;
 import org.apache.lucene.util.automaton.Automaton;
 import org.apache.lucene.util.automaton.Operations;
 import org.apache.lucene.util.automaton.Transition;
+import org.apache.lucene.util.hppc.IntArrayList;
+import org.apache.lucene.util.hppc.IntCursor;
 import org.apache.lucene.util.hppc.IntIntHashMap;
 
 /** Converts an Automaton into a TokenStream. */
@@ -49,7 +51,7 @@ public class AutomatonToTokenStream {
       throw new IllegalArgumentException("Automaton must be finite");
     }
 
-    List<List<Integer>> positionNodes = new ArrayList<>();
+    List<IntArrayList> positionNodes = new ArrayList<>();
 
     Transition[][] transitions = automaton.getSortedTransitions();
 
@@ -77,7 +79,7 @@ public class AutomatonToTokenStream {
         }
       }
       if (positionNodes.size() == currState.pos) {
-        List<Integer> posIncs = new ArrayList<>();
+        IntArrayList posIncs = new IntArrayList();
         posIncs.add(currState.id);
         positionNodes.add(posIncs);
       } else {
@@ -93,10 +95,10 @@ public class AutomatonToTokenStream {
     }
 
     List<List<EdgeToken>> edgesByLayer = new ArrayList<>();
-    for (List<Integer> layer : positionNodes) {
+    for (IntArrayList layer : positionNodes) {
       List<EdgeToken> edges = new ArrayList<>();
-      for (int state : layer) {
-        for (Transition t : transitions[state]) {
+      for (IntCursor state : layer) {
+        for (Transition t : transitions[state.value]) {
           // each edge in the token stream can only be on value, though a transition takes a range.
           for (int val = t.min; val <= t.max; val++) {
             int destLayer = idToPos.get(t.dest);
diff --git a/lucene/core/src/java/org/apache/lucene/codecs/lucene99/Lucene99ScalarQuantizedVectorsWriter.java b/lucene/core/src/java/org/apache/lucene/codecs/lucene99/Lucene99ScalarQuantizedVectorsWriter.java
index 0e2256a6a4a..b584ffff606 100644
--- a/lucene/core/src/java/org/apache/lucene/codecs/lucene99/Lucene99ScalarQuantizedVectorsWriter.java
+++ b/lucene/core/src/java/org/apache/lucene/codecs/lucene99/Lucene99ScalarQuantizedVectorsWriter.java
@@ -59,6 +59,7 @@ import org.apache.lucene.util.VectorUtil;
 import org.apache.lucene.util.hnsw.CloseableRandomVectorScorerSupplier;
 import org.apache.lucene.util.hnsw.RandomVectorScorer;
 import org.apache.lucene.util.hnsw.RandomVectorScorerSupplier;
+import org.apache.lucene.util.hppc.IntArrayList;
 import org.apache.lucene.util.quantization.QuantizedByteVectorValues;
 import org.apache.lucene.util.quantization.QuantizedVectorsReader;
 import org.apache.lucene.util.quantization.ScalarQuantizer;
@@ -540,7 +541,7 @@ public final class Lucene99ScalarQuantizedVectorsWriter extends FlatVectorsWrite
   }
 
   static ScalarQuantizer mergeQuantiles(
-      List<ScalarQuantizer> quantizationStates, List<Integer> segmentSizes, byte bits) {
+      List<ScalarQuantizer> quantizationStates, IntArrayList segmentSizes, byte bits) {
     assert quantizationStates.size() == segmentSizes.size();
     if (quantizationStates.isEmpty()) {
       return null;
@@ -633,7 +634,7 @@ public final class Lucene99ScalarQuantizedVectorsWriter extends FlatVectorsWrite
       throws IOException {
     assert fieldInfo.getVectorEncoding().equals(VectorEncoding.FLOAT32);
     List<ScalarQuantizer> quantizationStates = new ArrayList<>(mergeState.liveDocs.length);
-    List<Integer> segmentSizes = new ArrayList<>(mergeState.liveDocs.length);
+    IntArrayList segmentSizes = new IntArrayList(mergeState.liveDocs.length);
     for (int i = 0; i < mergeState.liveDocs.length; i++) {
       FloatVectorValues fvv;
       if (mergeState.knnVectorsReaders[i] != null
diff --git a/lucene/core/src/java/org/apache/lucene/index/SegmentDocValues.java b/lucene/core/src/java/org/apache/lucene/index/SegmentDocValues.java
index 451123913f5..bf4a9358439 100644
--- a/lucene/core/src/java/org/apache/lucene/index/SegmentDocValues.java
+++ b/lucene/core/src/java/org/apache/lucene/index/SegmentDocValues.java
@@ -17,13 +17,13 @@
 package org.apache.lucene.index;
 
 import java.io.IOException;
-import java.util.List;
 import org.apache.lucene.codecs.DocValuesFormat;
 import org.apache.lucene.codecs.DocValuesProducer;
 import org.apache.lucene.store.Directory;
 import org.apache.lucene.store.IOContext;
-import org.apache.lucene.util.IOUtils;
 import org.apache.lucene.util.RefCount;
+import org.apache.lucene.util.hppc.LongArrayList;
+import org.apache.lucene.util.hppc.LongCursor;
 import org.apache.lucene.util.hppc.LongObjectHashMap;
 
 /**
@@ -36,12 +36,12 @@ final class SegmentDocValues {
       new LongObjectHashMap<>();
 
   private RefCount<DocValuesProducer> newDocValuesProducer(
-      SegmentCommitInfo si, Directory dir, final Long gen, FieldInfos infos) throws IOException {
+      SegmentCommitInfo si, Directory dir, final long gen, FieldInfos infos) throws IOException {
     Directory dvDir = dir;
     String segmentSuffix = "";
-    if (gen.longValue() != -1) {
+    if (gen != -1) {
       dvDir = si.info.dir; // gen'd files are written outside CFS, so use SegInfo directory
-      segmentSuffix = Long.toString(gen.longValue(), Character.MAX_RADIX);
+      segmentSuffix = Long.toString(gen, Character.MAX_RADIX);
     }
 
     // set SegmentReadState to list only the fields that are relevant to that gen
@@ -75,13 +75,11 @@ final class SegmentDocValues {
   }
 
   /** Decrement the reference count of the given {@link DocValuesProducer} generations. */
-  synchronized void decRef(List<Long> dvProducersGens) throws IOException {
-    IOUtils.applyToAll(
-        dvProducersGens,
-        gen -> {
-          RefCount<DocValuesProducer> dvp = genDVProducers.get(gen);
-          assert dvp != null : "gen=" + gen;
-          dvp.decRef();
-        });
+  synchronized void decRef(LongArrayList dvProducersGens) throws IOException {
+    for (LongCursor gen : dvProducersGens) {
+      RefCount<DocValuesProducer> dvp = genDVProducers.get(gen.value);
+      assert dvp != null : "gen=" + gen;
+      dvp.decRef();
+    }
   }
 }
diff --git a/lucene/core/src/java/org/apache/lucene/index/SegmentDocValuesProducer.java b/lucene/core/src/java/org/apache/lucene/index/SegmentDocValuesProducer.java
index 1a1fa6f963b..f0979ddd3e7 100644
--- a/lucene/core/src/java/org/apache/lucene/index/SegmentDocValuesProducer.java
+++ b/lucene/core/src/java/org/apache/lucene/index/SegmentDocValuesProducer.java
@@ -17,15 +17,14 @@
 package org.apache.lucene.index;
 
 import java.io.IOException;
-import java.util.ArrayList;
 import java.util.Collections;
 import java.util.HashMap;
 import java.util.IdentityHashMap;
-import java.util.List;
 import java.util.Map;
 import java.util.Set;
 import org.apache.lucene.codecs.DocValuesProducer;
 import org.apache.lucene.store.Directory;
+import org.apache.lucene.util.hppc.LongArrayList;
 
 /** Encapsulates multiple producers when there are docvalues updates as one producer */
 // TODO: try to clean up close? no-op?
@@ -36,7 +35,7 @@ class SegmentDocValuesProducer extends DocValuesProducer {
   final Map<String, DocValuesProducer> dvProducersByField = new HashMap<>();
   final Set<DocValuesProducer> dvProducers =
       Collections.newSetFromMap(new IdentityHashMap<DocValuesProducer, Boolean>());
-  final List<Long> dvGens = new ArrayList<>();
+  final LongArrayList dvGens = new LongArrayList();
 
   /**
    * Creates a new producer that handles updated docvalues fields
diff --git a/lucene/core/src/java/org/apache/lucene/index/SegmentReader.java b/lucene/core/src/java/org/apache/lucene/index/SegmentReader.java
index b6bd266da5e..0bc5feeb6ba 100644
--- a/lucene/core/src/java/org/apache/lucene/index/SegmentReader.java
+++ b/lucene/core/src/java/org/apache/lucene/index/SegmentReader.java
@@ -17,7 +17,6 @@
 package org.apache.lucene.index;
 
 import java.io.IOException;
-import java.util.Collections;
 import java.util.Objects;
 import java.util.Set;
 import java.util.concurrent.CopyOnWriteArraySet;
@@ -36,6 +35,7 @@ import org.apache.lucene.store.Directory;
 import org.apache.lucene.store.IOContext;
 import org.apache.lucene.util.Bits;
 import org.apache.lucene.util.IOUtils;
+import org.apache.lucene.util.hppc.LongArrayList;
 
 /**
  * IndexReader implementation over a single segment.
@@ -226,7 +226,7 @@ public final class SegmentReader extends CodecReader {
       if (docValuesProducer instanceof SegmentDocValuesProducer) {
         segDocValues.decRef(((SegmentDocValuesProducer) docValuesProducer).dvGens);
       } else if (docValuesProducer != null) {
-        segDocValues.decRef(Collections.singletonList(-1L));
+        segDocValues.decRef(LongArrayList.from(-1L));
       }
     }
   }
diff --git a/lucene/core/src/java/org/apache/lucene/search/MultiPhraseQuery.java b/lucene/core/src/java/org/apache/lucene/search/MultiPhraseQuery.java
index 4607487b370..8ad2f5afb18 100644
--- a/lucene/core/src/java/org/apache/lucene/search/MultiPhraseQuery.java
+++ b/lucene/core/src/java/org/apache/lucene/search/MultiPhraseQuery.java
@@ -38,6 +38,7 @@ import org.apache.lucene.search.similarities.Similarity.SimScorer;
 import org.apache.lucene.util.ArrayUtil;
 import org.apache.lucene.util.BytesRef;
 import org.apache.lucene.util.PriorityQueue;
+import org.apache.lucene.util.hppc.IntArrayList;
 
 /**
  * A generalized version of {@link PhraseQuery}, with the possibility of adding more than one term
@@ -53,14 +54,14 @@ public class MultiPhraseQuery extends Query {
   public static class Builder {
     private String field; // becomes non-null on first add() then is unmodified
     private final ArrayList<Term[]> termArrays;
-    private final ArrayList<Integer> positions;
+    private final IntArrayList positions;
     private int slop;
 
     /** Default constructor. */
     public Builder() {
       this.field = null;
       this.termArrays = new ArrayList<>();
-      this.positions = new ArrayList<>();
+      this.positions = new IntArrayList();
       this.slop = 0;
     }
 
@@ -74,7 +75,7 @@ public class MultiPhraseQuery extends Query {
       int length = multiPhraseQuery.termArrays.length;
 
       this.termArrays = new ArrayList<>(length);
-      this.positions = new ArrayList<>(length);
+      this.positions = new IntArrayList(length);
 
       for (int i = 0; i < length; ++i) {
         this.termArrays.add(multiPhraseQuery.termArrays[i]);
@@ -138,15 +139,8 @@ public class MultiPhraseQuery extends Query {
 
     /** Builds a {@link MultiPhraseQuery}. */
     public MultiPhraseQuery build() {
-      int[] positionsArray = new int[this.positions.size()];
-
-      for (int i = 0; i < this.positions.size(); ++i) {
-        positionsArray[i] = this.positions.get(i);
-      }
-
       Term[][] termArraysArray = termArrays.toArray(new Term[termArrays.size()][]);
-
-      return new MultiPhraseQuery(field, termArraysArray, positionsArray, slop);
+      return new MultiPhraseQuery(field, termArraysArray, positions.toArray(), slop);
     }
   }
 
diff --git a/lucene/core/src/java/org/apache/lucene/search/PhraseQuery.java b/lucene/core/src/java/org/apache/lucene/search/PhraseQuery.java
index ea9df193413..5c771aba2cf 100644
--- a/lucene/core/src/java/org/apache/lucene/search/PhraseQuery.java
+++ b/lucene/core/src/java/org/apache/lucene/search/PhraseQuery.java
@@ -37,6 +37,7 @@ import org.apache.lucene.search.similarities.Similarity;
 import org.apache.lucene.search.similarities.Similarity.SimScorer;
 import org.apache.lucene.util.ArrayUtil;
 import org.apache.lucene.util.BytesRef;
+import org.apache.lucene.util.hppc.IntArrayList;
 
 /**
  * A Query that matches documents containing a particular sequence of terms. A PhraseQuery is built
@@ -73,13 +74,13 @@ public class PhraseQuery extends Query {
 
     private int slop;
     private final List<Term> terms;
-    private final List<Integer> positions;
+    private final IntArrayList positions;
 
     /** Sole constructor. */
     public Builder() {
       slop = 0;
       terms = new ArrayList<>();
-      positions = new ArrayList<>();
+      positions = new IntArrayList();
     }
 
     /**
@@ -133,12 +134,8 @@ public class PhraseQuery extends Query {
 
     /** Build a phrase query based on the terms that have been added. */
     public PhraseQuery build() {
-      Term[] terms = this.terms.toArray(new Term[this.terms.size()]);
-      int[] positions = new int[this.positions.size()];
-      for (int i = 0; i < positions.length; ++i) {
-        positions[i] = this.positions.get(i);
-      }
-      return new PhraseQuery(slop, terms, positions);
+      Term[] terms = this.terms.toArray(new Term[0]);
+      return new PhraseQuery(slop, terms, positions.toArray());
     }
   }
 
diff --git a/lucene/core/src/java/org/apache/lucene/util/automaton/CompiledAutomaton.java b/lucene/core/src/java/org/apache/lucene/util/automaton/CompiledAutomaton.java
index f2ec26d807a..83d937809b1 100644
--- a/lucene/core/src/java/org/apache/lucene/util/automaton/CompiledAutomaton.java
+++ b/lucene/core/src/java/org/apache/lucene/util/automaton/CompiledAutomaton.java
@@ -17,8 +17,6 @@
 package org.apache.lucene.util.automaton;
 
 import java.io.IOException;
-import java.util.ArrayList;
-import java.util.List;
 import org.apache.lucene.index.SingleTermsEnum;
 import org.apache.lucene.index.Term;
 import org.apache.lucene.index.Terms;
@@ -32,6 +30,7 @@ import org.apache.lucene.util.IntsRef;
 import org.apache.lucene.util.RamUsageEstimator;
 import org.apache.lucene.util.StringHelper;
 import org.apache.lucene.util.UnicodeUtil;
+import org.apache.lucene.util.hppc.IntArrayList;
 
 /**
  * Immutable class holding compiled details for a given Automaton. The Automaton is deterministic,
@@ -394,7 +393,7 @@ public class CompiledAutomaton implements Accountable {
       }
     }
 
-    final List<Integer> stack = new ArrayList<>();
+    final IntArrayList stack = new IntArrayList();
 
     int idx = 0;
     while (true) {
@@ -442,7 +441,7 @@ public class CompiledAutomaton implements Accountable {
                 // if (DEBUG) System.out.println("  pop ord=" + idx + " return null");
                 return null;
               } else {
-                state = stack.remove(stack.size() - 1);
+                state = stack.removeLast();
                 idx--;
                 // if (DEBUG) System.out.println("  pop ord=" + (idx+1) + " label=" + (char) label +
                 // " first trans.min=" + (char) transitions[0].min);
diff --git a/lucene/core/src/java/org/apache/lucene/util/automaton/UTF32ToUTF8.java b/lucene/core/src/java/org/apache/lucene/util/automaton/UTF32ToUTF8.java
index 700847a0a1d..a1734cbad3c 100644
--- a/lucene/core/src/java/org/apache/lucene/util/automaton/UTF32ToUTF8.java
+++ b/lucene/core/src/java/org/apache/lucene/util/automaton/UTF32ToUTF8.java
@@ -16,9 +16,8 @@
  */
 package org.apache.lucene.util.automaton;
 
-import java.util.ArrayList;
 import java.util.Arrays;
-import java.util.List;
+import org.apache.lucene.util.hppc.IntArrayList;
 
 // TODO
 //   - do we really need the .bits...?  if not we can make util in UnicodeUtil to convert 1 char
@@ -297,7 +296,7 @@ public final class UTF32ToUTF8 {
     int[] map = new int[utf32.getNumStates()];
     Arrays.fill(map, -1);
 
-    List<Integer> pending = new ArrayList<>();
+    IntArrayList pending = new IntArrayList();
     int utf32State = 0;
     pending.add(utf32State);
     utf8 = new Automaton.Builder();
@@ -311,7 +310,7 @@ public final class UTF32ToUTF8 {
     Transition scratch = new Transition();
 
     while (pending.size() != 0) {
-      utf32State = pending.remove(pending.size() - 1);
+      utf32State = pending.removeLast();
       utf8State = map[utf32State];
       assert utf8State != -1;
 
diff --git a/lucene/core/src/java/org/apache/lucene/util/bkd/BKDWriter.java b/lucene/core/src/java/org/apache/lucene/util/bkd/BKDWriter.java
index 6463a254c02..a65ba9164b8 100644
--- a/lucene/core/src/java/org/apache/lucene/util/bkd/BKDWriter.java
+++ b/lucene/core/src/java/org/apache/lucene/util/bkd/BKDWriter.java
@@ -45,6 +45,7 @@ import org.apache.lucene.util.IOUtils;
 import org.apache.lucene.util.NumericUtils;
 import org.apache.lucene.util.PriorityQueue;
 import org.apache.lucene.util.bkd.BKDUtil.ByteArrayPredicate;
+import org.apache.lucene.util.hppc.LongArrayList;
 
 // TODO
 //   - allow variable length byte[] (across docs and dims), but this is quite a bit more hairy
@@ -651,7 +652,7 @@ public class BKDWriter implements Closeable {
 
     final IndexOutput metaOut, indexOut, dataOut;
     final long dataStartFP;
-    final List<Long> leafBlockFPs = new ArrayList<>();
+    final LongArrayList leafBlockFPs = new LongArrayList();
     final List<byte[]> leafBlockStartValues = new ArrayList<>();
     final byte[] leafValues = new byte[config.maxPointsInLeafNode * config.packedBytesLength];
     final int[] leafDocs = new int[config.maxPointsInLeafNode];
diff --git a/lucene/core/src/java/org/apache/lucene/util/fst/Util.java b/lucene/core/src/java/org/apache/lucene/util/fst/Util.java
index 5800b22028d..4ee07a2feda 100644
--- a/lucene/core/src/java/org/apache/lucene/util/fst/Util.java
+++ b/lucene/core/src/java/org/apache/lucene/util/fst/Util.java
@@ -32,6 +32,8 @@ import org.apache.lucene.util.IntsRef;
 import org.apache.lucene.util.IntsRefBuilder;
 import org.apache.lucene.util.fst.FST.Arc;
 import org.apache.lucene.util.fst.FST.BytesReader;
+import org.apache.lucene.util.hppc.IntArrayList;
+import org.apache.lucene.util.hppc.IntCursor;
 
 /**
  * Static helper methods.
@@ -524,7 +526,7 @@ public final class Util {
     // System.out.println("toDot: startArc: " + startArc);
 
     // A list of states on the same level (for ranking).
-    final List<Integer> sameLevelStates = new ArrayList<>();
+    final IntArrayList sameLevelStates = new IntArrayList();
 
     // A bitset of already seen states (target offset).
     final BitSet seen = new BitSet();
@@ -692,8 +694,8 @@ public final class Util {
       // Emit state ranking information.
       if (sameRank && sameLevelStates.size() > 1) {
         out.write("  {rank=same; ");
-        for (int state : sameLevelStates) {
-          out.write(state + "; ");
+        for (IntCursor state : sameLevelStates) {
+          out.write(state.value + "; ");
         }
         out.write(" }\n");
       }
diff --git a/lucene/core/src/java/org/apache/lucene/util/graph/GraphTokenStreamFiniteStrings.java b/lucene/core/src/java/org/apache/lucene/util/graph/GraphTokenStreamFiniteStrings.java
index 321c6ff133a..f8ad4806da1 100644
--- a/lucene/core/src/java/org/apache/lucene/util/graph/GraphTokenStreamFiniteStrings.java
+++ b/lucene/core/src/java/org/apache/lucene/util/graph/GraphTokenStreamFiniteStrings.java
@@ -23,7 +23,6 @@ import java.io.IOException;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.BitSet;
-import java.util.Collections;
 import java.util.Iterator;
 import java.util.List;
 import org.apache.lucene.analysis.TokenStream;
@@ -38,6 +37,7 @@ import org.apache.lucene.util.automaton.Automaton;
 import org.apache.lucene.util.automaton.FiniteStringsIterator;
 import org.apache.lucene.util.automaton.Operations;
 import org.apache.lucene.util.automaton.Transition;
+import org.apache.lucene.util.hppc.IntArrayList;
 
 /**
  * Consumes a TokenStream and creates an {@link Automaton} where the transition labels are terms
@@ -182,10 +182,9 @@ public final class GraphTokenStreamFiniteStrings {
     int[] low = new int[det.getNumStates()];
     int[] parent = new int[det.getNumStates()];
     Arrays.fill(parent, -1);
-    List<Integer> points = new ArrayList<>();
+    IntArrayList points = new IntArrayList();
     articulationPointsRecurse(undirect.finish(), 0, 0, depth, low, parent, visited, points);
-    Collections.reverse(points);
-    return points.stream().mapToInt(p -> p).toArray();
+    return points.reverse().toArray();
   }
 
   /** Build an automaton from the provided {@link TokenStream}. */
@@ -261,7 +260,7 @@ public final class GraphTokenStreamFiniteStrings {
       int[] low,
       int[] parent,
       BitSet visited,
-      List<Integer> points) {
+      IntArrayList points) {
     visited.set(state);
     depth[state] = d;
     low[state] = d;
diff --git a/lucene/core/src/java/org/apache/lucene/util/hnsw/HnswGraph.java b/lucene/core/src/java/org/apache/lucene/util/hnsw/HnswGraph.java
index ff6695f2c7b..5907a262fe0 100644
--- a/lucene/core/src/java/org/apache/lucene/util/hnsw/HnswGraph.java
+++ b/lucene/core/src/java/org/apache/lucene/util/hnsw/HnswGraph.java
@@ -21,11 +21,12 @@ import static org.apache.lucene.search.DocIdSetIterator.NO_MORE_DOCS;
 
 import java.io.IOException;
 import java.util.Arrays;
-import java.util.Collection;
 import java.util.Iterator;
 import java.util.NoSuchElementException;
 import java.util.PrimitiveIterator;
 import org.apache.lucene.index.FloatVectorValues;
+import org.apache.lucene.util.hppc.IntArrayList;
+import org.apache.lucene.util.hppc.IntCursor;
 
 /**
  * Hierarchical Navigable Small World graph. Provides efficient approximate nearest neighbor search
@@ -220,10 +221,10 @@ public abstract class HnswGraph {
 
   /** Nodes iterator based on set representation of nodes. */
   public static class CollectionNodesIterator extends NodesIterator {
-    Iterator<Integer> nodes;
+    Iterator<IntCursor> nodes;
 
     /** Constructor for iterator based on collection representing nodes */
-    public CollectionNodesIterator(Collection<Integer> nodes) {
+    public CollectionNodesIterator(IntArrayList nodes) {
       super(nodes.size());
       this.nodes = nodes.iterator();
     }
@@ -247,7 +248,7 @@ public abstract class HnswGraph {
       if (hasNext() == false) {
         throw new NoSuchElementException();
       }
-      return nodes.next();
+      return nodes.next().value;
     }
 
     @Override
diff --git a/lucene/core/src/java/org/apache/lucene/util/hnsw/OnHeapHnswGraph.java b/lucene/core/src/java/org/apache/lucene/util/hnsw/OnHeapHnswGraph.java
index 08c67a2af6d..6368a120fcc 100644
--- a/lucene/core/src/java/org/apache/lucene/util/hnsw/OnHeapHnswGraph.java
+++ b/lucene/core/src/java/org/apache/lucene/util/hnsw/OnHeapHnswGraph.java
@@ -19,13 +19,12 @@ package org.apache.lucene.util.hnsw;
 
 import static org.apache.lucene.search.DocIdSetIterator.NO_MORE_DOCS;
 
-import java.util.ArrayList;
-import java.util.List;
 import java.util.concurrent.atomic.AtomicInteger;
 import java.util.concurrent.atomic.AtomicReference;
 import org.apache.lucene.util.Accountable;
 import org.apache.lucene.util.ArrayUtil;
 import org.apache.lucene.util.RamUsageEstimator;
+import org.apache.lucene.util.hppc.IntArrayList;
 
 /**
  * An {@link HnswGraph} where all nodes and connections are held in memory. This class is used to
@@ -44,7 +43,7 @@ public final class OnHeapHnswGraph extends HnswGraph implements Accountable {
   // essentially another 2d map which the first dimension is level and second dimension is node id,
   // this is only
   // generated on demand when there's someone calling getNodeOnLevel on a non-zero level
-  private List<Integer>[] levelToNodes;
+  private IntArrayList[] levelToNodes;
   private int
       lastFreezeSize; // remember the size we are at last time to freeze the graph and generate
   // levelToNodes
@@ -252,9 +251,9 @@ public final class OnHeapHnswGraph extends HnswGraph implements Accountable {
       return;
     }
     int maxLevels = numLevels();
-    levelToNodes = new List[maxLevels];
+    levelToNodes = new IntArrayList[maxLevels];
     for (int i = 1; i < maxLevels; i++) {
-      levelToNodes[i] = new ArrayList<>();
+      levelToNodes[i] = new IntArrayList();
     }
     int nonNullNode = 0;
     for (int node = 0; node < graph.length; node++) {
diff --git a/lucene/core/src/java/org/apache/lucene/util/hppc/IntArrayList.java b/lucene/core/src/java/org/apache/lucene/util/hppc/IntArrayList.java
new file mode 100644
index 00000000000..bc80e36463b
--- /dev/null
+++ b/lucene/core/src/java/org/apache/lucene/util/hppc/IntArrayList.java
@@ -0,0 +1,454 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.lucene.util.hppc;
+
+import static org.apache.lucene.util.hppc.HashContainers.DEFAULT_EXPECTED_ELEMENTS;
+
+import java.util.Arrays;
+import java.util.Iterator;
+import java.util.stream.IntStream;
+import org.apache.lucene.util.Accountable;
+import org.apache.lucene.util.ArrayUtil;
+import org.apache.lucene.util.RamUsageEstimator;
+
+/**
+ * An array-backed list of {@code int}.
+ *
+ * <p>Mostly forked and trimmed from com.carrotsearch.hppc.IntArrayList
+ *
+ * <p>github: https://github.com/carrotsearch/hppc release 0.9.0
+ */
+public class IntArrayList implements Iterable<IntCursor>, Cloneable, Accountable {
+  private static final long BASE_RAM_BYTES_USED =
+      RamUsageEstimator.shallowSizeOfInstance(IntArrayList.class);
+
+  /** An immutable empty buffer (array). */
+  public static final int[] EMPTY_ARRAY = new int[0];
+
+  ;
+
+  /**
+   * Internal array for storing the list. The array may be larger than the current size ({@link
+   * #size()}).
+   */
+  public int[] buffer = EMPTY_ARRAY;
+
+  /** Current number of elements stored in {@link #buffer}. */
+  public int elementsCount;
+
+  /** New instance with sane defaults. */
+  public IntArrayList() {
+    this(DEFAULT_EXPECTED_ELEMENTS);
+  }
+
+  /**
+   * New instance with sane defaults.
+   *
+   * @param expectedElements The expected number of elements guaranteed not to cause buffer
+   *     expansion (inclusive).
+   */
+  public IntArrayList(int expectedElements) {
+    buffer = new int[expectedElements];
+  }
+
+  /** Creates a new list from the elements of another list in its iteration order. */
+  public IntArrayList(IntArrayList list) {
+    this(list.size());
+    addAll(list);
+  }
+
+  public void add(int e1) {
+    ensureBufferSpace(1);
+    buffer[elementsCount++] = e1;
+  }
+
+  /** Add all elements from a range of given array to the list. */
+  public void add(int[] elements, int start, int length) {
+    assert length >= 0 : "Length must be >= 0";
+
+    ensureBufferSpace(length);
+    System.arraycopy(elements, start, buffer, elementsCount, length);
+    elementsCount += length;
+  }
+
+  /**
+   * Vararg-signature method for adding elements at the end of the list.
+   *
+   * <p><b>This method is handy, but costly if used in tight loops (anonymous array passing)</b>
+   */
+  /*  */
+  public final void add(int... elements) {
+    add(elements, 0, elements.length);
+  }
+
+  /** Adds all elements from another list. */
+  public int addAll(IntArrayList list) {
+    final int size = list.size();
+    ensureBufferSpace(size);
+
+    for (IntCursor cursor : list) {
+      add(cursor.value);
+    }
+
+    return size;
+  }
+
+  /** Adds all elements from another iterable. */
+  public int addAll(Iterable<? extends IntCursor> iterable) {
+    int size = 0;
+    for (IntCursor cursor : iterable) {
+      add(cursor.value);
+      size++;
+    }
+    return size;
+  }
+
+  public void insert(int index, int e1) {
+    assert (index >= 0 && index <= size())
+        : "Index " + index + " out of bounds [" + 0 + ", " + size() + "].";
+
+    ensureBufferSpace(1);
+    System.arraycopy(buffer, index, buffer, index + 1, elementsCount - index);
+    buffer[index] = e1;
+    elementsCount++;
+  }
+
+  public int get(int index) {
+    assert (index >= 0 && index < size())
+        : "Index " + index + " out of bounds [" + 0 + ", " + size() + ").";
+
+    return buffer[index];
+  }
+
+  public int set(int index, int e1) {
+    assert (index >= 0 && index < size())
+        : "Index " + index + " out of bounds [" + 0 + ", " + size() + ").";
+
+    final int v = buffer[index];
+    buffer[index] = e1;
+    return v;
+  }
+
+  /** Removes the element at the specified position in this container and returns it. */
+  public int removeAt(int index) {
+    assert (index >= 0 && index < size())
+        : "Index " + index + " out of bounds [" + 0 + ", " + size() + ").";
+
+    final int v = buffer[index];
+    System.arraycopy(buffer, index + 1, buffer, index, --elementsCount - index);
+    return v;
+  }
+
+  /** Removes and returns the last element of this list. */
+  public int removeLast() {
+    assert !isEmpty() : "List is empty";
+
+    return buffer[--elementsCount];
+  }
+
+  /**
+   * Removes from this list all the elements with indexes between <code>fromIndex</code>, inclusive,
+   * and <code>toIndex</code>, exclusive.
+   */
+  public void removeRange(int fromIndex, int toIndex) {
+    assert (fromIndex >= 0 && fromIndex <= size())
+        : "Index " + fromIndex + " out of bounds [" + 0 + ", " + size() + ").";
+    assert (toIndex >= 0 && toIndex <= size())
+        : "Index " + toIndex + " out of bounds [" + 0 + ", " + size() + "].";
+    assert fromIndex <= toIndex : "fromIndex must be <= toIndex: " + fromIndex + ", " + toIndex;
+
+    System.arraycopy(buffer, toIndex, buffer, fromIndex, elementsCount - toIndex);
+    final int count = toIndex - fromIndex;
+    elementsCount -= count;
+  }
+
+  /**
+   * Removes the first element that equals <code>e</code>, returning whether an element has been
+   * removed.
+   */
+  public boolean removeElement(int e) {
+    return removeFirst(e) != -1;
+  }
+
+  /**
+   * Removes the first element that equals <code>e1</code>, returning its deleted position or <code>
+   * -1</code> if the element was not found.
+   */
+  public int removeFirst(int e1) {
+    final int index = indexOf(e1);
+    if (index >= 0) removeAt(index);
+    return index;
+  }
+
+  /**
+   * Removes the last element that equals <code>e1</code>, returning its deleted position or <code>
+   * -1</code> if the element was not found.
+   */
+  public int removeLast(int e1) {
+    final int index = lastIndexOf(e1);
+    if (index >= 0) removeAt(index);
+    return index;
+  }
+
+  /**
+   * Removes all occurrences of <code>e</code> from this collection.
+   *
+   * @param e Element to be removed from this collection, if present.
+   * @return The number of removed elements as a result of this call.
+   */
+  public int removeAll(int e) {
+    int to = 0;
+    for (int from = 0; from < elementsCount; from++) {
+      if (((e) == (buffer[from]))) {
+        continue;
+      }
+      if (to != from) {
+        buffer[to] = buffer[from];
+      }
+      to++;
+    }
+    final int deleted = elementsCount - to;
+    this.elementsCount = to;
+    return deleted;
+  }
+
+  public boolean contains(int e1) {
+    return indexOf(e1) >= 0;
+  }
+
+  public int indexOf(int e1) {
+    for (int i = 0; i < elementsCount; i++) {
+      if (((e1) == (buffer[i]))) {
+        return i;
+      }
+    }
+
+    return -1;
+  }
+
+  public int lastIndexOf(int e1) {
+    for (int i = elementsCount - 1; i >= 0; i--) {
+      if (((e1) == (buffer[i]))) {
+        return i;
+      }
+    }
+
+    return -1;
+  }
+
+  public boolean isEmpty() {
+    return elementsCount == 0;
+  }
+
+  /**
+   * Ensure this container can hold at least the given number of elements without resizing its
+   * buffers.
+   *
+   * @param expectedElements The total number of elements, inclusive.
+   */
+  public void ensureCapacity(int expectedElements) {
+    final int bufferLen = (buffer == null ? 0 : buffer.length);
+    if (expectedElements > bufferLen) {
+      ensureBufferSpace(expectedElements - size());
+    }
+  }
+
+  /**
+   * Ensures the internal buffer has enough free slots to store <code>expectedAdditions</code>.
+   * Increases internal buffer size if needed.
+   */
+  protected void ensureBufferSpace(int expectedAdditions) {
+    final int bufferLen = (buffer == null ? 0 : buffer.length);
+    if (elementsCount + expectedAdditions > bufferLen) {
+      this.buffer = ArrayUtil.grow(buffer, elementsCount + expectedAdditions);
+    }
+  }
+
+  /**
+   * Truncate or expand the list to the new size. If the list is truncated, the buffer will not be
+   * reallocated (use {@link #trimToSize()} if you need a truncated buffer), but the truncated
+   * values will be reset to the default value (zero). If the list is expanded, the elements beyond
+   * the current size are initialized with JVM-defaults (zero or <code>null</code> values).
+   */
+  public void resize(int newSize) {
+    if (newSize <= buffer.length) {
+      if (newSize < elementsCount) {
+        Arrays.fill(buffer, newSize, elementsCount, 0);
+      } else {
+        Arrays.fill(buffer, elementsCount, newSize, 0);
+      }
+    } else {
+      ensureCapacity(newSize);
+    }
+    this.elementsCount = newSize;
+  }
+
+  public int size() {
+    return elementsCount;
+  }
+
+  /** Trim the internal buffer to the current size. */
+  public void trimToSize() {
+    if (size() != this.buffer.length) {
+      this.buffer = toArray();
+    }
+  }
+
+  /**
+   * Sets the number of stored elements to zero. Releases and initializes the internal storage array
+   * to default values. To clear the list without cleaning the buffer, simply set the {@link
+   * #elementsCount} field to zero.
+   */
+  public void clear() {
+    Arrays.fill(buffer, 0, elementsCount, 0);
+    this.elementsCount = 0;
+  }
+
+  /** Sets the number of stored elements to zero and releases the internal storage array. */
+  public void release() {
+    this.buffer = EMPTY_ARRAY;
+    this.elementsCount = 0;
+  }
+
+  /** The returned array is sized to match exactly the number of elements of the stack. */
+  public int[] toArray() {
+
+    return ArrayUtil.copyOfSubArray(buffer, 0, elementsCount);
+  }
+
+  /**
+   * Clone this object. The returned clone will reuse the same hash function and array resizing
+   * strategy.
+   */
+  @Override
+  public IntArrayList clone() {
+    try {
+      final IntArrayList cloned = (IntArrayList) super.clone();
+      cloned.buffer = buffer.clone();
+      return cloned;
+    } catch (CloneNotSupportedException e) {
+      throw new RuntimeException(e);
+    }
+  }
+
+  @Override
+  public int hashCode() {
+    int h = 1, max = elementsCount;
+    for (int i = 0; i < max; i++) {
+      h = 31 * h + BitMixer.mix(this.buffer[i]);
+    }
+    return h;
+  }
+
+  /**
+   * Returns <code>true</code> only if the other object is an instance of the same class and with
+   * the same elements.
+   */
+  @Override
+  public boolean equals(Object obj) {
+    return (this == obj)
+        || (obj != null && getClass() == obj.getClass() && equalElements(getClass().cast(obj)));
+  }
+
+  /** Compare index-aligned elements against another {@link IntArrayList}. */
+  protected boolean equalElements(IntArrayList other) {
+    int max = size();
+    if (other.size() != max) {
+      return false;
+    }
+
+    for (int i = 0; i < max; i++) {
+      if (!((get(i)) == (other.get(i)))) {
+        return false;
+      }
+    }
+
+    return true;
+  }
+
+  /** Convert the contents of this list to a human-friendly string. */
+  @Override
+  public String toString() {
+    return Arrays.toString(this.toArray());
+  }
+
+  @Override
+  public long ramBytesUsed() {
+    return BASE_RAM_BYTES_USED + RamUsageEstimator.sizeOf(buffer);
+  }
+
+  /** Returns a stream of all the elements contained in this list. */
+  public IntStream stream() {
+    return Arrays.stream(buffer, 0, size());
+  }
+
+  /** Sorts the elements in this list and returns this list. */
+  public IntArrayList sort() {
+    Arrays.sort(buffer, 0, elementsCount);
+    return this;
+  }
+
+  /** Reverses the elements in this list and returns this list. */
+  public IntArrayList reverse() {
+    for (int i = 0, mid = elementsCount >> 1, j = elementsCount - 1; i < mid; i++, j--) {
+      int tmp = buffer[i];
+      buffer[i] = buffer[j];
+      buffer[j] = tmp;
+    }
+    return this;
+  }
+
+  /** An iterator implementation for {@link IntArrayList#iterator}. */
+  static final class ValueIterator extends AbstractIterator<IntCursor> {
+    private final IntCursor cursor;
+
+    private final int[] buffer;
+    private final int size;
+
+    public ValueIterator(int[] buffer, int size) {
+      this.cursor = new IntCursor();
+      this.cursor.index = -1;
+      this.size = size;
+      this.buffer = buffer;
+    }
+
+    @Override
+    protected IntCursor fetch() {
+      if (cursor.index + 1 == size) return done();
+
+      cursor.value = buffer[++cursor.index];
+      return cursor;
+    }
+  }
+
+  @Override
+  public Iterator<IntCursor> iterator() {
+    return new ValueIterator(buffer, size());
+  }
+
+  /**
+   * Create a list from a variable number of arguments or an array of <code>int</code>. The elements
+   * are copied from the argument to the internal buffer.
+   */
+  /*  */
+  public static IntArrayList from(int... elements) {
+    final IntArrayList list = new IntArrayList(elements.length);
+    list.add(elements);
+    return list;
+  }
+}
diff --git a/lucene/core/src/java/org/apache/lucene/util/hppc/LongArrayList.java b/lucene/core/src/java/org/apache/lucene/util/hppc/LongArrayList.java
new file mode 100644
index 00000000000..a3503944bdd
--- /dev/null
+++ b/lucene/core/src/java/org/apache/lucene/util/hppc/LongArrayList.java
@@ -0,0 +1,454 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.lucene.util.hppc;
+
+import static org.apache.lucene.util.hppc.HashContainers.DEFAULT_EXPECTED_ELEMENTS;
+
+import java.util.Arrays;
+import java.util.Iterator;
+import java.util.stream.LongStream;
+import org.apache.lucene.util.Accountable;
+import org.apache.lucene.util.ArrayUtil;
+import org.apache.lucene.util.RamUsageEstimator;
+
+/**
+ * An array-backed list of {@code long}.
+ *
+ * <p>Mostly forked and trimmed from com.carrotsearch.hppc.LongArrayList
+ *
+ * <p>github: https://github.com/carrotsearch/hppc release 0.9.0
+ */
+public class LongArrayList implements Iterable<LongCursor>, Cloneable, Accountable {
+  private static final long BASE_RAM_BYTES_USED =
+      RamUsageEstimator.shallowSizeOfInstance(LongArrayList.class);
+
+  /** An immutable empty buffer (array). */
+  public static final long[] EMPTY_ARRAY = new long[0];
+
+  ;
+
+  /**
+   * Internal array for storing the list. The array may be larger than the current size ({@link
+   * #size()}).
+   */
+  public long[] buffer = EMPTY_ARRAY;
+
+  /** Current number of elements stored in {@link #buffer}. */
+  public int elementsCount;
+
+  /** New instance with sane defaults. */
+  public LongArrayList() {
+    this(DEFAULT_EXPECTED_ELEMENTS);
+  }
+
+  /**
+   * New instance with sane defaults.
+   *
+   * @param expectedElements The expected number of elements guaranteed not to cause buffer
+   *     expansion (inclusive).
+   */
+  public LongArrayList(int expectedElements) {
+    buffer = new long[expectedElements];
+  }
+
+  /** Creates a new list from the elements of another list in its iteration order. */
+  public LongArrayList(LongArrayList list) {
+    this(list.size());
+    addAll(list);
+  }
+
+  public void add(long e1) {
+    ensureBufferSpace(1);
+    buffer[elementsCount++] = e1;
+  }
+
+  /** Add all elements from a range of given array to the list. */
+  public void add(long[] elements, int start, int length) {
+    assert length >= 0 : "Length must be >= 0";
+
+    ensureBufferSpace(length);
+    System.arraycopy(elements, start, buffer, elementsCount, length);
+    elementsCount += length;
+  }
+
+  /**
+   * Vararg-signature method for adding elements at the end of the list.
+   *
+   * <p><b>This method is handy, but costly if used in tight loops (anonymous array passing)</b>
+   */
+  /*  */
+  public final void add(long... elements) {
+    add(elements, 0, elements.length);
+  }
+
+  /** Adds all elements from another list. */
+  public int addAll(LongArrayList list) {
+    final int size = list.size();
+    ensureBufferSpace(size);
+
+    for (LongCursor cursor : list) {
+      add(cursor.value);
+    }
+
+    return size;
+  }
+
+  /** Adds all elements from another iterable. */
+  public int addAll(Iterable<? extends LongCursor> iterable) {
+    int size = 0;
+    for (LongCursor cursor : iterable) {
+      add(cursor.value);
+      size++;
+    }
+    return size;
+  }
+
+  public void insert(int index, long e1) {
+    assert (index >= 0 && index <= size())
+        : "Index " + index + " out of bounds [" + 0 + ", " + size() + "].";
+
+    ensureBufferSpace(1);
+    System.arraycopy(buffer, index, buffer, index + 1, elementsCount - index);
+    buffer[index] = e1;
+    elementsCount++;
+  }
+
+  public long get(int index) {
+    assert (index >= 0 && index < size())
+        : "Index " + index + " out of bounds [" + 0 + ", " + size() + ").";
+
+    return buffer[index];
+  }
+
+  public long set(int index, long e1) {
+    assert (index >= 0 && index < size())
+        : "Index " + index + " out of bounds [" + 0 + ", " + size() + ").";
+
+    final long v = buffer[index];
+    buffer[index] = e1;
+    return v;
+  }
+
+  /** Removes the element at the specified position in this container and returns it. */
+  public long removeAt(int index) {
+    assert (index >= 0 && index < size())
+        : "Index " + index + " out of bounds [" + 0 + ", " + size() + ").";
+
+    final long v = buffer[index];
+    System.arraycopy(buffer, index + 1, buffer, index, --elementsCount - index);
+    return v;
+  }
+
+  /** Removes and returns the last element of this list. */
+  public long removeLast() {
+    assert !isEmpty() : "List is empty";
+
+    return buffer[--elementsCount];
+  }
+
+  /**
+   * Removes from this list all the elements with indexes between <code>fromIndex</code>, inclusive,
+   * and <code>toIndex</code>, exclusive.
+   */
+  public void removeRange(int fromIndex, int toIndex) {
+    assert (fromIndex >= 0 && fromIndex <= size())
+        : "Index " + fromIndex + " out of bounds [" + 0 + ", " + size() + ").";
+    assert (toIndex >= 0 && toIndex <= size())
+        : "Index " + toIndex + " out of bounds [" + 0 + ", " + size() + "].";
+    assert fromIndex <= toIndex : "fromIndex must be <= toIndex: " + fromIndex + ", " + toIndex;
+
+    System.arraycopy(buffer, toIndex, buffer, fromIndex, elementsCount - toIndex);
+    final int count = toIndex - fromIndex;
+    elementsCount -= count;
+  }
+
+  /**
+   * Removes the first element that equals <code>e</code>, returning whether an element has been
+   * removed.
+   */
+  public boolean removeElement(long e) {
+    return removeFirst(e) != -1;
+  }
+
+  /**
+   * Removes the first element that equals <code>e1</code>, returning its deleted position or <code>
+   * -1</code> if the element was not found.
+   */
+  public int removeFirst(long e1) {
+    final int index = indexOf(e1);
+    if (index >= 0) removeAt(index);
+    return index;
+  }
+
+  /**
+   * Removes the last element that equals <code>e1</code>, returning its deleted position or <code>
+   * -1</code> if the element was not found.
+   */
+  public int removeLast(long e1) {
+    final int index = lastIndexOf(e1);
+    if (index >= 0) removeAt(index);
+    return index;
+  }
+
+  /**
+   * Removes all occurrences of <code>e</code> from this collection.
+   *
+   * @param e Element to be removed from this collection, if present.
+   * @return The number of removed elements as a result of this call.
+   */
+  public int removeAll(long e) {
+    int to = 0;
+    for (int from = 0; from < elementsCount; from++) {
+      if (((e) == (buffer[from]))) {
+        continue;
+      }
+      if (to != from) {
+        buffer[to] = buffer[from];
+      }
+      to++;
+    }
+    final int deleted = elementsCount - to;
+    this.elementsCount = to;
+    return deleted;
+  }
+
+  public boolean contains(long e1) {
+    return indexOf(e1) >= 0;
+  }
+
+  public int indexOf(long e1) {
+    for (int i = 0; i < elementsCount; i++) {
+      if (((e1) == (buffer[i]))) {
+        return i;
+      }
+    }
+
+    return -1;
+  }
+
+  public int lastIndexOf(long e1) {
+    for (int i = elementsCount - 1; i >= 0; i--) {
+      if (((e1) == (buffer[i]))) {
+        return i;
+      }
+    }
+
+    return -1;
+  }
+
+  public boolean isEmpty() {
+    return elementsCount == 0;
+  }
+
+  /**
+   * Ensure this container can hold at least the given number of elements without resizing its
+   * buffers.
+   *
+   * @param expectedElements The total number of elements, inclusive.
+   */
+  public void ensureCapacity(int expectedElements) {
+    final int bufferLen = (buffer == null ? 0 : buffer.length);
+    if (expectedElements > bufferLen) {
+      ensureBufferSpace(expectedElements - size());
+    }
+  }
+
+  /**
+   * Ensures the internal buffer has enough free slots to store <code>expectedAdditions</code>.
+   * Increases internal buffer size if needed.
+   */
+  protected void ensureBufferSpace(int expectedAdditions) {
+    final int bufferLen = (buffer == null ? 0 : buffer.length);
+    if (elementsCount + expectedAdditions > bufferLen) {
+      this.buffer = ArrayUtil.grow(buffer, elementsCount + expectedAdditions);
+    }
+  }
+
+  /**
+   * Truncate or expand the list to the new size. If the list is truncated, the buffer will not be
+   * reallocated (use {@link #trimToSize()} if you need a truncated buffer), but the truncated
+   * values will be reset to the default value (zero). If the list is expanded, the elements beyond
+   * the current size are initialized with JVM-defaults (zero or <code>null</code> values).
+   */
+  public void resize(int newSize) {
+    if (newSize <= buffer.length) {
+      if (newSize < elementsCount) {
+        Arrays.fill(buffer, newSize, elementsCount, 0L);
+      } else {
+        Arrays.fill(buffer, elementsCount, newSize, 0L);
+      }
+    } else {
+      ensureCapacity(newSize);
+    }
+    this.elementsCount = newSize;
+  }
+
+  public int size() {
+    return elementsCount;
+  }
+
+  /** Trim the internal buffer to the current size. */
+  public void trimToSize() {
+    if (size() != this.buffer.length) {
+      this.buffer = toArray();
+    }
+  }
+
+  /**
+   * Sets the number of stored elements to zero. Releases and initializes the internal storage array
+   * to default values. To clear the list without cleaning the buffer, simply set the {@link
+   * #elementsCount} field to zero.
+   */
+  public void clear() {
+    Arrays.fill(buffer, 0, elementsCount, 0);
+    this.elementsCount = 0;
+  }
+
+  /** Sets the number of stored elements to zero and releases the internal storage array. */
+  public void release() {
+    this.buffer = EMPTY_ARRAY;
+    this.elementsCount = 0;
+  }
+
+  /** The returned array is sized to match exactly the number of elements of the stack. */
+  public long[] toArray() {
+
+    return ArrayUtil.copyOfSubArray(buffer, 0, elementsCount);
+  }
+
+  /**
+   * Clone this object. The returned clone will reuse the same hash function and array resizing
+   * strategy.
+   */
+  @Override
+  public LongArrayList clone() {
+    try {
+      final LongArrayList cloned = (LongArrayList) super.clone();
+      cloned.buffer = buffer.clone();
+      return cloned;
+    } catch (CloneNotSupportedException e) {
+      throw new RuntimeException(e);
+    }
+  }
+
+  @Override
+  public int hashCode() {
+    int h = 1, max = elementsCount;
+    for (int i = 0; i < max; i++) {
+      h = 31 * h + BitMixer.mix(this.buffer[i]);
+    }
+    return h;
+  }
+
+  /**
+   * Returns <code>true</code> only if the other object is an instance of the same class and with
+   * the same elements.
+   */
+  @Override
+  public boolean equals(Object obj) {
+    return (this == obj)
+        || (obj != null && getClass() == obj.getClass() && equalElements(getClass().cast(obj)));
+  }
+
+  /** Compare index-aligned elements against another {@link LongArrayList}. */
+  protected boolean equalElements(LongArrayList other) {
+    int max = size();
+    if (other.size() != max) {
+      return false;
+    }
+
+    for (int i = 0; i < max; i++) {
+      if (!((get(i)) == (other.get(i)))) {
+        return false;
+      }
+    }
+
+    return true;
+  }
+
+  /** Convert the contents of this list to a human-friendly string. */
+  @Override
+  public String toString() {
+    return Arrays.toString(this.toArray());
+  }
+
+  @Override
+  public long ramBytesUsed() {
+    return BASE_RAM_BYTES_USED + RamUsageEstimator.sizeOf(buffer);
+  }
+
+  /** Returns a stream of all the elements contained in this list. */
+  public LongStream stream() {
+    return Arrays.stream(buffer, 0, size());
+  }
+
+  /** Sorts the elements in this list and returns this list. */
+  public LongArrayList sort() {
+    Arrays.sort(buffer, 0, elementsCount);
+    return this;
+  }
+
+  /** Reverses the elements in this list and returns this list. */
+  public LongArrayList reverse() {
+    for (int i = 0, mid = elementsCount >> 1, j = elementsCount - 1; i < mid; i++, j--) {
+      long tmp = buffer[i];
+      buffer[i] = buffer[j];
+      buffer[j] = tmp;
+    }
+    return this;
+  }
+
+  /** An iterator implementation for {@link LongArrayList#iterator}. */
+  static final class ValueIterator extends AbstractIterator<LongCursor> {
+    private final LongCursor cursor;
+
+    private final long[] buffer;
+    private final int size;
+
+    public ValueIterator(long[] buffer, int size) {
+      this.cursor = new LongCursor();
+      this.cursor.index = -1;
+      this.size = size;
+      this.buffer = buffer;
+    }
+
+    @Override
+    protected LongCursor fetch() {
+      if (cursor.index + 1 == size) return done();
+
+      cursor.value = buffer[++cursor.index];
+      return cursor;
+    }
+  }
+
+  @Override
+  public Iterator<LongCursor> iterator() {
+    return new ValueIterator(buffer, size());
+  }
+
+  /**
+   * Create a list from a variable number of arguments or an array of <code>int</code>. The elements
+   * are copied from the argument to the internal buffer.
+   */
+  /*  */
+  public static LongArrayList from(long... elements) {
+    final LongArrayList list = new LongArrayList(elements.length);
+    list.add(elements);
+    return list;
+  }
+}
diff --git a/lucene/core/src/test/org/apache/lucene/util/hppc/TestIntArrayList.java b/lucene/core/src/test/org/apache/lucene/util/hppc/TestIntArrayList.java
new file mode 100644
index 00000000000..dd1371aea77
--- /dev/null
+++ b/lucene/core/src/test/org/apache/lucene/util/hppc/TestIntArrayList.java
@@ -0,0 +1,466 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.lucene.util.hppc;
+
+import java.util.Arrays;
+import java.util.Iterator;
+import java.util.NoSuchElementException;
+import org.apache.lucene.tests.util.LuceneTestCase;
+import org.junit.Before;
+import org.junit.Test;
+
+/**
+ * Tests for {@link IntArrayList}.
+ *
+ * <p>Mostly forked and trimmed from com.carrotsearch.hppc.IntArrayListTest
+ *
+ * <p>github: https://github.com/carrotsearch/hppc release: 0.9.0
+ */
+public class TestIntArrayList extends LuceneTestCase {
+  private final int key0 = cast(0);
+  private final int key1 = cast(1);
+  private final int key2 = cast(2);
+  private final int key3 = cast(3);
+  private final int key4 = cast(4);
+  private final int key5 = cast(5);
+  private final int key6 = cast(6);
+  private final int key7 = cast(7);
+
+  /** Convert to target type from an integer used to test stuff. */
+  private int cast(int v) {
+    return v;
+  }
+
+  /** Per-test fresh initialized instance. */
+  private IntArrayList list;
+
+  @Before
+  public void initialize() {
+    list = new IntArrayList();
+  }
+
+  @Test
+  public void testInitiallyEmpty() {
+    assertEquals(0, list.size());
+  }
+
+  @Test
+  public void testAdd() {
+    list.add(key1, key2);
+    assertListEquals(list.toArray(), 1, 2);
+  }
+
+  @Test
+  public void testAddTwoArgs() {
+    list.add(key1, key2);
+    list.add(key3, key4);
+    assertListEquals(list.toArray(), 1, 2, 3, 4);
+  }
+
+  @Test
+  public void testAddArray() {
+    list.add(asArray(0, 1, 2, 3), 1, 2);
+    assertListEquals(list.toArray(), 1, 2);
+  }
+
+  @Test
+  public void testAddVarArg() {
+    list.add(asArray(0, 1, 2, 3));
+    list.add(key4, key5, key6, key7);
+    assertListEquals(list.toArray(), 0, 1, 2, 3, 4, 5, 6, 7);
+  }
+
+  @Test
+  public void testAddAll() {
+    IntArrayList list2 = new IntArrayList();
+    list2.add(asArray(0, 1, 2));
+
+    list.addAll(list2);
+    list.addAll(list2);
+
+    assertListEquals(list.toArray(), 0, 1, 2, 0, 1, 2);
+  }
+
+  @Test
+  public void testInsert() {
+    list.insert(0, key1);
+    list.insert(0, key2);
+    list.insert(2, key3);
+    list.insert(1, key4);
+
+    assertListEquals(list.toArray(), 2, 4, 1, 3);
+  }
+
+  @Test
+  public void testSet() {
+    list.add(asArray(0, 1, 2));
+
+    assertEquals(0, list.set(0, key3));
+    assertEquals(1, list.set(1, key4));
+    assertEquals(2, list.set(2, key5));
+
+    assertListEquals(list.toArray(), 3, 4, 5);
+  }
+
+  @Test
+  public void testRemoveAt() {
+    list.add(asArray(0, 1, 2, 3, 4));
+
+    list.removeAt(0);
+    list.removeAt(2);
+    list.removeAt(1);
+
+    assertListEquals(list.toArray(), 1, 4);
+  }
+
+  @Test
+  public void testRemoveLast() {
+    list.add(asArray(0, 1, 2, 3, 4));
+
+    assertEquals(4, list.removeLast());
+    assertEquals(4, list.size());
+    assertListEquals(list.toArray(), 0, 1, 2, 3);
+    assertEquals(3, list.removeLast());
+    assertEquals(3, list.size());
+    assertListEquals(list.toArray(), 0, 1, 2);
+    assertEquals(2, list.removeLast());
+    assertEquals(1, list.removeLast());
+    assertEquals(0, list.removeLast());
+    assertTrue(list.isEmpty());
+  }
+
+  @Test
+  public void testRemoveElement() {
+    list.add(asArray(0, 1, 2, 3, 3, 4));
+
+    assertTrue(list.removeElement(3));
+    assertTrue(list.removeElement(2));
+    assertFalse(list.removeElement(5));
+
+    assertListEquals(list.toArray(), 0, 1, 3, 4);
+  }
+
+  @Test
+  public void testRemoveRange() {
+    list.add(asArray(0, 1, 2, 3, 4));
+
+    list.removeRange(0, 2);
+    assertListEquals(list.toArray(), 2, 3, 4);
+
+    list.removeRange(2, 3);
+    assertListEquals(list.toArray(), 2, 3);
+
+    list.removeRange(1, 1);
+    assertListEquals(list.toArray(), 2, 3);
+
+    list.removeRange(0, 1);
+    assertListEquals(list.toArray(), 3);
+  }
+
+  @Test
+  public void testRemoveFirstLast() {
+    list.add(asArray(0, 1, 2, 1, 0));
+
+    assertEquals(-1, list.removeFirst(key5));
+    assertEquals(-1, list.removeLast(key5));
+    assertListEquals(list.toArray(), 0, 1, 2, 1, 0);
+
+    assertEquals(1, list.removeFirst(key1));
+    assertListEquals(list.toArray(), 0, 2, 1, 0);
+    assertEquals(3, list.removeLast(key0));
+    assertListEquals(list.toArray(), 0, 2, 1);
+    assertEquals(0, list.removeLast(key0));
+    assertListEquals(list.toArray(), 2, 1);
+    assertEquals(-1, list.removeLast(key0));
+  }
+
+  @Test
+  public void testRemoveAll() {
+    list.add(asArray(0, 1, 0, 1, 0));
+
+    assertEquals(0, list.removeAll(key2));
+    assertEquals(3, list.removeAll(key0));
+    assertListEquals(list.toArray(), 1, 1);
+
+    assertEquals(2, list.removeAll(key1));
+    assertTrue(list.isEmpty());
+  }
+
+  @Test
+  public void testIndexOf() {
+    list.add(asArray(0, 1, 2, 1, 0));
+
+    assertEquals(0, list.indexOf(key0));
+    assertEquals(-1, list.indexOf(key3));
+    assertEquals(2, list.indexOf(key2));
+  }
+
+  @Test
+  public void testLastIndexOf() {
+    list.add(asArray(0, 1, 2, 1, 0));
+
+    assertEquals(4, list.lastIndexOf(key0));
+    assertEquals(-1, list.lastIndexOf(key3));
+    assertEquals(2, list.lastIndexOf(key2));
+  }
+
+  @Test
+  public void testEnsureCapacity() {
+    IntArrayList list = new IntArrayList(0);
+    assertEquals(list.size(), list.buffer.length);
+    int[] buffer1 = list.buffer;
+    list.ensureCapacity(100);
+    assertNotSame(buffer1, list.buffer);
+  }
+
+  @Test
+  public void testResizeAndCleanBuffer() {
+    list.ensureCapacity(20);
+    Arrays.fill(list.buffer, key1);
+
+    list.resize(10);
+    assertEquals(10, list.size());
+    for (int i = 0; i < list.size(); i++) {
+      assertEquals(0, list.get(i));
+    }
+
+    Arrays.fill(list.buffer, 0);
+    for (int i = 5; i < list.size(); i++) {
+      list.set(i, key1);
+    }
+    list.resize(5);
+    assertEquals(5, list.size());
+    for (int i = list.size(); i < list.buffer.length; i++) {
+      assertEquals(0, list.buffer[i]);
+    }
+  }
+
+  @Test
+  public void testTrimToSize() {
+    list.add(asArray(1, 2));
+    list.trimToSize();
+    assertEquals(2, list.buffer.length);
+  }
+
+  @Test
+  public void testRelease() {
+    list.add(asArray(1, 2));
+    list.release();
+    assertEquals(0, list.size());
+    list.add(asArray(1, 2));
+    assertEquals(2, list.size());
+  }
+
+  @Test
+  public void testIterable() {
+    list.add(asArray(0, 1, 2, 3));
+    int count = 0;
+    for (IntCursor cursor : list) {
+      count++;
+      assertEquals(list.get(cursor.index), cursor.value);
+      assertEquals(list.buffer[cursor.index], cursor.value);
+    }
+    assertEquals(count, list.size());
+
+    count = 0;
+    list.resize(0);
+    for (@SuppressWarnings("unused") IntCursor cursor : list) {
+      count++;
+    }
+    assertEquals(0, count);
+  }
+
+  @Test
+  public void testIterator() {
+    list.add(asArray(0, 1, 2, 3));
+    Iterator<IntCursor> iterator = list.iterator();
+    int count = 0;
+    while (iterator.hasNext()) {
+      iterator.hasNext();
+      iterator.hasNext();
+      iterator.hasNext();
+      iterator.next();
+      count++;
+    }
+    assertEquals(count, list.size());
+
+    list.resize(0);
+    assertFalse(list.iterator().hasNext());
+  }
+
+  @Test
+  public void testClear() {
+    list.add(asArray(1, 2, 3));
+    list.clear();
+    assertTrue(list.isEmpty());
+    assertEquals(-1, list.indexOf(cast(1)));
+  }
+
+  @Test
+  public void testFrom() {
+    list = IntArrayList.from(key1, key2, key3);
+    assertEquals(3, list.size());
+    assertListEquals(list.toArray(), 1, 2, 3);
+    assertEquals(list.size(), list.buffer.length);
+  }
+
+  @Test
+  public void testCopyList() {
+    list.add(asArray(1, 2, 3));
+    IntArrayList copy = new IntArrayList(list);
+    assertEquals(3, copy.size());
+    assertListEquals(copy.toArray(), 1, 2, 3);
+    assertEquals(copy.size(), copy.buffer.length);
+  }
+
+  @Test
+  public void testHashCodeEquals() {
+    IntArrayList l0 = IntArrayList.from();
+    assertEquals(1, l0.hashCode());
+    assertEquals(l0, IntArrayList.from());
+
+    IntArrayList l1 = IntArrayList.from(key1, key2, key3);
+    IntArrayList l2 = IntArrayList.from(key1, key2);
+    l2.add(key3);
+
+    assertEquals(l1.hashCode(), l2.hashCode());
+    assertEquals(l1, l2);
+  }
+
+  @Test
+  public void testEqualElements() {
+    IntArrayList l1 = IntArrayList.from(key1, key2, key3);
+    IntArrayList l2 = IntArrayList.from(key1, key2);
+    l2.add(key3);
+
+    assertEquals(l1.hashCode(), l2.hashCode());
+    assertTrue(l2.equalElements(l1));
+  }
+
+  @Test
+  public void testToArray() {
+    IntArrayList l1 = IntArrayList.from(key1, key2, key3);
+    l1.ensureCapacity(100);
+    int[] result = l1.toArray();
+    assertArrayEquals(new int[] {key1, key2, key3}, result);
+  }
+
+  @Test
+  public void testClone() {
+    list.add(key1, key2, key3);
+
+    IntArrayList cloned = list.clone();
+    cloned.removeAt(cloned.indexOf(key1));
+
+    assertSortedListEquals(list.toArray(), key1, key2, key3);
+    assertSortedListEquals(cloned.toArray(), key2, key3);
+  }
+
+  @Test
+  public void testToString() {
+    assertEquals(
+        "[" + key1 + ", " + key2 + ", " + key3 + "]",
+        IntArrayList.from(key1, key2, key3).toString());
+  }
+
+  @Test
+  public void testEqualsSameClass() {
+    IntArrayList l1 = IntArrayList.from(key1, key2, key3);
+    IntArrayList l2 = IntArrayList.from(key1, key2, key3);
+    IntArrayList l3 = IntArrayList.from(key1, key3, key2);
+
+    assertEquals(l1, l2);
+    assertEquals(l1.hashCode(), l2.hashCode());
+    assertNotEquals(l1, l3);
+  }
+
+  @Test
+  public void testEqualsSubClass() {
+    class Sub extends IntArrayList {}
+    ;
+
+    IntArrayList l1 = IntArrayList.from(key1, key2, key3);
+    IntArrayList l2 = new Sub();
+    IntArrayList l3 = new Sub();
+    l2.addAll(l1);
+    l3.addAll(l1);
+
+    assertEquals(l2, l3);
+    assertNotEquals(l1, l3);
+  }
+
+  @Test
+  public void testStream() {
+    assertEquals(key1, IntArrayList.from(key1, key2, key3).stream().min().orElseThrow());
+    assertEquals(key3, IntArrayList.from(key2, key1, key3).stream().max().orElseThrow());
+    assertEquals(0, IntArrayList.from(key1, key2, -key3).stream().sum());
+    expectThrows(
+        NoSuchElementException.class,
+        () -> {
+          IntArrayList.from().stream().min().orElseThrow();
+        });
+  }
+
+  @Test
+  public void testSort() {
+    list.add(key1, key3, key2);
+
+    IntArrayList list2 = new IntArrayList();
+    list2.ensureCapacity(100);
+    list2.addAll(list);
+    assertSame(list2, list2.sort());
+    assertEquals(IntArrayList.from(list.stream().sorted().toArray()), list2);
+  }
+
+  @Test
+  public void testReverse() {
+    for (int i = 0; i < 10; i++) {
+      int[] elements = new int[i];
+      for (int j = 0; j < i; j++) {
+        elements[j] = cast(j);
+      }
+      IntArrayList list = new IntArrayList();
+      list.ensureCapacity(30);
+      list.add(elements);
+      assertSame(list, list.reverse());
+      assertEquals(elements.length, list.size());
+      int reverseIndex = elements.length - 1;
+      for (IntCursor cursor : list) {
+        assertEquals(elements[reverseIndex--], cursor.value);
+      }
+    }
+  }
+
+  /** Check if the array's content is identical to a given sequence of elements. */
+  private static void assertListEquals(int[] array, int... elements) {
+    assertEquals(elements.length, array.length);
+    assertArrayEquals(elements, array);
+  }
+
+  private static int[] asArray(int... elements) {
+    return elements;
+  }
+
+  /** Check if the array's content is identical to a given sequence of elements. */
+  private static void assertSortedListEquals(int[] array, int... elements) {
+    assertEquals(elements.length, array.length);
+    Arrays.sort(array);
+    Arrays.sort(elements);
+    assertArrayEquals(elements, array);
+  }
+}
diff --git a/lucene/core/src/test/org/apache/lucene/util/hppc/TestLongArrayList.java b/lucene/core/src/test/org/apache/lucene/util/hppc/TestLongArrayList.java
new file mode 100644
index 00000000000..0f53a9f3749
--- /dev/null
+++ b/lucene/core/src/test/org/apache/lucene/util/hppc/TestLongArrayList.java
@@ -0,0 +1,466 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.lucene.util.hppc;
+
+import java.util.Arrays;
+import java.util.Iterator;
+import java.util.NoSuchElementException;
+import org.apache.lucene.tests.util.LuceneTestCase;
+import org.junit.Before;
+import org.junit.Test;
+
+/**
+ * Tests for {@link LongArrayList}.
+ *
+ * <p>Mostly forked and trimmed from com.carrotsearch.hppc.LongArrayListTest
+ *
+ * <p>github: https://github.com/carrotsearch/hppc release: 0.9.0
+ */
+public class TestLongArrayList extends LuceneTestCase {
+  private final long key0 = cast(0);
+  private final long key1 = cast(1);
+  private final long key2 = cast(2);
+  private final long key3 = cast(3);
+  private final long key4 = cast(4);
+  private final long key5 = cast(5);
+  private final long key6 = cast(6);
+  private final long key7 = cast(7);
+
+  /** Convert to target type from an integer used to test stuff. */
+  private long cast(int v) {
+    return v;
+  }
+
+  /** Per-test fresh initialized instance. */
+  private LongArrayList list;
+
+  @Before
+  public void initialize() {
+    list = new LongArrayList();
+  }
+
+  @Test
+  public void testInitiallyEmpty() {
+    assertEquals(0, list.size());
+  }
+
+  @Test
+  public void testAdd() {
+    list.add(key1, key2);
+    assertListEquals(list.toArray(), 1, 2);
+  }
+
+  @Test
+  public void testAddTwoArgs() {
+    list.add(key1, key2);
+    list.add(key3, key4);
+    assertListEquals(list.toArray(), 1, 2, 3, 4);
+  }
+
+  @Test
+  public void testAddArray() {
+    list.add(asArray(0, 1, 2, 3), 1, 2);
+    assertListEquals(list.toArray(), 1, 2);
+  }
+
+  @Test
+  public void testAddVarArg() {
+    list.add(asArray(0, 1, 2, 3));
+    list.add(key4, key5, key6, key7);
+    assertListEquals(list.toArray(), 0, 1, 2, 3, 4, 5, 6, 7);
+  }
+
+  @Test
+  public void testAddAll() {
+    LongArrayList list2 = new LongArrayList();
+    list2.add(asArray(0, 1, 2));
+
+    list.addAll(list2);
+    list.addAll(list2);
+
+    assertListEquals(list.toArray(), 0, 1, 2, 0, 1, 2);
+  }
+
+  @Test
+  public void testInsert() {
+    list.insert(0, key1);
+    list.insert(0, key2);
+    list.insert(2, key3);
+    list.insert(1, key4);
+
+    assertListEquals(list.toArray(), 2, 4, 1, 3);
+  }
+
+  @Test
+  public void testSet() {
+    list.add(asArray(0, 1, 2));
+
+    assertEquals(0, list.set(0, key3));
+    assertEquals(1, list.set(1, key4));
+    assertEquals(2, list.set(2, key5));
+
+    assertListEquals(list.toArray(), 3, 4, 5);
+  }
+
+  @Test
+  public void testRemoveAt() {
+    list.add(asArray(0, 1, 2, 3, 4));
+
+    list.removeAt(0);
+    list.removeAt(2);
+    list.removeAt(1);
+
+    assertListEquals(list.toArray(), 1, 4);
+  }
+
+  @Test
+  public void testRemoveLast() {
+    list.add(asArray(0, 1, 2, 3, 4));
+
+    assertEquals(4, list.removeLast());
+    assertEquals(4, list.size());
+    assertListEquals(list.toArray(), 0, 1, 2, 3);
+    assertEquals(3, list.removeLast());
+    assertEquals(3, list.size());
+    assertListEquals(list.toArray(), 0, 1, 2);
+    assertEquals(2, list.removeLast());
+    assertEquals(1, list.removeLast());
+    assertEquals(0, list.removeLast());
+    assertTrue(list.isEmpty());
+  }
+
+  @Test
+  public void testRemoveElement() {
+    list.add(asArray(0, 1, 2, 3, 3, 4));
+
+    assertTrue(list.removeElement(3));
+    assertTrue(list.removeElement(2));
+    assertFalse(list.removeElement(5));
+
+    assertListEquals(list.toArray(), 0, 1, 3, 4);
+  }
+
+  @Test
+  public void testRemoveRange() {
+    list.add(asArray(0, 1, 2, 3, 4));
+
+    list.removeRange(0, 2);
+    assertListEquals(list.toArray(), 2, 3, 4);
+
+    list.removeRange(2, 3);
+    assertListEquals(list.toArray(), 2, 3);
+
+    list.removeRange(1, 1);
+    assertListEquals(list.toArray(), 2, 3);
+
+    list.removeRange(0, 1);
+    assertListEquals(list.toArray(), 3);
+  }
+
+  @Test
+  public void testRemoveFirstLast() {
+    list.add(asArray(0, 1, 2, 1, 0));
+
+    assertEquals(-1, list.removeFirst(key5));
+    assertEquals(-1, list.removeLast(key5));
+    assertListEquals(list.toArray(), 0, 1, 2, 1, 0);
+
+    assertEquals(1, list.removeFirst(key1));
+    assertListEquals(list.toArray(), 0, 2, 1, 0);
+    assertEquals(3, list.removeLast(key0));
+    assertListEquals(list.toArray(), 0, 2, 1);
+    assertEquals(0, list.removeLast(key0));
+    assertListEquals(list.toArray(), 2, 1);
+    assertEquals(-1, list.removeLast(key0));
+  }
+
+  @Test
+  public void testRemoveAll() {
+    list.add(asArray(0, 1, 0, 1, 0));
+
+    assertEquals(0, list.removeAll(key2));
+    assertEquals(3, list.removeAll(key0));
+    assertListEquals(list.toArray(), 1, 1);
+
+    assertEquals(2, list.removeAll(key1));
+    assertTrue(list.isEmpty());
+  }
+
+  @Test
+  public void testIndexOf() {
+    list.add(asArray(0, 1, 2, 1, 0));
+
+    assertEquals(0, list.indexOf(key0));
+    assertEquals(-1, list.indexOf(key3));
+    assertEquals(2, list.indexOf(key2));
+  }
+
+  @Test
+  public void testLastIndexOf() {
+    list.add(asArray(0, 1, 2, 1, 0));
+
+    assertEquals(4, list.lastIndexOf(key0));
+    assertEquals(-1, list.lastIndexOf(key3));
+    assertEquals(2, list.lastIndexOf(key2));
+  }
+
+  @Test
+  public void testEnsureCapacity() {
+    LongArrayList list = new LongArrayList(0);
+    assertEquals(list.size(), list.buffer.length);
+    long[] buffer1 = list.buffer;
+    list.ensureCapacity(100);
+    assertNotSame(buffer1, list.buffer);
+  }
+
+  @Test
+  public void testResizeAndCleanBuffer() {
+    list.ensureCapacity(20);
+    Arrays.fill(list.buffer, key1);
+
+    list.resize(10);
+    assertEquals(10, list.size());
+    for (int i = 0; i < list.size(); i++) {
+      assertEquals(0, list.get(i));
+    }
+
+    Arrays.fill(list.buffer, 0);
+    for (int i = 5; i < list.size(); i++) {
+      list.set(i, key1);
+    }
+    list.resize(5);
+    assertEquals(5, list.size());
+    for (int i = list.size(); i < list.buffer.length; i++) {
+      assertEquals(0, list.buffer[i]);
+    }
+  }
+
+  @Test
+  public void testTrimToSize() {
+    list.add(asArray(1, 2));
+    list.trimToSize();
+    assertEquals(2, list.buffer.length);
+  }
+
+  @Test
+  public void testRelease() {
+    list.add(asArray(1, 2));
+    list.release();
+    assertEquals(0, list.size());
+    list.add(asArray(1, 2));
+    assertEquals(2, list.size());
+  }
+
+  @Test
+  public void testIterable() {
+    list.add(asArray(0, 1, 2, 3));
+    int count = 0;
+    for (LongCursor cursor : list) {
+      count++;
+      assertEquals(list.get(cursor.index), cursor.value);
+      assertEquals(list.buffer[cursor.index], cursor.value);
+    }
+    assertEquals(count, list.size());
+
+    count = 0;
+    list.resize(0);
+    for (@SuppressWarnings("unused") LongCursor cursor : list) {
+      count++;
+    }
+    assertEquals(0, count);
+  }
+
+  @Test
+  public void testIterator() {
+    list.add(asArray(0, 1, 2, 3));
+    Iterator<LongCursor> iterator = list.iterator();
+    int count = 0;
+    while (iterator.hasNext()) {
+      iterator.hasNext();
+      iterator.hasNext();
+      iterator.hasNext();
+      iterator.next();
+      count++;
+    }
+    assertEquals(count, list.size());
+
+    list.resize(0);
+    assertFalse(list.iterator().hasNext());
+  }
+
+  @Test
+  public void testClear() {
+    list.add(asArray(1, 2, 3));
+    list.clear();
+    assertTrue(list.isEmpty());
+    assertEquals(-1, list.indexOf(cast(1)));
+  }
+
+  @Test
+  public void testFrom() {
+    list = LongArrayList.from(key1, key2, key3);
+    assertEquals(3, list.size());
+    assertListEquals(list.toArray(), 1, 2, 3);
+    assertEquals(list.size(), list.buffer.length);
+  }
+
+  @Test
+  public void testCopyList() {
+    list.add(asArray(1, 2, 3));
+    LongArrayList copy = new LongArrayList(list);
+    assertEquals(3, copy.size());
+    assertListEquals(copy.toArray(), 1, 2, 3);
+    assertEquals(copy.size(), copy.buffer.length);
+  }
+
+  @Test
+  public void testHashCodeEquals() {
+    LongArrayList l0 = LongArrayList.from();
+    assertEquals(1, l0.hashCode());
+    assertEquals(l0, LongArrayList.from());
+
+    LongArrayList l1 = LongArrayList.from(key1, key2, key3);
+    LongArrayList l2 = LongArrayList.from(key1, key2);
+    l2.add(key3);
+
+    assertEquals(l1.hashCode(), l2.hashCode());
+    assertEquals(l1, l2);
+  }
+
+  @Test
+  public void testEqualElements() {
+    LongArrayList l1 = LongArrayList.from(key1, key2, key3);
+    LongArrayList l2 = LongArrayList.from(key1, key2);
+    l2.add(key3);
+
+    assertEquals(l1.hashCode(), l2.hashCode());
+    assertTrue(l2.equalElements(l1));
+  }
+
+  @Test
+  public void testToArray() {
+    LongArrayList l1 = LongArrayList.from(key1, key2, key3);
+    l1.ensureCapacity(100);
+    long[] result = l1.toArray();
+    assertArrayEquals(new long[] {key1, key2, key3}, result);
+  }
+
+  @Test
+  public void testClone() {
+    list.add(key1, key2, key3);
+
+    LongArrayList cloned = list.clone();
+    cloned.removeAt(cloned.indexOf(key1));
+
+    assertSortedListEquals(list.toArray(), key1, key2, key3);
+    assertSortedListEquals(cloned.toArray(), key2, key3);
+  }
+
+  @Test
+  public void testToString() {
+    assertEquals(
+        "[" + key1 + ", " + key2 + ", " + key3 + "]",
+        LongArrayList.from(key1, key2, key3).toString());
+  }
+
+  @Test
+  public void testEqualsSameClass() {
+    LongArrayList l1 = LongArrayList.from(key1, key2, key3);
+    LongArrayList l2 = LongArrayList.from(key1, key2, key3);
+    LongArrayList l3 = LongArrayList.from(key1, key3, key2);
+
+    assertEquals(l1, l2);
+    assertEquals(l1.hashCode(), l2.hashCode());
+    assertNotEquals(l1, l3);
+  }
+
+  @Test
+  public void testEqualsSubClass() {
+    class Sub extends LongArrayList {}
+    ;
+
+    LongArrayList l1 = LongArrayList.from(key1, key2, key3);
+    LongArrayList l2 = new Sub();
+    LongArrayList l3 = new Sub();
+    l2.addAll(l1);
+    l3.addAll(l1);
+
+    assertEquals(l2, l3);
+    assertNotEquals(l1, l3);
+  }
+
+  @Test
+  public void testStream() {
+    assertEquals(key1, LongArrayList.from(key1, key2, key3).stream().min().orElseThrow());
+    assertEquals(key3, LongArrayList.from(key2, key1, key3).stream().max().orElseThrow());
+    assertEquals(0, LongArrayList.from(key1, key2, -key3).stream().sum());
+    expectThrows(
+        NoSuchElementException.class,
+        () -> {
+          LongArrayList.from().stream().min().orElseThrow();
+        });
+  }
+
+  @Test
+  public void testSort() {
+    list.add(key1, key3, key2);
+
+    LongArrayList list2 = new LongArrayList();
+    list2.ensureCapacity(100);
+    list2.addAll(list);
+    assertSame(list2, list2.sort());
+    assertEquals(LongArrayList.from(list.stream().sorted().toArray()), list2);
+  }
+
+  @Test
+  public void testReverse() {
+    for (int i = 0; i < 10; i++) {
+      long[] elements = new long[i];
+      for (int j = 0; j < i; j++) {
+        elements[j] = cast(j);
+      }
+      LongArrayList list = new LongArrayList();
+      list.ensureCapacity(30);
+      list.add(elements);
+      assertSame(list, list.reverse());
+      assertEquals(elements.length, list.size());
+      int reverseIndex = elements.length - 1;
+      for (LongCursor cursor : list) {
+        assertEquals(elements[reverseIndex--], cursor.value);
+      }
+    }
+  }
+
+  /** Check if the array's content is identical to a given sequence of elements. */
+  private static void assertListEquals(long[] array, long... elements) {
+    assertEquals(elements.length, array.length);
+    assertArrayEquals(elements, array);
+  }
+
+  private static long[] asArray(long... elements) {
+    return elements;
+  }
+
+  /** Check if the array's content is identical to a given sequence of elements. */
+  private static void assertSortedListEquals(long[] array, long... elements) {
+    assertEquals(elements.length, array.length);
+    Arrays.sort(array);
+    Arrays.sort(elements);
+    assertArrayEquals(elements, array);
+  }
+}
diff --git a/lucene/facet/src/java/org/apache/lucene/facet/range/OverlappingLongRangeCounter.java b/lucene/facet/src/java/org/apache/lucene/facet/range/OverlappingLongRangeCounter.java
index a46adac7b86..9c09f29841f 100644
--- a/lucene/facet/src/java/org/apache/lucene/facet/range/OverlappingLongRangeCounter.java
+++ b/lucene/facet/src/java/org/apache/lucene/facet/range/OverlappingLongRangeCounter.java
@@ -16,8 +16,10 @@
  */
 package org.apache.lucene.facet.range;
 
+import com.carrotsearch.hppc.IntArrayList;
 import com.carrotsearch.hppc.LongArrayList;
 import com.carrotsearch.hppc.LongIntHashMap;
+import com.carrotsearch.hppc.cursors.IntCursor;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.List;
@@ -198,8 +200,8 @@ class OverlappingLongRangeCounter extends LongRangeCounter {
       }
     }
     if (node.outputs != null) {
-      for (int rangeIndex : node.outputs) {
-        increment(rangeIndex, count);
+      for (IntCursor rangeIndex : node.outputs) {
+        increment(rangeIndex.value, count);
       }
     }
 
@@ -224,8 +226,8 @@ class OverlappingLongRangeCounter extends LongRangeCounter {
       elementaryIntervalUpto++;
     }
     if (containedHit && node.outputs != null) {
-      for (int rangeIndex : node.outputs) {
-        multiValuedDocRangeHits.set(rangeIndex);
+      for (IntCursor rangeIndex : node.outputs) {
+        multiValuedDocRangeHits.set(rangeIndex.value);
       }
     }
 
@@ -318,7 +320,7 @@ class OverlappingLongRangeCounter extends LongRangeCounter {
 
     // Which range indices to output when a query goes
     // through this node:
-    List<Integer> outputs;
+    IntArrayList outputs;
 
     public LongRangeNode(
         long start,
@@ -352,7 +354,7 @@ class OverlappingLongRangeCounter extends LongRangeCounter {
         // Our range is fully included in the incoming
         // range; add to our output list:
         if (outputs == null) {
-          outputs = new ArrayList<>();
+          outputs = new IntArrayList();
         }
         outputs.add(index);
       } else if (left != null) {
diff --git a/lucene/facet/src/java/org/apache/lucene/facet/taxonomy/directory/DirectoryTaxonomyReader.java b/lucene/facet/src/java/org/apache/lucene/facet/taxonomy/directory/DirectoryTaxonomyReader.java
index c1788d0a6a2..0b0ea71a58a 100644
--- a/lucene/facet/src/java/org/apache/lucene/facet/taxonomy/directory/DirectoryTaxonomyReader.java
+++ b/lucene/facet/src/java/org/apache/lucene/facet/taxonomy/directory/DirectoryTaxonomyReader.java
@@ -16,6 +16,8 @@
  */
 package org.apache.lucene.facet.taxonomy.directory;
 
+import com.carrotsearch.hppc.IntArrayList;
+import com.carrotsearch.hppc.cursors.IntCursor;
 import java.io.IOException;
 import java.util.ArrayList;
 import java.util.Arrays;
@@ -553,7 +555,7 @@ public class DirectoryTaxonomyReader extends TaxonomyReader implements Accountab
     LeafReader leafReader;
     LeafReaderContext leafReaderContext;
     BinaryDocValues values = null;
-    List<Integer> uncachedOrdinalPositions = new ArrayList<>();
+    IntArrayList uncachedOrdinalPositions = new IntArrayList();
 
     for (int i = 0; i < ordinalsLength; i++) {
       if (bulkPath[originalPosition[i]] == null) {
@@ -590,9 +592,9 @@ public class DirectoryTaxonomyReader extends TaxonomyReader implements Accountab
 
     if (uncachedOrdinalPositions.isEmpty() == false) {
       synchronized (categoryCache) {
-        for (int i : uncachedOrdinalPositions) {
+        for (IntCursor i : uncachedOrdinalPositions) {
           // add the value to the categoryCache after computation
-          categoryCache.put(ordinals[i], bulkPath[originalPosition[i]]);
+          categoryCache.put(ordinals[i.value], bulkPath[originalPosition[i.value]]);
         }
       }
     }
diff --git a/lucene/join/src/java/org/apache/lucene/search/join/DiversifyingNearestChildrenKnnCollector.java b/lucene/join/src/java/org/apache/lucene/search/join/DiversifyingNearestChildrenKnnCollector.java
index b71adb4f014..f79958e812b 100644
--- a/lucene/join/src/java/org/apache/lucene/search/join/DiversifyingNearestChildrenKnnCollector.java
+++ b/lucene/join/src/java/org/apache/lucene/search/join/DiversifyingNearestChildrenKnnCollector.java
@@ -17,13 +17,13 @@
 
 package org.apache.lucene.search.join;
 
+import com.carrotsearch.hppc.IntIntHashMap;
 import org.apache.lucene.search.AbstractKnnCollector;
 import org.apache.lucene.search.ScoreDoc;
 import org.apache.lucene.search.TopDocs;
 import org.apache.lucene.search.TotalHits;
 import org.apache.lucene.util.ArrayUtil;
 import org.apache.lucene.util.BitSet;
-import org.apache.lucene.util.hppc.IntIntHashMap;
 
 /**
  * This collects the nearest children vectors. Diversifying the results over the provided parent
diff --git a/lucene/join/src/java/org/apache/lucene/search/join/PointInSetIncludingScoreQuery.java b/lucene/join/src/java/org/apache/lucene/search/join/PointInSetIncludingScoreQuery.java
index 55030086a44..1cdf62920de 100644
--- a/lucene/join/src/java/org/apache/lucene/search/join/PointInSetIncludingScoreQuery.java
+++ b/lucene/join/src/java/org/apache/lucene/search/join/PointInSetIncludingScoreQuery.java
@@ -17,10 +17,10 @@
 
 package org.apache.lucene.search.join;
 
+import com.carrotsearch.hppc.FloatArrayList;
+import com.carrotsearch.hppc.cursors.FloatCursor;
 import java.io.IOException;
-import java.util.ArrayList;
 import java.util.Iterator;
-import java.util.List;
 import java.util.function.BiFunction;
 import org.apache.lucene.document.DoublePoint;
 import org.apache.lucene.document.FloatPoint;
@@ -77,7 +77,7 @@ abstract class PointInSetIncludingScoreQuery extends Query implements Accountabl
   final String field;
   final int bytesPerDim;
 
-  final List<Float> aggregatedJoinScores;
+  final FloatArrayList aggregatedJoinScores;
 
   private final long ramBytesUsed; // cache
 
@@ -103,7 +103,7 @@ abstract class PointInSetIncludingScoreQuery extends Query implements Accountabl
     }
     this.bytesPerDim = bytesPerDim;
 
-    aggregatedJoinScores = new ArrayList<>();
+    aggregatedJoinScores = new FloatArrayList();
     PrefixCodedTerms.Builder builder = new PrefixCodedTerms.Builder();
     BytesRefBuilder previous = null;
     BytesRef current;
@@ -240,8 +240,8 @@ abstract class PointInSetIncludingScoreQuery extends Query implements Accountabl
     private final FixedBitSet result;
     private final float[] scores;
 
-    private TermIterator iterator;
-    private Iterator<Float> scoreIterator;
+    private final TermIterator iterator;
+    private final Iterator<FloatCursor> scoreIterator;
     private BytesRef nextQueryPoint;
     float nextScore;
     private final BytesRef scratch = new BytesRef();
@@ -256,7 +256,7 @@ abstract class PointInSetIncludingScoreQuery extends Query implements Accountabl
       this.scoreIterator = aggregatedJoinScores.iterator();
       nextQueryPoint = iterator.next();
       if (scoreIterator.hasNext()) {
-        nextScore = scoreIterator.next();
+        nextScore = scoreIterator.next().value;
       }
     }
 
@@ -286,7 +286,7 @@ abstract class PointInSetIncludingScoreQuery extends Query implements Accountabl
           // Query point is before index point, so we move to next query point
           nextQueryPoint = iterator.next();
           if (scoreIterator.hasNext()) {
-            nextScore = scoreIterator.next();
+            nextScore = scoreIterator.next().value;
           }
         } else {
           // Query point is after index point, so we don't collect and we return:
@@ -304,7 +304,7 @@ abstract class PointInSetIncludingScoreQuery extends Query implements Accountabl
           // query point is before the start of this cell
           nextQueryPoint = iterator.next();
           if (scoreIterator.hasNext()) {
-            nextScore = scoreIterator.next();
+            nextScore = scoreIterator.next().value;
           }
           continue;
         }
diff --git a/lucene/sandbox/src/java/org/apache/lucene/sandbox/document/FloatPointNearestNeighbor.java b/lucene/sandbox/src/java/org/apache/lucene/sandbox/document/FloatPointNearestNeighbor.java
index 4e4417814a4..3f0ced4f520 100644
--- a/lucene/sandbox/src/java/org/apache/lucene/sandbox/document/FloatPointNearestNeighbor.java
+++ b/lucene/sandbox/src/java/org/apache/lucene/sandbox/document/FloatPointNearestNeighbor.java
@@ -31,6 +31,7 @@ import org.apache.lucene.search.ScoreDoc;
 import org.apache.lucene.search.TopFieldDocs;
 import org.apache.lucene.search.TotalHits;
 import org.apache.lucene.util.Bits;
+import org.apache.lucene.util.hppc.IntArrayList;
 
 /**
  * KNN search on top of N dimensional indexed float points.
@@ -176,7 +177,7 @@ public class FloatPointNearestNeighbor {
   private static NearestHit[] nearest(
       List<PointValues> readers,
       List<Bits> liveDocs,
-      List<Integer> docBases,
+      IntArrayList docBases,
       final int topN,
       float[] origin)
       throws IOException {
@@ -310,7 +311,7 @@ public class FloatPointNearestNeighbor {
       throw new IllegalArgumentException("searcher must not be null");
     }
     List<PointValues> readers = new ArrayList<>();
-    List<Integer> docBases = new ArrayList<>();
+    IntArrayList docBases = new IntArrayList();
     List<Bits> liveDocs = new ArrayList<>();
     int totalHits = 0;
     for (LeafReaderContext leaf : searcher.getIndexReader().leaves()) {
diff --git a/lucene/sandbox/src/java/org/apache/lucene/sandbox/search/LatLonPointPrototypeQueries.java b/lucene/sandbox/src/java/org/apache/lucene/sandbox/search/LatLonPointPrototypeQueries.java
index 38cf543caf4..712a0fd2842 100644
--- a/lucene/sandbox/src/java/org/apache/lucene/sandbox/search/LatLonPointPrototypeQueries.java
+++ b/lucene/sandbox/src/java/org/apache/lucene/sandbox/search/LatLonPointPrototypeQueries.java
@@ -33,6 +33,7 @@ import org.apache.lucene.search.TopFieldDocs;
 import org.apache.lucene.search.TotalHits;
 import org.apache.lucene.util.Bits;
 import org.apache.lucene.util.SloppyMath;
+import org.apache.lucene.util.hppc.IntArrayList;
 
 /**
  * Holder class for prototype sandboxed queries
@@ -89,7 +90,7 @@ public class LatLonPointPrototypeQueries {
       throw new IllegalArgumentException("searcher must not be null");
     }
     List<PointValues> readers = new ArrayList<>();
-    List<Integer> docBases = new ArrayList<>();
+    IntArrayList docBases = new IntArrayList();
     List<Bits> liveDocs = new ArrayList<>();
     int totalHits = 0;
     for (LeafReaderContext leaf : searcher.getIndexReader().leaves()) {
diff --git a/lucene/sandbox/src/java/org/apache/lucene/sandbox/search/NearestNeighbor.java b/lucene/sandbox/src/java/org/apache/lucene/sandbox/search/NearestNeighbor.java
index a3fe984e651..c35a16e97a2 100644
--- a/lucene/sandbox/src/java/org/apache/lucene/sandbox/search/NearestNeighbor.java
+++ b/lucene/sandbox/src/java/org/apache/lucene/sandbox/search/NearestNeighbor.java
@@ -30,6 +30,7 @@ import org.apache.lucene.index.PointValues.PointTree;
 import org.apache.lucene.index.PointValues.Relation;
 import org.apache.lucene.util.Bits;
 import org.apache.lucene.util.SloppyMath;
+import org.apache.lucene.util.hppc.IntArrayList;
 
 /**
  * KNN search on top of 2D lat/lon indexed points.
@@ -244,7 +245,7 @@ class NearestNeighbor {
       double pointLon,
       List<PointValues> readers,
       List<Bits> liveDocs,
-      List<Integer> docBases,
+      IntArrayList docBases,
       final int n)
       throws IOException {
 
diff --git a/lucene/sandbox/src/java/org/apache/lucene/sandbox/search/QueryProfilerTree.java b/lucene/sandbox/src/java/org/apache/lucene/sandbox/search/QueryProfilerTree.java
index 98860e9f56c..1044b0aa44c 100644
--- a/lucene/sandbox/src/java/org/apache/lucene/sandbox/search/QueryProfilerTree.java
+++ b/lucene/sandbox/src/java/org/apache/lucene/sandbox/search/QueryProfilerTree.java
@@ -23,6 +23,8 @@ import java.util.Collections;
 import java.util.Deque;
 import java.util.List;
 import org.apache.lucene.search.Query;
+import org.apache.lucene.util.hppc.IntArrayList;
+import org.apache.lucene.util.hppc.IntCursor;
 
 /**
  * This class tracks the dependency tree for queries (scoring and rewriting) and generates {@link
@@ -33,13 +35,13 @@ class QueryProfilerTree {
   private final ArrayList<QueryProfilerBreakdown> breakdowns;
 
   /** Maps the Query to it's list of children. This is basically the dependency tree */
-  private final ArrayList<ArrayList<Integer>> tree;
+  private final ArrayList<IntArrayList> tree;
 
   /** A list of the original queries, keyed by index position */
   private final ArrayList<Query> queries;
 
   /** A list of top-level "roots". Each root can have its own tree of profiles */
-  private final ArrayList<Integer> roots;
+  private final IntArrayList roots;
 
   /** A temporary stack used to record where we are in the dependency tree. */
   private final Deque<Integer> stack;
@@ -56,7 +58,7 @@ class QueryProfilerTree {
     stack = new ArrayDeque<>(10);
     tree = new ArrayList<>(10);
     queries = new ArrayList<>(10);
-    roots = new ArrayList<>(10);
+    roots = new IntArrayList(10);
   }
 
   /**
@@ -113,7 +115,7 @@ class QueryProfilerTree {
   private QueryProfilerBreakdown addDependencyNode(Query query, int token) {
 
     // Add a new slot in the dependency tree
-    tree.add(new ArrayList<>(5));
+    tree.add(new IntArrayList(5));
 
     // Save our query for lookup later
     queries.add(query);
@@ -140,8 +142,8 @@ class QueryProfilerTree {
    */
   public List<QueryProfilerResult> getTree() {
     ArrayList<QueryProfilerResult> results = new ArrayList<>(roots.size());
-    for (Integer root : roots) {
-      results.add(doGetTree(root));
+    for (IntCursor root : roots) {
+      results.add(doGetTree(root.value));
     }
     return results;
   }
@@ -155,13 +157,13 @@ class QueryProfilerTree {
   private QueryProfilerResult doGetTree(int token) {
     Query query = queries.get(token);
     QueryProfilerBreakdown breakdown = breakdowns.get(token);
-    List<Integer> children = tree.get(token);
+    IntArrayList children = tree.get(token);
     List<QueryProfilerResult> childrenProfileResults = Collections.emptyList();
 
     if (children != null) {
       childrenProfileResults = new ArrayList<>(children.size());
-      for (Integer child : children) {
-        QueryProfilerResult childNode = doGetTree(child);
+      for (IntCursor child : children) {
+        QueryProfilerResult childNode = doGetTree(child.value);
         childrenProfileResults.add(childNode);
       }
     }
@@ -198,7 +200,7 @@ class QueryProfilerTree {
    */
   private void updateParent(int childToken) {
     Integer parent = stack.peekLast();
-    ArrayList<Integer> parentNode = tree.get(parent);
+    IntArrayList parentNode = tree.get(parent);
     parentNode.add(childToken);
     tree.set(parent, parentNode);
   }
diff --git a/lucene/spatial3d/build.gradle b/lucene/spatial3d/build.gradle
index 0b258337cac..184c39cd4bb 100644
--- a/lucene/spatial3d/build.gradle
+++ b/lucene/spatial3d/build.gradle
@@ -21,6 +21,9 @@ description = '3D spatial planar geometry APIs'
 
 dependencies {
   moduleApi project(':lucene:core')
+
+  moduleImplementation 'com.carrotsearch:hppc'
+
   moduleTestImplementation project(':lucene:test-framework')
 
   // We have to exclude ourselves because spatial-test-fixtures depend
diff --git a/lucene/spatial3d/src/java/module-info.java b/lucene/spatial3d/src/java/module-info.java
index 9bffb23a468..7fd67a03b55 100644
--- a/lucene/spatial3d/src/java/module-info.java
+++ b/lucene/spatial3d/src/java/module-info.java
@@ -16,8 +16,10 @@
  */
 
 /** 3D spatial planar geometry APIs */
+@SuppressWarnings({"requires-automatic"})
 module org.apache.lucene.spatial3d {
   requires org.apache.lucene.core;
+  requires com.carrotsearch.hppc;
 
   exports org.apache.lucene.spatial3d;
   exports org.apache.lucene.spatial3d.geom;
diff --git a/lucene/spatial3d/src/java/org/apache/lucene/spatial3d/geom/GeoStandardPath.java b/lucene/spatial3d/src/java/org/apache/lucene/spatial3d/geom/GeoStandardPath.java
index 41bec878465..e100b51563d 100755
--- a/lucene/spatial3d/src/java/org/apache/lucene/spatial3d/geom/GeoStandardPath.java
+++ b/lucene/spatial3d/src/java/org/apache/lucene/spatial3d/geom/GeoStandardPath.java
@@ -16,6 +16,7 @@
  */
 package org.apache.lucene.spatial3d.geom;
 
+import com.carrotsearch.hppc.IntArrayList;
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.OutputStream;
@@ -1958,18 +1959,18 @@ class GeoStandardPath extends GeoBasePath {
 
   private static class TreeBuilder {
     private final List<PathComponent> componentStack;
-    private final List<Integer> depthStack;
+    private final IntArrayList depthStack;
 
     public TreeBuilder(final int max) {
       componentStack = new ArrayList<>(max);
-      depthStack = new ArrayList<>(max);
+      depthStack = new IntArrayList(max);
     }
 
     public void addComponent(final PathComponent component) {
       componentStack.add(component);
       depthStack.add(0);
       while (depthStack.size() >= 2) {
-        if (depthStack.get(depthStack.size() - 1).equals(depthStack.get(depthStack.size() - 2))) {
+        if (depthStack.get(depthStack.size() - 1) == depthStack.get(depthStack.size() - 2)) {
           mergeTop();
         } else {
           break;
diff --git a/lucene/spatial3d/src/java/org/apache/lucene/spatial3d/geom/StandardObjects.java b/lucene/spatial3d/src/java/org/apache/lucene/spatial3d/geom/StandardObjects.java
index f40691d7146..22e949bbd97 100644
--- a/lucene/spatial3d/src/java/org/apache/lucene/spatial3d/geom/StandardObjects.java
+++ b/lucene/spatial3d/src/java/org/apache/lucene/spatial3d/geom/StandardObjects.java
@@ -17,9 +17,9 @@
 
 package org.apache.lucene.spatial3d.geom;
 
+import com.carrotsearch.hppc.IntObjectHashMap;
 import java.util.HashMap;
 import java.util.Map;
-import org.apache.lucene.util.hppc.IntObjectHashMap;
 
 /**
  * Lookup tables for classes that can be serialized using a code.