diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index ba0168a19fa..07036f8d99f 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -11,7 +11,7 @@ on:
 
 jobs:
   build:
-    name: ${{ matrix.os }} with Java 17
+    name: ${{ matrix.os }} with Java 21
     runs-on: ${{ matrix.os }}
     strategy:
       fail-fast: false
@@ -22,11 +22,11 @@ jobs:
       uses: actions/checkout@v4
       with:
         fetch-depth: 0
-    - name: Set up JDK 17
+    - name: Set up JDK 21
       uses: actions/setup-java@v4
       with:
         distribution: 'oracle'
-        java-version: '17'
+        java-version: '21'
     - name: Cache Maven packages
       uses: actions/cache@v4
       with:
diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml
index 352b229168d..d4be7ea762e 100644
--- a/.github/workflows/codeql-analysis.yml
+++ b/.github/workflows/codeql-analysis.yml
@@ -22,11 +22,11 @@ jobs:
     steps:
     - name: Checkout repository
       uses: actions/checkout@v4
-    - name: Set up JDK 17
+    - name: Set up JDK 21
       uses: actions/setup-java@v4
       with:
         distribution: 'oracle'
-        java-version: '17'
+        java-version: '21'
     - name: Initialize CodeQL
       uses: github/codeql-action/init@v3
       with:
diff --git a/.github/workflows/javadoc.yml b/.github/workflows/javadoc.yml
index 43374bbe752..52bbeebae5e 100644
--- a/.github/workflows/javadoc.yml
+++ b/.github/workflows/javadoc.yml
@@ -18,11 +18,11 @@ jobs:
     steps:
     - name: Checkout master branch
       uses: actions/checkout@v4
-    - name: Set up JDK 17
+    - name: Set up JDK 21
       uses: actions/setup-java@v4
       with:
         distribution: 'oracle'
-        java-version: '17'
+        java-version: '21'
     - name: Cache Maven packages
       uses: actions/cache@v4
       with:
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
index f3ec07b299e..dd22c931570 100644
--- a/.github/workflows/release.yml
+++ b/.github/workflows/release.yml
@@ -25,11 +25,11 @@ jobs:
     steps:
     - name: Checkout master branch
       uses: actions/checkout@v4
-    - name: Set up JDK 17
+    - name: Set up JDK 21
       uses: actions/setup-java@v4
       with:
         distribution: 'oracle'
-        java-version: '17'
+        java-version: '21'
     - name: Cache Maven packages
       uses: actions/cache@v4
       with:
diff --git a/Dockerfile b/Dockerfile
index f8343d803ce..bd1fe1d1887 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -4,7 +4,7 @@
 FROM ubuntu:jammy AS build
 
 # hadolint ignore=DL3008
-RUN apt-get update && apt-get install --no-install-recommends -y openjdk-17-jdk python3 python3-venv && \
+RUN apt-get update && apt-get install --no-install-recommends -y openjdk-21-jdk python3 python3-venv && \
     apt-get clean && \
     rm -rf /var/lib/apt/lists/*
 
@@ -41,7 +41,7 @@ RUN cp `ls -t distribution/target/*.tar.gz | head -1` /opengrok.tar.gz
 # Store the version in a file so that the tools can report it.
 RUN /mvn/mvnw help:evaluate -Dexpression=project.version -q -DforceStdout > /mvn/VERSION
 
-FROM tomcat:10.1.30-jdk17
+FROM tomcat:10.1.31-jdk21
 LABEL maintainer="https://github.com/oracle/opengrok"
 LABEL org.opencontainers.image.source="https://github.com/oracle/opengrok"
 LABEL org.opencontainers.image.description="OpenGrok code search"
diff --git a/opengrok-indexer/src/main/java/org/opengrok/indexer/analysis/Definitions.java b/opengrok-indexer/src/main/java/org/opengrok/indexer/analysis/Definitions.java
index 6c26ecb04a0..b2c724caa7c 100644
--- a/opengrok-indexer/src/main/java/org/opengrok/indexer/analysis/Definitions.java
+++ b/opengrok-indexer/src/main/java/org/opengrok/indexer/analysis/Definitions.java
@@ -65,7 +65,7 @@ public class Definitions implements Serializable {
     public static class LineTagMap implements Serializable {
 
         private static final long serialVersionUID = 1191703801007779481L;
-        @SuppressWarnings("java:S116")
+        @SuppressWarnings({"java:S116", "serial"})
         private final Map<String, Set<Tag>> sym_tags; //NOPMD
 
         protected LineTagMap() {
@@ -73,16 +73,18 @@ protected LineTagMap() {
         }
     }
     // line number -> tag map
-    @SuppressWarnings("java:S116")
+    @SuppressWarnings({"java:S116", "serial"})
     private final Map<Integer, LineTagMap> line_maps;
 
     /**
      * Map from symbol to the line numbers on which the symbol is defined.
      */
+    @SuppressWarnings("serial")
     private final Map<String, Set<Integer>> symbols;
     /**
      * List of all the tags.
      */
+    @SuppressWarnings("serial")
     private final List<Tag> tags;
 
     public Definitions() {
diff --git a/opengrok-indexer/src/main/java/org/opengrok/indexer/analysis/JFlexTokenizer.java b/opengrok-indexer/src/main/java/org/opengrok/indexer/analysis/JFlexTokenizer.java
index 0824870747f..b7ab9ece856 100644
--- a/opengrok-indexer/src/main/java/org/opengrok/indexer/analysis/JFlexTokenizer.java
+++ b/opengrok-indexer/src/main/java/org/opengrok/indexer/analysis/JFlexTokenizer.java
@@ -49,6 +49,7 @@ public class JFlexTokenizer extends Tokenizer
      * will be owned by the {@link JFlexTokenizer}.
      * @param matcher a defined instance
      */
+    @SuppressWarnings("this-escape")
     public JFlexTokenizer(ScanningSymbolMatcher matcher) {
         if (matcher == null) {
             throw new IllegalArgumentException("`matcher' is null");
@@ -83,10 +84,13 @@ public final void close() throws IOException {
         matcher.yyclose();
     }
 
+    @SuppressWarnings("this-escape")
     private final CharTermAttribute termAtt = addAttribute(
         CharTermAttribute.class);
+    @SuppressWarnings("this-escape")
     private final OffsetAttribute offsetAtt = addAttribute(
         OffsetAttribute.class);
+    @SuppressWarnings("this-escape")
     private final PositionIncrementAttribute posIncrAtt = addAttribute(
         PositionIncrementAttribute.class);
 
diff --git a/opengrok-indexer/src/main/java/org/opengrok/indexer/analysis/JFlexXref.java b/opengrok-indexer/src/main/java/org/opengrok/indexer/analysis/JFlexXref.java
index a11ff4748e9..1c4cfbec6a7 100644
--- a/opengrok-indexer/src/main/java/org/opengrok/indexer/analysis/JFlexXref.java
+++ b/opengrok-indexer/src/main/java/org/opengrok/indexer/analysis/JFlexXref.java
@@ -99,6 +99,7 @@ public class JFlexXref implements Xrefer, SymbolMatchedListener,
      * will be owned by the {@link JFlexXref}.
      * @param matcher a defined instance
      */
+    @SuppressWarnings("this-escape")
     public JFlexXref(ScanningSymbolMatcher matcher) {
         if (matcher == null) {
             throw new IllegalArgumentException("`matcher' is null");
diff --git a/opengrok-indexer/src/main/java/org/opengrok/indexer/analysis/PathTokenizer.java b/opengrok-indexer/src/main/java/org/opengrok/indexer/analysis/PathTokenizer.java
index d1cd58ea06d..7c4b7527b66 100644
--- a/opengrok-indexer/src/main/java/org/opengrok/indexer/analysis/PathTokenizer.java
+++ b/opengrok-indexer/src/main/java/org/opengrok/indexer/analysis/PathTokenizer.java
@@ -52,7 +52,9 @@ public class PathTokenizer extends Tokenizer {
     // below should be '/' since we try to convert even windows file separators
     // to unix ones
     public static final char DEFAULT_DELIMITER = '/';
+    @SuppressWarnings("this-escape")
     private final CharTermAttribute termAtt = addAttribute(CharTermAttribute.class);
+    @SuppressWarnings("this-escape")
     private final OffsetAttribute offsetAtt = addAttribute(OffsetAttribute.class);
     private int startPosition = 0;
     private final char delimiter;
diff --git a/opengrok-indexer/src/main/java/org/opengrok/indexer/analysis/plain/DefinitionsTokenStream.java b/opengrok-indexer/src/main/java/org/opengrok/indexer/analysis/plain/DefinitionsTokenStream.java
index 0b9023d511a..78f28676e98 100644
--- a/opengrok-indexer/src/main/java/org/opengrok/indexer/analysis/plain/DefinitionsTokenStream.java
+++ b/opengrok-indexer/src/main/java/org/opengrok/indexer/analysis/plain/DefinitionsTokenStream.java
@@ -48,10 +48,13 @@ public class DefinitionsTokenStream extends TokenStream {
      */
     private final List<PendingToken> events = new ArrayList<>();
 
+    @SuppressWarnings("this-escape")
     private final CharTermAttribute termAtt = addAttribute(
         CharTermAttribute.class);
+    @SuppressWarnings("this-escape")
     private final OffsetAttribute offsetAtt = addAttribute(
         OffsetAttribute.class);
+    @SuppressWarnings("this-escape")
     private final PositionIncrementAttribute posIncrAtt = addAttribute(
         PositionIncrementAttribute.class);
 
diff --git a/opengrok-indexer/src/main/java/org/opengrok/indexer/authorization/AuthorizationEntity.java b/opengrok-indexer/src/main/java/org/opengrok/indexer/authorization/AuthorizationEntity.java
index 086bb540301..27705271878 100644
--- a/opengrok-indexer/src/main/java/org/opengrok/indexer/authorization/AuthorizationEntity.java
+++ b/opengrok-indexer/src/main/java/org/opengrok/indexer/authorization/AuthorizationEntity.java
@@ -119,13 +119,16 @@ public boolean test(AuthorizationEntity t) {
      */
     protected AuthControlFlag flag;
     protected String name;
+    @SuppressWarnings("serial")
     protected Map<String, Object> setup = new TreeMap<>();
     /**
      * Hold current setup - merged with all ancestor's stacks.
      */
     protected transient Map<String, Object> currentSetup = new TreeMap<>();
 
+    @SuppressWarnings("serial")
     private Set<String> forProjects = new TreeSet<>();
+    @SuppressWarnings("serial")
     private Set<String> forGroups = new TreeSet<>();
 
     protected transient boolean working = true;
diff --git a/opengrok-indexer/src/main/java/org/opengrok/indexer/authorization/AuthorizationStack.java b/opengrok-indexer/src/main/java/org/opengrok/indexer/authorization/AuthorizationStack.java
index 3d6cc130b7c..8b4d90d1e09 100644
--- a/opengrok-indexer/src/main/java/org/opengrok/indexer/authorization/AuthorizationStack.java
+++ b/opengrok-indexer/src/main/java/org/opengrok/indexer/authorization/AuthorizationStack.java
@@ -51,6 +51,7 @@ public class AuthorizationStack extends AuthorizationEntity {
 
     private static final Logger LOGGER = LoggerFactory.getLogger(AuthorizationStack.class);
 
+    @SuppressWarnings("serial")
     private List<AuthorizationEntity> stack = new ArrayList<>();
 
     public AuthorizationStack() {
diff --git a/opengrok-indexer/src/main/java/org/opengrok/indexer/configuration/Filter.java b/opengrok-indexer/src/main/java/org/opengrok/indexer/configuration/Filter.java
index 235a6452c10..81f5c6fb3d7 100644
--- a/opengrok-indexer/src/main/java/org/opengrok/indexer/configuration/Filter.java
+++ b/opengrok-indexer/src/main/java/org/opengrok/indexer/configuration/Filter.java
@@ -37,10 +37,13 @@ public class Filter implements Serializable {
     private static final long serialVersionUID = 3L;
 
     /** The list of exact filenames. */
+    @SuppressWarnings("serial")
     private final Set<String> filenames;
     /** The list of filenames with wildcards. */
+    @SuppressWarnings("serial")
     private final List<Pattern> patterns;
     /** The list of paths. */
+    @SuppressWarnings("serial")
     private final List<String> paths;
     /**
      * The full list of all patterns. This list will be saved in the
diff --git a/opengrok-indexer/src/main/java/org/opengrok/indexer/configuration/OpenGrokThreadFactory.java b/opengrok-indexer/src/main/java/org/opengrok/indexer/configuration/OpenGrokThreadFactory.java
index c8e5f318381..83eef9175cd 100644
--- a/opengrok-indexer/src/main/java/org/opengrok/indexer/configuration/OpenGrokThreadFactory.java
+++ b/opengrok-indexer/src/main/java/org/opengrok/indexer/configuration/OpenGrokThreadFactory.java
@@ -47,7 +47,7 @@ public OpenGrokThreadFactory(String name) {
     @Override
     public Thread newThread(@NotNull Runnable runnable) {
         Thread thread = Executors.defaultThreadFactory().newThread(Objects.requireNonNull(runnable, "runnable"));
-        thread.setName(PREFIX + threadPrefix + thread.getId());
+        thread.setName(PREFIX + threadPrefix + thread.threadId());
         return thread;
     }
 }
diff --git a/opengrok-indexer/src/main/java/org/opengrok/indexer/configuration/Project.java b/opengrok-indexer/src/main/java/org/opengrok/indexer/configuration/Project.java
index aefd23e661a..337f1c8fe89 100644
--- a/opengrok-indexer/src/main/java/org/opengrok/indexer/configuration/Project.java
+++ b/opengrok-indexer/src/main/java/org/opengrok/indexer/configuration/Project.java
@@ -195,7 +195,7 @@ public String getId() {
      * @return tab size if set, 0 otherwise
      * @see #hasTabSizeSetting()
      */
-    public int getTabSize() {
+    public final int getTabSize() {
         return tabSize;
     }
 
@@ -233,7 +233,7 @@ public void setIndexed(boolean flag) {
      *
      * @param tabSize the size of tabs in this project
      */
-    public void setTabSize(int tabSize) {
+    public final void setTabSize(int tabSize) {
         this.tabSize = tabSize;
     }
 
@@ -262,7 +262,7 @@ public boolean isNavigateWindowEnabled() {
      *
      * @param navigateWindowEnabled new value of navigateWindowEnabled
      */
-    public void setNavigateWindowEnabled(boolean navigateWindowEnabled) {
+    public final void setNavigateWindowEnabled(boolean navigateWindowEnabled) {
         this.navigateWindowEnabled = navigateWindowEnabled;
     }
 
@@ -283,7 +283,7 @@ public boolean isMergeCommitsEnabled() {
     /**
      * @param flag true if project should handle renamed files, false otherwise.
      */
-    public void setHandleRenamedFiles(boolean flag) {
+    public final void setHandleRenamedFiles(boolean flag) {
         this.handleRenamedFiles = flag;
     }
 
@@ -297,7 +297,7 @@ public boolean isHistoryEnabled() {
     /**
      * @param flag true if project should have history cache, false otherwise.
      */
-    public void setHistoryEnabled(boolean flag) {
+    public final void setHistoryEnabled(boolean flag) {
         this.historyEnabled = flag;
     }
 
@@ -311,14 +311,14 @@ public boolean isAnnotationCacheEnabled() {
     /**
      * @param flag true if project should have annotation cache, false otherwise.
      */
-    public void setAnnotationCacheEnabled(boolean flag) {
+    public final void setAnnotationCacheEnabled(boolean flag) {
         this.annotationCacheEnabled = flag;
     }
 
     /**
      * @param flag true if project's repositories should deal with merge commits.
      */
-    public void setMergeCommitsEnabled(boolean flag) {
+    public final void setMergeCommitsEnabled(boolean flag) {
         this.mergeCommitsEnabled = flag;
     }
 
@@ -332,7 +332,7 @@ public boolean isHistoryBasedReindex() {
     /**
      * @param flag true if project should handle renamed files, false otherwise.
      */
-    public void setHistoryBasedReindex(boolean flag) {
+    public final void setHistoryBasedReindex(boolean flag) {
         this.historyBasedReindex = flag;
     }
 
@@ -400,7 +400,7 @@ public void addGroup(Group group) {
         }
     }
 
-    public void setBugPage(String bugPage) {
+    public final void setBugPage(String bugPage) {
         this.bugPage = bugPage;
     }
 
@@ -420,7 +420,7 @@ public String getBugPage() {
      * does not contain at least one capture group and the group does not
      * contain a single character
      */
-    public void setBugPattern(String bugPattern) throws PatternSyntaxException {
+    public final void setBugPattern(String bugPattern) throws PatternSyntaxException {
         this.bugPattern = compilePattern(bugPattern);
     }
 
@@ -440,7 +440,7 @@ public String getReviewPage() {
         }
     }
 
-    public void setReviewPage(String reviewPage) {
+    public final void setReviewPage(String reviewPage) {
         this.reviewPage = reviewPage;
     }
 
@@ -460,7 +460,7 @@ public String getReviewPattern() {
      * does not contain at least one capture group and the group does not
      * contain a single character
      */
-    public void setReviewPattern(String reviewPattern) throws PatternSyntaxException {
+    public final void setReviewPattern(String reviewPattern) throws PatternSyntaxException {
         this.reviewPattern = compilePattern(reviewPattern);
     }
 
@@ -469,7 +469,7 @@ public void setReviewPattern(String reviewPattern) throws PatternSyntaxException
      * project property has a default value.
      */
     public final void completeWithDefaults() {
-        Configuration defaultCfg = new Configuration();
+        final Configuration defaultCfg = new Configuration();
         final RuntimeEnvironment env = RuntimeEnvironment.getInstance();
 
         /*
diff --git a/opengrok-indexer/src/main/java/org/opengrok/indexer/configuration/SuggesterConfig.java b/opengrok-indexer/src/main/java/org/opengrok/indexer/configuration/SuggesterConfig.java
index 359305b3bc1..51d92535141 100644
--- a/opengrok-indexer/src/main/java/org/opengrok/indexer/configuration/SuggesterConfig.java
+++ b/opengrok-indexer/src/main/java/org/opengrok/indexer/configuration/SuggesterConfig.java
@@ -36,7 +36,7 @@
 /**
  * The suggester specific configuration.
  */
-public class SuggesterConfig {
+public final class SuggesterConfig {
 
     public static final boolean ENABLED_DEFAULT = true;
     public static final int MAX_RESULTS_DEFAULT = 10;
diff --git a/opengrok-indexer/src/main/java/org/opengrok/indexer/framework/PluginFramework.java b/opengrok-indexer/src/main/java/org/opengrok/indexer/framework/PluginFramework.java
index 988db5acbf8..769f82917c4 100644
--- a/opengrok-indexer/src/main/java/org/opengrok/indexer/framework/PluginFramework.java
+++ b/opengrok-indexer/src/main/java/org/opengrok/indexer/framework/PluginFramework.java
@@ -97,7 +97,7 @@ public synchronized File getPluginDirectory() {
      *
      * @param pluginDirectory the directory
      */
-    public synchronized void setPluginDirectory(File pluginDirectory) {
+    public final synchronized void setPluginDirectory(File pluginDirectory) {
         this.pluginDirectory = pluginDirectory;
     }
 
@@ -106,7 +106,7 @@ public synchronized void setPluginDirectory(File pluginDirectory) {
      *
      * @param directory the directory path
      */
-    public synchronized void setPluginDirectory(String directory) {
+    public final synchronized void setPluginDirectory(String directory) {
         setPluginDirectory(directory != null ? new File(directory) : null);
     }
 
diff --git a/opengrok-indexer/src/main/java/org/opengrok/indexer/history/AccuRevRepository.java b/opengrok-indexer/src/main/java/org/opengrok/indexer/history/AccuRevRepository.java
index 7f024d00e2f..0e28981c09f 100644
--- a/opengrok-indexer/src/main/java/org/opengrok/indexer/history/AccuRevRepository.java
+++ b/opengrok-indexer/src/main/java/org/opengrok/indexer/history/AccuRevRepository.java
@@ -96,6 +96,7 @@ public class AccuRevRepository extends Repository {
      */
     private static final String DEPOT_ROOT = String.format("%s.%s", File.separator, File.separator);
 
+    @SuppressWarnings("this-escape")
     public AccuRevRepository() {
         type = "AccuRev";
         datePatterns = new String[]{
diff --git a/opengrok-indexer/src/main/java/org/opengrok/indexer/history/Annotation.java b/opengrok-indexer/src/main/java/org/opengrok/indexer/history/Annotation.java
index afa7e27cfe3..d28eb77919e 100644
--- a/opengrok-indexer/src/main/java/org/opengrok/indexer/history/Annotation.java
+++ b/opengrok-indexer/src/main/java/org/opengrok/indexer/history/Annotation.java
@@ -47,7 +47,7 @@
  * Class representing file annotation, i.e., revision and author for the last
  * modification of each line in the file.
  */
-public class Annotation {
+public final class Annotation {
 
     private static final Logger LOGGER = LoggerFactory.getLogger(Annotation.class);
 
diff --git a/opengrok-indexer/src/main/java/org/opengrok/indexer/history/AnnotationData.java b/opengrok-indexer/src/main/java/org/opengrok/indexer/history/AnnotationData.java
index e8b4c90ddb5..63d367313e6 100644
--- a/opengrok-indexer/src/main/java/org/opengrok/indexer/history/AnnotationData.java
+++ b/opengrok-indexer/src/main/java/org/opengrok/indexer/history/AnnotationData.java
@@ -52,6 +52,7 @@ public AnnotationData(String filename) {
         this.filename = filename;
     }
 
+    @SuppressWarnings("serial")
     private List<AnnotationLine> annotationLines = new ArrayList<>();
     private int widestRevision;
     private int widestAuthor;
diff --git a/opengrok-indexer/src/main/java/org/opengrok/indexer/history/BitKeeperRepository.java b/opengrok-indexer/src/main/java/org/opengrok/indexer/history/BitKeeperRepository.java
index e3145f9e94b..eebb1090b1d 100644
--- a/opengrok-indexer/src/main/java/org/opengrok/indexer/history/BitKeeperRepository.java
+++ b/opengrok-indexer/src/main/java/org/opengrok/indexer/history/BitKeeperRepository.java
@@ -88,6 +88,7 @@ public class BitKeeperRepository extends Repository {
     /**
      * The version of the BitKeeper executable. This affects the correct dspec to use for tags.
      */
+    @SuppressWarnings("serial")
     private Version version = null;
 
     /**
diff --git a/opengrok-indexer/src/main/java/org/opengrok/indexer/history/CVSRepository.java b/opengrok-indexer/src/main/java/org/opengrok/indexer/history/CVSRepository.java
index f68f68d47a7..dd87ddaf3be 100644
--- a/opengrok-indexer/src/main/java/org/opengrok/indexer/history/CVSRepository.java
+++ b/opengrok-indexer/src/main/java/org/opengrok/indexer/history/CVSRepository.java
@@ -58,6 +58,7 @@ public class CVSRepository extends RCSRepository {
      */
     public static final String CMD_FALLBACK = "cvs";
 
+    @SuppressWarnings("this-escape")
     public CVSRepository() {
         /*
          * This variable is set in the ancestor to TRUE which has a side effect
diff --git a/opengrok-indexer/src/main/java/org/opengrok/indexer/history/History.java b/opengrok-indexer/src/main/java/org/opengrok/indexer/history/History.java
index dca8d97aa81..457fa509000 100644
--- a/opengrok-indexer/src/main/java/org/opengrok/indexer/history/History.java
+++ b/opengrok-indexer/src/main/java/org/opengrok/indexer/history/History.java
@@ -46,19 +46,23 @@ public class History implements Serializable {
     static final String TAGS_SEPARATOR = ", ";
 
     /** Entries in the log. The first entry is the most recent one. */
+    @SuppressWarnings("serial")
     private List<HistoryEntry> entries;
     /**
      * Track renamed files, so they can be treated in special way (for some SCMs) during cache creation.
      * These are relative to repository root.
      */
+    @SuppressWarnings("serial")
     private final Set<String> renamedFiles;
 
     /**
      * Revision of the newest change. Used in history cache.
      */
+    @SuppressWarnings("serial")
     private String latestRev;
 
     // revision to tag list. Individual tags are joined via TAGS_SEPARATOR.
+    @SuppressWarnings("serial")
     private Map<String, String> tags = new HashMap<>();
 
     public History() {
diff --git a/opengrok-indexer/src/main/java/org/opengrok/indexer/history/HistoryEntry.java b/opengrok-indexer/src/main/java/org/opengrok/indexer/history/HistoryEntry.java
index f91def5e692..f81bac58fe7 100644
--- a/opengrok-indexer/src/main/java/org/opengrok/indexer/history/HistoryEntry.java
+++ b/opengrok-indexer/src/main/java/org/opengrok/indexer/history/HistoryEntry.java
@@ -57,6 +57,7 @@ public class HistoryEntry implements Serializable {
 
     private boolean active;
     @JsonIgnore
+    @SuppressWarnings("serial")
     private SortedSet<String> files;
 
     /** Creates a new instance of HistoryEntry. */
diff --git a/opengrok-indexer/src/main/java/org/opengrok/indexer/history/RepoRepository.java b/opengrok-indexer/src/main/java/org/opengrok/indexer/history/RepoRepository.java
index 90a23f66905..d44c26125ca 100644
--- a/opengrok-indexer/src/main/java/org/opengrok/indexer/history/RepoRepository.java
+++ b/opengrok-indexer/src/main/java/org/opengrok/indexer/history/RepoRepository.java
@@ -48,6 +48,7 @@ public class RepoRepository extends Repository {
      */
     public static final String CMD_FALLBACK = "repo";
 
+    @SuppressWarnings("this-escape")
     public RepoRepository() {
         type = "repo";
         setWorking(Boolean.TRUE);
diff --git a/opengrok-indexer/src/main/java/org/opengrok/indexer/history/Repository.java b/opengrok-indexer/src/main/java/org/opengrok/indexer/history/Repository.java
index d2ddf2831f2..9742f0311ee 100644
--- a/opengrok-indexer/src/main/java/org/opengrok/indexer/history/Repository.java
+++ b/opengrok-indexer/src/main/java/org/opengrok/indexer/history/Repository.java
@@ -78,14 +78,17 @@ public abstract class Repository extends RepositoryInfo {
      */
     protected String RepoCommand;
 
+    @SuppressWarnings("serial")
     protected final List<String> ignoredFiles;
 
+    @SuppressWarnings("serial")
     protected final List<String> ignoredDirs;
 
     /**
      * List of &lt;revision, tags&gt; pairs for repositories which display tags
      * only for files changed by the tagged commit.
      */
+    @SuppressWarnings("serial")
     protected NavigableSet<TagEntry> tagList = null;
 
     abstract boolean fileHasHistory(File file);
diff --git a/opengrok-indexer/src/main/java/org/opengrok/indexer/history/SSCMRepository.java b/opengrok-indexer/src/main/java/org/opengrok/indexer/history/SSCMRepository.java
index 7ef1692b5d8..e1b6b4aacfa 100644
--- a/opengrok-indexer/src/main/java/org/opengrok/indexer/history/SSCMRepository.java
+++ b/opengrok-indexer/src/main/java/org/opengrok/indexer/history/SSCMRepository.java
@@ -70,6 +70,7 @@ public class SSCMRepository extends Repository {
     private static final String BRANCH_PROPERTY = "SCMBranch";
     private static final String REPOSITORY_PROPERTY = "SCMRepository";
 
+    @SuppressWarnings("this-escape")
     public SSCMRepository() {
         setType("SSCM");
         setRemote(true);
diff --git a/opengrok-indexer/src/main/java/org/opengrok/indexer/index/IndexAnalysisSettings.java b/opengrok-indexer/src/main/java/org/opengrok/indexer/index/IndexAnalysisSettings.java
index e180f4bae7a..4980c5be902 100644
--- a/opengrok-indexer/src/main/java/org/opengrok/indexer/index/IndexAnalysisSettings.java
+++ b/opengrok-indexer/src/main/java/org/opengrok/indexer/index/IndexAnalysisSettings.java
@@ -69,6 +69,7 @@ public final class IndexAnalysisSettings implements Serializable {
      * de-serialization circumvents normal construction.
      * @serial
      */
+    @SuppressWarnings("serial")
     private Map<String, Long> analyzersVersions = new HashMap<>();
 
     /**
diff --git a/opengrok-indexer/src/main/java/org/opengrok/indexer/index/IndexAnalysisSettings3.java b/opengrok-indexer/src/main/java/org/opengrok/indexer/index/IndexAnalysisSettings3.java
index be88b724362..3b2df2ac7f1 100644
--- a/opengrok-indexer/src/main/java/org/opengrok/indexer/index/IndexAnalysisSettings3.java
+++ b/opengrok-indexer/src/main/java/org/opengrok/indexer/index/IndexAnalysisSettings3.java
@@ -69,6 +69,7 @@ public final class IndexAnalysisSettings3 implements Serializable {
      * de-serialization circumvents normal construction.
      * @serial
      */
+    @SuppressWarnings("serial")
     private Map<String, Long> analyzersVersions = new HashMap<>();
 
     /**
@@ -78,6 +79,7 @@ public final class IndexAnalysisSettings3 implements Serializable {
      * anything but a simple {@link HashMap} here.
      * @serial
      */
+    @SuppressWarnings("serial")
     private Map<String, IndexedSymlink> indexedSymlinks = new HashMap<>();
 
     /**
diff --git a/opengrok-indexer/src/main/java/org/opengrok/indexer/index/IndexAnalysisSettingsAccessor.java b/opengrok-indexer/src/main/java/org/opengrok/indexer/index/IndexAnalysisSettingsAccessor.java
index 047263c972c..f4e73f35328 100644
--- a/opengrok-indexer/src/main/java/org/opengrok/indexer/index/IndexAnalysisSettingsAccessor.java
+++ b/opengrok-indexer/src/main/java/org/opengrok/indexer/index/IndexAnalysisSettingsAccessor.java
@@ -89,7 +89,7 @@ public IndexAnalysisSettings3[] read(IndexReader reader, int n) throws IOExcepti
         }
         TopDocs top = searcher.search(q, n);
 
-        int nres = top.totalHits.value > n ? n : (int) top.totalHits.value;
+        int nres = top.totalHits.value() > n ? n : (int) top.totalHits.value();
         IndexAnalysisSettings3[] res = new IndexAnalysisSettings3[nres];
 
         IndexAnalysisSettingsUpgrader upgrader = new IndexAnalysisSettingsUpgrader();
diff --git a/opengrok-indexer/src/main/java/org/opengrok/indexer/index/IndexCheckException.java b/opengrok-indexer/src/main/java/org/opengrok/indexer/index/IndexCheckException.java
index 8955ddf4f4c..6d46d519d26 100644
--- a/opengrok-indexer/src/main/java/org/opengrok/indexer/index/IndexCheckException.java
+++ b/opengrok-indexer/src/main/java/org/opengrok/indexer/index/IndexCheckException.java
@@ -33,6 +33,7 @@
 public class IndexCheckException extends Exception {
     private static final long serialVersionUID = 5693446916108385595L;
 
+    @SuppressWarnings("serial")
     private final Set<Path> failedPaths = new HashSet<>();
 
     public IndexCheckException(String message, Path path) {
diff --git a/opengrok-indexer/src/main/java/org/opengrok/indexer/index/IndexDatabase.java b/opengrok-indexer/src/main/java/org/opengrok/indexer/index/IndexDatabase.java
index de89aa00b81..a9ea5b3ef7c 100644
--- a/opengrok-indexer/src/main/java/org/opengrok/indexer/index/IndexDatabase.java
+++ b/opengrok-indexer/src/main/java/org/opengrok/indexer/index/IndexDatabase.java
@@ -2200,8 +2200,8 @@ public static Document getDocument(File file) throws ParseException, IOException
             TopDocs top = searcher.search(q, 1);
             stat.report(LOGGER, Level.FINEST, String.format("search via getDocument(%s) done", file),
                     "search.latency", new String[]{"category", "getdocument",
-                            "outcome", top.totalHits.value == 0 ? "empty" : "success"});
-            if (top.totalHits.value == 0) {
+                            "outcome", top.totalHits.value() == 0 ? "empty" : "success"});
+            if (top.totalHits.value() == 0) {
                 // No hits, no document...
                 return null;
             }
@@ -2294,7 +2294,7 @@ private CountingWriter newXrefWriter(String path, File transientXref, boolean co
                 new FileOutputStream(transientXref))));
     }
 
-    LockFactory pickLockFactory(RuntimeEnvironment env) {
+    private LockFactory pickLockFactory(RuntimeEnvironment env) {
         switch (env.getLuceneLocking()) {
             case ON:
             case SIMPLE:
diff --git a/opengrok-indexer/src/main/java/org/opengrok/indexer/index/IndexDocumentException.java b/opengrok-indexer/src/main/java/org/opengrok/indexer/index/IndexDocumentException.java
index a36a9dca1a6..5d3505dffc8 100644
--- a/opengrok-indexer/src/main/java/org/opengrok/indexer/index/IndexDocumentException.java
+++ b/opengrok-indexer/src/main/java/org/opengrok/indexer/index/IndexDocumentException.java
@@ -32,7 +32,9 @@
 public class IndexDocumentException extends IndexCheckException {
     private static final long serialVersionUID = -277429315137557112L;
 
+    @SuppressWarnings("serial")
     private final Map<Path, Integer> duplicatePathMap;
+    @SuppressWarnings("serial")
     private final Set<Path> missingPaths;
 
     public IndexDocumentException(String s, Path path) {
diff --git a/opengrok-indexer/src/main/java/org/opengrok/indexer/index/NumLinesLOCAccessor.java b/opengrok-indexer/src/main/java/org/opengrok/indexer/index/NumLinesLOCAccessor.java
index 91cf2583fec..4427708b628 100644
--- a/opengrok-indexer/src/main/java/org/opengrok/indexer/index/NumLinesLOCAccessor.java
+++ b/opengrok-indexer/src/main/java/org/opengrok/indexer/index/NumLinesLOCAccessor.java
@@ -65,7 +65,7 @@ class NumLinesLOCAccessor {
      */
     public boolean hasStored(IndexReader reader) throws IOException {
         DSearchResult searchResult = newDSearch(reader, 1);
-        return searchResult.hits.totalHits.value > 0;
+        return searchResult.hits.totalHits.value() > 0;
     }
 
     /**
@@ -121,8 +121,8 @@ private void storeBulk(IndexWriter writer, IndexReader reader,
 
         // Index the existing document IDs by QueryBuilder.D.
         HashMap<String, Integer> byDir = new HashMap<>();
-        int intMaximum = Integer.MAX_VALUE < searchResult.hits.totalHits.value ?
-                Integer.MAX_VALUE : (int) searchResult.hits.totalHits.value;
+        int intMaximum = Integer.MAX_VALUE < searchResult.hits.totalHits.value() ?
+                Integer.MAX_VALUE : (int) searchResult.hits.totalHits.value();
         StoredFields storedFields = searchResult.searcher.storedFields();
         for (int i = 0; i < intMaximum; ++i) {
             int docID = searchResult.hits.scoreDocs[i].doc;
@@ -148,7 +148,7 @@ private void storeIterative(IndexWriter writer, IndexReader reader,
             TopDocs hits = searcher.search(query, 1);
 
             Integer docID = null;
-            if (hits.totalHits.value > 0) {
+            if (hits.totalHits.value() > 0) {
                 docID = hits.scoreDocs[0].doc;
             }
             updateDocumentData(writer, searcher, entry, docID, isAggregatingDeltas);
diff --git a/opengrok-indexer/src/main/java/org/opengrok/indexer/search/CustomQueryParser.java b/opengrok-indexer/src/main/java/org/opengrok/indexer/search/CustomQueryParser.java
index 37765543b7f..25ab9312fbb 100644
--- a/opengrok-indexer/src/main/java/org/opengrok/indexer/search/CustomQueryParser.java
+++ b/opengrok-indexer/src/main/java/org/opengrok/indexer/search/CustomQueryParser.java
@@ -40,6 +40,7 @@ public class CustomQueryParser extends QueryParser {
      *
      * @param field default field for unqualified query terms
      */
+    @SuppressWarnings("this-escape")
     public CustomQueryParser(String field) {
         super(field, new CompatibleAnalyser());
         setDefaultOperator(AND_OPERATOR);
diff --git a/opengrok-indexer/src/main/java/org/opengrok/indexer/search/QueryBuilder.java b/opengrok-indexer/src/main/java/org/opengrok/indexer/search/QueryBuilder.java
index 494d158694e..0a00e74829a 100644
--- a/opengrok-indexer/src/main/java/org/opengrok/indexer/search/QueryBuilder.java
+++ b/opengrok-indexer/src/main/java/org/opengrok/indexer/search/QueryBuilder.java
@@ -502,7 +502,7 @@ protected Query buildQuery(String field, String queryText)
      */
     private boolean hasClause(BooleanQuery query, Occur occur) {
         for (BooleanClause clause : query) {
-            if (clause.getOccur().equals(occur)) {
+            if (clause.occur().equals(occur)) {
                 return true;
             }
         }
diff --git a/opengrok-indexer/src/main/java/org/opengrok/indexer/search/SearchEngine.java b/opengrok-indexer/src/main/java/org/opengrok/indexer/search/SearchEngine.java
index e12ad247142..3aa27c9d2c4 100644
--- a/opengrok-indexer/src/main/java/org/opengrok/indexer/search/SearchEngine.java
+++ b/opengrok-indexer/src/main/java/org/opengrok/indexer/search/SearchEngine.java
@@ -48,7 +48,7 @@
 import org.apache.lucene.search.IndexSearcher;
 import org.apache.lucene.search.Query;
 import org.apache.lucene.search.ScoreDoc;
-import org.apache.lucene.search.TopScoreDocCollector;
+import org.apache.lucene.search.TopScoreDocCollectorManager;
 import org.apache.lucene.util.Version;
 import org.opengrok.indexer.analysis.AbstractAnalyzer;
 import org.opengrok.indexer.analysis.CompatibleAnalyser;
@@ -132,7 +132,7 @@ public class SearchEngine {
     int cachePages = RuntimeEnvironment.getInstance().getCachePages();
     int totalHits = 0;
     private ScoreDoc[] hits;
-    private TopScoreDocCollector collector;
+    private TopScoreDocCollectorManager collectorManager;
     private IndexSearcher searcher;
     boolean allCollected;
     private final ArrayList<SuperIndexSearcher> searcherList = new ArrayList<>();
@@ -205,18 +205,17 @@ private void searchMultiDatabase(List<Project> projectList, boolean paging) thro
     }
 
     private void searchIndex(IndexSearcher searcher, boolean paging) throws IOException {
-        collector = TopScoreDocCollector.create(hitsPerPage * cachePages, Short.MAX_VALUE);
+        collectorManager = new TopScoreDocCollectorManager(hitsPerPage * cachePages, Short.MAX_VALUE);
         Statistics stat = new Statistics();
-        searcher.search(query, collector);
-        totalHits = collector.getTotalHits();
+        hits = searcher.search(query, collectorManager).scoreDocs;
+        totalHits = searcher.count(query);
         stat.report(LOGGER, Level.FINEST, "search via SearchEngine done",
                 "search.latency", new String[]{"category", "engine",
                         "outcome", totalHits > 0 ? "success" : "empty"});
         if (!paging && totalHits > 0) {
-            collector = TopScoreDocCollector.create(totalHits, Short.MAX_VALUE);
-            searcher.search(query, collector);
+            collectorManager = new TopScoreDocCollectorManager(totalHits, Short.MAX_VALUE);
+            hits = searcher.search(query, collectorManager).scoreDocs;
         }
-        hits = collector.topDocs().scoreDocs;
         StoredFields storedFields = searcher.storedFields();
         for (ScoreDoc hit : hits) {
             int docId = hit.doc;
@@ -412,14 +411,13 @@ public void results(int start, int end, List<Hit> ret) {
         // TODO check if below fits for if end=old hits.length, or it should include it
         if (end > hits.length && !allCollected) {
             //do the requery, we want more than 5 pages
-            collector = TopScoreDocCollector.create(totalHits, Short.MAX_VALUE);
+            collectorManager = new TopScoreDocCollectorManager(totalHits, Short.MAX_VALUE);
             try {
-                searcher.search(query, collector);
+                hits = searcher.search(query, collectorManager).scoreDocs;
             } catch (Exception e) { // this exception should never be hit, since search() will hit this before
                 LOGGER.log(
                         Level.WARNING, SEARCH_EXCEPTION_MSG, e);
             }
-            hits = collector.topDocs().scoreDocs;
             StoredFields storedFields = null;
             try {
                 storedFields = searcher.storedFields();
diff --git a/opengrok-indexer/src/main/java/org/opengrok/indexer/search/Summarizer.java b/opengrok-indexer/src/main/java/org/opengrok/indexer/search/Summarizer.java
index 7abf9d496e9..2859c2ad573 100644
--- a/opengrok-indexer/src/main/java/org/opengrok/indexer/search/Summarizer.java
+++ b/opengrok-indexer/src/main/java/org/opengrok/indexer/search/Summarizer.java
@@ -330,7 +330,7 @@ private void getTerms(Query query) {
     private void getBooleans(BooleanQuery query) {
         for (BooleanClause clause : query) {
             if (!clause.isProhibited()) {
-                getTerms(clause.getQuery());
+                getTerms(clause.query());
             }
         }
     }
diff --git a/opengrok-indexer/src/main/java/org/opengrok/indexer/search/context/OGKUnifiedHighlighter.java b/opengrok-indexer/src/main/java/org/opengrok/indexer/search/context/OGKUnifiedHighlighter.java
index 92a7f897fcc..3ee37779527 100644
--- a/opengrok-indexer/src/main/java/org/opengrok/indexer/search/context/OGKUnifiedHighlighter.java
+++ b/opengrok-indexer/src/main/java/org/opengrok/indexer/search/context/OGKUnifiedHighlighter.java
@@ -247,7 +247,7 @@ protected List<CharSequence[]> loadFieldValues(String[] fields,
     protected OffsetSource getOptimizedOffsetSource(UHComponents components) {
 
         OffsetSource res = super.getOptimizedOffsetSource(components);
-        String field = components.getField();
+        String field = components.field();
         if (res == OffsetSource.ANALYSIS) {
             /*
                   Testing showed that UnifiedHighlighter falls back to
diff --git a/opengrok-indexer/src/main/java/org/opengrok/indexer/search/context/PrefixMatcher.java b/opengrok-indexer/src/main/java/org/opengrok/indexer/search/context/PrefixMatcher.java
index 7d321994390..e05c0b344c5 100644
--- a/opengrok-indexer/src/main/java/org/opengrok/indexer/search/context/PrefixMatcher.java
+++ b/opengrok-indexer/src/main/java/org/opengrok/indexer/search/context/PrefixMatcher.java
@@ -28,6 +28,7 @@
 public class PrefixMatcher extends LineMatcher {
     private final String prefix;
 
+    @SuppressWarnings("this-escape")
     public PrefixMatcher(String prefix, boolean caseInsensitive) {
         super(caseInsensitive);
         this.prefix = normalizeString(prefix);
diff --git a/opengrok-indexer/src/main/java/org/opengrok/indexer/search/context/QueryMatchers.java b/opengrok-indexer/src/main/java/org/opengrok/indexer/search/context/QueryMatchers.java
index b8c4f15a727..b8d8ca8bc48 100644
--- a/opengrok-indexer/src/main/java/org/opengrok/indexer/search/context/QueryMatchers.java
+++ b/opengrok-indexer/src/main/java/org/opengrok/indexer/search/context/QueryMatchers.java
@@ -105,7 +105,7 @@ private void getRegexp(RegexpQuery query) {
     private void getBooleans(BooleanQuery query) {
         for (BooleanClause clause : query) {
             if (!clause.isProhibited()) {
-                getTerms(clause.getQuery());
+                getTerms(clause.query());
             }
         }
     }
diff --git a/opengrok-indexer/src/main/java/org/opengrok/indexer/search/context/TokenSetMatcher.java b/opengrok-indexer/src/main/java/org/opengrok/indexer/search/context/TokenSetMatcher.java
index 01c261dedad..99ed813ff4d 100644
--- a/opengrok-indexer/src/main/java/org/opengrok/indexer/search/context/TokenSetMatcher.java
+++ b/opengrok-indexer/src/main/java/org/opengrok/indexer/search/context/TokenSetMatcher.java
@@ -28,6 +28,7 @@
 public class TokenSetMatcher extends LineMatcher {
     private final Set<String> tokenSet;
 
+    @SuppressWarnings("this-escape")
     public TokenSetMatcher(Set<String> tokenSet, boolean caseInsensitive) {
         super(caseInsensitive);
 
diff --git a/opengrok-indexer/src/main/java/org/opengrok/indexer/search/context/WildCardMatcher.java b/opengrok-indexer/src/main/java/org/opengrok/indexer/search/context/WildCardMatcher.java
index 8ce5f2d4e98..eafc8c8760f 100644
--- a/opengrok-indexer/src/main/java/org/opengrok/indexer/search/context/WildCardMatcher.java
+++ b/opengrok-indexer/src/main/java/org/opengrok/indexer/search/context/WildCardMatcher.java
@@ -29,6 +29,7 @@ public class WildCardMatcher extends LineMatcher {
 
     final String pattern;
 
+    @SuppressWarnings("this-escape")
     public WildCardMatcher(String pattern, boolean caseInsensitive) {
         super(caseInsensitive);
         this.pattern = normalizeString(pattern);
diff --git a/opengrok-indexer/src/main/java/org/opengrok/indexer/util/Executor.java b/opengrok-indexer/src/main/java/org/opengrok/indexer/util/Executor.java
index 773c1ecc18d..0c30b6430c2 100644
--- a/opengrok-indexer/src/main/java/org/opengrok/indexer/util/Executor.java
+++ b/opengrok-indexer/src/main/java/org/opengrok/indexer/util/Executor.java
@@ -412,7 +412,7 @@ public static void registerErrorHandler() {
             LOGGER.log(Level.FINE, "Installing default uncaught exception handler");
             Thread.setDefaultUncaughtExceptionHandler((t, e) ->
                     LOGGER.log(Level.SEVERE, String.format("Uncaught exception in thread %s with ID %d: %s",
-                            t.getName(), t.getId(), e.getMessage()), e));
+                            t.getName(), t.threadId(), e.getMessage()), e));
         }
     }
 
diff --git a/opengrok-indexer/src/main/java/org/opengrok/indexer/util/Progress.java b/opengrok-indexer/src/main/java/org/opengrok/indexer/util/Progress.java
index 63fe5713d40..a517ad54a39 100644
--- a/opengrok-indexer/src/main/java/org/opengrok/indexer/util/Progress.java
+++ b/opengrok-indexer/src/main/java/org/opengrok/indexer/util/Progress.java
@@ -43,7 +43,7 @@
  * the higher log level ({@link Level} value) will be used. The default base level is {@code Level.INFO}.
  * Regardless of the base level, maximum 4 log levels will be used.
  */
-public class Progress implements AutoCloseable {
+public final class Progress implements AutoCloseable {
     private final Logger logger;
     private final Long totalCount;
     private final String suffix;
diff --git a/opengrok-indexer/src/main/java/org/opengrok/indexer/web/SearchHelper.java b/opengrok-indexer/src/main/java/org/opengrok/indexer/web/SearchHelper.java
index f8177ef2d62..be6c8d2da2e 100644
--- a/opengrok-indexer/src/main/java/org/opengrok/indexer/web/SearchHelper.java
+++ b/opengrok-indexer/src/main/java/org/opengrok/indexer/web/SearchHelper.java
@@ -476,7 +476,7 @@ public SearchHelper executeQuery() {
         }
         try {
             TopFieldDocs fdocs = searcher.search(query, start + maxItems, sort);
-            totalHits = fdocs.totalHits.value;
+            totalHits = fdocs.totalHits.value();
             hits = fdocs.scoreDocs;
 
             /*
@@ -743,7 +743,7 @@ public int searchSingle(File file) throws IOException,
         query = singleBuilder.setPath(path).build();
 
         TopDocs top = searcher.search(query, 1);
-        if (top.totalHits.value == 0) {
+        if (top.totalHits.value() == 0) {
             return -1;
         }
 
diff --git a/opengrok-indexer/src/main/java/org/opengrok/indexer/web/Util.java b/opengrok-indexer/src/main/java/org/opengrok/indexer/web/Util.java
index 877e62241a2..2ada6f865e6 100644
--- a/opengrok-indexer/src/main/java/org/opengrok/indexer/web/Util.java
+++ b/opengrok-indexer/src/main/java/org/opengrok/indexer/web/Util.java
@@ -677,13 +677,12 @@ public static void encode(String s, Appendable dest) throws IOException {
      * @param urlStr string URL
      * @return the encoded URL
      * @throws URISyntaxException URI syntax
-     * @throws MalformedURLException URL malformed
      */
-    public static String encodeURL(String urlStr) throws URISyntaxException, MalformedURLException {
-        URL url = new URL(urlStr);
-        URI constructed = new URI(url.getProtocol(), url.getUserInfo(),
-                url.getHost(), url.getPort(),
-                url.getPath(), url.getQuery(), url.getRef());
+    public static String encodeURL(String urlStr) throws URISyntaxException {
+        // URL url = new URL(urlStr); - this call
+        //FIXME - above can encode url parts somehow, while if you change it to URI, it won't be able to convert e.g.
+        // http://www.example.com/"quotation"/else\ to http://www.example.com/"quotation"/else , see UtilTest:414
+        URI constructed = new URI(urlStr);
         return constructed.toString();
     }
 
@@ -1462,13 +1461,17 @@ private static String generatePageLink(int page, int offset, int limit, long siz
      * @return true if it is http URL, false otherwise
      */
     public static boolean isHttpUri(String string) {
-        URL url;
+        URI uri;
         try {
-            url = new URL(string);
-        } catch (MalformedURLException ex) {
+            uri = new URI(string);
+        } catch (URISyntaxException e) {
+            return false;
+        }
+        String scheme = uri.getScheme();
+        if (scheme == null) {
             return false;
         }
-        return url.getProtocol().equals("http") || url.getProtocol().equals("https");
+        return scheme.equals("http") || scheme.equals("https");
     }
 
     protected static final String REDACTED_USER_INFO = "redacted_by_OpenGrok";
@@ -1481,8 +1484,8 @@ public static boolean isHttpUri(String string) {
     public static String redactUrl(String path) {
         URL url;
         try {
-            url = new URL(path);
-        } catch (MalformedURLException e) {
+            url = (new URI(path)).toURL();
+        } catch (MalformedURLException | URISyntaxException | IllegalArgumentException e) {
             // not an URL
             return path;
         }
@@ -1527,7 +1530,7 @@ public static String linkify(String url, boolean newTab) {
                     attrs.put("rel", "noreferrer");
                 }
                 return buildLink(url, attrs);
-            } catch (URISyntaxException | MalformedURLException ex) {
+            } catch (URISyntaxException ex) {
                 return url;
             }
         }
@@ -1544,10 +1547,9 @@ public static String linkify(String url, boolean newTab) {
      * @return string containing the result
      *
      * @throws URISyntaxException URI syntax
-     * @throws MalformedURLException malformed URL
      */
     public static String buildLink(String name, Map<String, String> attrs)
-            throws URISyntaxException, MalformedURLException {
+            throws URISyntaxException {
         StringBuilder buffer = new StringBuilder();
         buffer.append("<a");
         for (Entry<String, String> attr : attrs.entrySet()) {
@@ -1577,10 +1579,9 @@ public static String buildLink(String name, Map<String, String> attrs)
      * @return string containing the result
      *
      * @throws URISyntaxException URI syntax
-     * @throws MalformedURLException bad URL
      */
     public static String buildLink(String name, String url)
-            throws URISyntaxException, MalformedURLException {
+            throws URISyntaxException {
         Map<String, String> attrs = new TreeMap<>();
         attrs.put("href", url);
         return buildLink(name, attrs);
@@ -1674,20 +1675,20 @@ public static String completeUrl(String url, HttpServletRequest req) {
 
     /**
      * Parses the specified URL and returns its query params.
-     * @param url URL to retrieve the query params from
+     * @param uri URI to retrieve the query params from
      * @return query params of {@code url}
      */
-    public static Map<String, List<String>> getQueryParams(final URL url) {
-        if (url == null) {
+    public static Map<String, List<String>> getQueryParams(final URI uri) {
+        if (uri == null) {
             throw new IllegalArgumentException("Cannot get query params from the null url");
         }
         Map<String, List<String>> returnValue = new HashMap<>();
 
-        if (url.getQuery() == null) {
+        if (uri.getQuery() == null) {
             return returnValue;
         }
 
-        Arrays.stream(url.getQuery().split("&"))
+        Arrays.stream(uri.getQuery().split("&"))
                 .filter(pair -> !pair.isEmpty())
                 .forEach(pair -> {
                     int idx = pair.indexOf('=');
diff --git a/opengrok-indexer/src/test/java/org/opengrok/indexer/index/IndexDatabaseTestHistBasedIterationTest.java b/opengrok-indexer/src/test/java/org/opengrok/indexer/index/IndexDatabaseTestHistBasedIterationTest.java
index ec54efdab8d..99f88b80400 100644
--- a/opengrok-indexer/src/test/java/org/opengrok/indexer/index/IndexDatabaseTestHistBasedIterationTest.java
+++ b/opengrok-indexer/src/test/java/org/opengrok/indexer/index/IndexDatabaseTestHistBasedIterationTest.java
@@ -31,6 +31,7 @@
 import org.apache.lucene.index.TermsEnum;
 import org.apache.lucene.util.AttributeSource;
 import org.apache.lucene.util.BytesRef;
+import org.apache.lucene.util.IOBooleanSupplier;
 import org.junit.jupiter.api.AfterEach;
 import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Test;
@@ -109,6 +110,11 @@ public boolean seekExact(BytesRef bytesRef) throws IOException {
             return false;
         }
 
+        @Override
+        public IOBooleanSupplier prepareSeekExact(BytesRef bytesRef) throws IOException {
+            return null;
+        }
+
         @Override
         public SeekStatus seekCeil(BytesRef bytesRef) throws IOException {
             return null;
diff --git a/opengrok-indexer/src/test/java/org/opengrok/indexer/web/UtilTest.java b/opengrok-indexer/src/test/java/org/opengrok/indexer/web/UtilTest.java
index 71e676b5bc1..917735009fd 100644
--- a/opengrok-indexer/src/test/java/org/opengrok/indexer/web/UtilTest.java
+++ b/opengrok-indexer/src/test/java/org/opengrok/indexer/web/UtilTest.java
@@ -28,8 +28,8 @@
 import java.io.StringWriter;
 import java.io.Writer;
 import java.net.MalformedURLException;
+import java.net.URI;
 import java.net.URISyntaxException;
-import java.net.URL;
 import java.nio.charset.StandardCharsets;
 import java.util.List;
 import java.util.Locale;
@@ -453,7 +453,7 @@ void testBuildLink() throws URISyntaxException, MalformedURLException {
 
     @Test
     void testBuildLinkInvalidUrl1() {
-        assertThrows(MalformedURLException.class, () -> Util.buildLink("link", "www.example.com")); // invalid protocol
+        assertThrows(URISyntaxException.class, () -> Util.buildLink("link", "www.example.com")); // invalid protocol
     }
 
     @Test
@@ -553,21 +553,21 @@ void getQueryParamsNullTest() {
     }
 
     @Test
-    void getQueryParamsEmptyTest() throws MalformedURLException {
-        URL url = new URL("http://test.com/test");
+    void getQueryParamsEmptyTest() throws URISyntaxException {
+        URI url = new URI("http://test.com/test");
         assertTrue(Util.getQueryParams(url).isEmpty());
     }
 
     @Test
-    void getQueryParamsEmptyTest2() throws MalformedURLException {
-        URL url = new URL("http://test.com/test?");
-        assertTrue(Util.getQueryParams(url).isEmpty());
+    void getQueryParamsEmptyTest2() throws URISyntaxException {
+        URI uri = new URI("http://test.com/test?");
+        assertTrue(Util.getQueryParams(uri).isEmpty());
     }
 
     @Test
-    void getQueryParamsSingleTest() throws MalformedURLException {
-        URL url = new URL("http://test.com?param1=value1");
-        Map<String, List<String>> params = Util.getQueryParams(url);
+    void getQueryParamsSingleTest() throws URISyntaxException {
+        URI uri = new URI("http://test.com?param1=value1");
+        Map<String, List<String>> params = Util.getQueryParams(uri);
 
         assertEquals(1, params.size());
 
@@ -575,9 +575,9 @@ void getQueryParamsSingleTest() throws MalformedURLException {
     }
 
     @Test
-    void getQueryParamsMultipleTest() throws MalformedURLException {
-        URL url = new URL("http://test.com?param1=value1&param2=value2");
-        Map<String, List<String>> params = Util.getQueryParams(url);
+    void getQueryParamsMultipleTest() throws URISyntaxException {
+        URI uri = new URI("http://test.com?param1=value1&param2=value2");
+        Map<String, List<String>> params = Util.getQueryParams(uri);
 
         assertEquals(2, params.size());
 
@@ -586,9 +586,9 @@ void getQueryParamsMultipleTest() throws MalformedURLException {
     }
 
     @Test
-    void getQueryParamsMultipleSameTest() throws MalformedURLException {
-        URL url = new URL("http://test.com?param1=value1&param1=value2");
-        Map<String, List<String>> params = Util.getQueryParams(url);
+    void getQueryParamsMultipleSameTest() throws URISyntaxException {
+        URI uri = new URI("http://test.com?param1=value1&param1=value2");
+        Map<String, List<String>> params = Util.getQueryParams(uri);
 
         assertEquals(1, params.size());
 
@@ -596,9 +596,9 @@ void getQueryParamsMultipleSameTest() throws MalformedURLException {
     }
 
     @Test
-    void getQueryParamsEncodedTest() throws MalformedURLException {
-        URL url = new URL("http://test.com?param1=%3Fvalue%3F");
-        Map<String, List<String>> params = Util.getQueryParams(url);
+    void getQueryParamsEncodedTest() throws URISyntaxException {
+        URI uri = new URI("http://test.com?param1=%3Fvalue%3F");
+        Map<String, List<String>> params = Util.getQueryParams(uri);
 
         assertEquals(1, params.size());
 
@@ -606,19 +606,19 @@ void getQueryParamsEncodedTest() throws MalformedURLException {
     }
 
     @Test
-    void getQueryParamsEmptyValueTest() throws MalformedURLException {
-        URL url = new URL("http://test.com?param1=");
+    void getQueryParamsEmptyValueTest() throws URISyntaxException {
+        URI uri = new URI("http://test.com?param1=");
 
-        Map<String, List<String>> params = Util.getQueryParams(url);
+        Map<String, List<String>> params = Util.getQueryParams(uri);
 
         assertThat(params.get("param1"), contains(""));
     }
 
     @Test
-    void getQueryParamsEmptyAndNormalValuesCombinedTest() throws MalformedURLException {
-        URL url = new URL("http://test.com?param1=value1&param2=&param3&param4=value4");
+    void getQueryParamsEmptyAndNormalValuesCombinedTest() throws URISyntaxException {
+        URI uri = new URI("http://test.com?param1=value1&param2=&param3&param4=value4");
 
-        Map<String, List<String>> params = Util.getQueryParams(url);
+        Map<String, List<String>> params = Util.getQueryParams(uri);
 
         assertThat(params.get("param1"), contains("value1"));
         assertThat(params.get("param2"), contains(""));
diff --git a/opengrok-web/src/main/java/org/opengrok/web/api/v1/RestApp.java b/opengrok-web/src/main/java/org/opengrok/web/api/v1/RestApp.java
index ba2a1a5aace..7d179a16cd1 100644
--- a/opengrok-web/src/main/java/org/opengrok/web/api/v1/RestApp.java
+++ b/opengrok-web/src/main/java/org/opengrok/web/api/v1/RestApp.java
@@ -31,6 +31,7 @@ public class RestApp extends ResourceConfig {
 
     public static final String API_PATH = "api/v1";
 
+    @SuppressWarnings("this-escape")
     public RestApp() {
         register(new SuggesterAppBinder());
         packages("org.opengrok.web.api.constraints", "org.opengrok.web.api.error");
diff --git a/opengrok-web/src/main/java/org/opengrok/web/api/v1/controller/SuggesterController.java b/opengrok-web/src/main/java/org/opengrok/web/api/v1/controller/SuggesterController.java
index 926be5d02b2..4886ca44855 100644
--- a/opengrok-web/src/main/java/org/opengrok/web/api/v1/controller/SuggesterController.java
+++ b/opengrok-web/src/main/java/org/opengrok/web/api/v1/controller/SuggesterController.java
@@ -62,8 +62,8 @@
 import org.opengrok.web.api.v1.suggester.provider.filter.Suggester;
 import org.opengrok.web.api.v1.suggester.provider.service.SuggesterService;
 
-import java.net.MalformedURLException;
-import java.net.URL;
+import java.net.URI;
+import java.net.URISyntaxException;
 import java.time.Duration;
 import java.time.Instant;
 import java.util.AbstractMap.SimpleEntry;
@@ -197,8 +197,8 @@ public void rebuild(@PathParam("project") final String project) {
     public void addSearchCountsQueries(final List<String> urls) {
         for (String urlStr : urls) {
             try {
-                var url = new URL(urlStr);
-                var params = Util.getQueryParams(url);
+                var uri = new URI(urlStr);
+                var params = Util.getQueryParams(uri);
 
                 var projects = params.get("project");
 
@@ -211,12 +211,10 @@ public void addSearchCountsQueries(final List<String> urls) {
                         } else {
                             getQuery(field, fieldQueryText.get(0))
                                     .ifPresent(q -> suggester.onSearch(projects, q));
-
                         }
                     }
-
                 }
-            } catch (MalformedURLException e) {
+            } catch (URISyntaxException e) {
                 logger.log(Level.WARNING, e, () -> "Could not add search counts for " + urlStr);
             }
         }
diff --git a/opengrok-web/src/main/java/org/opengrok/web/api/v1/suggester/query/SuggesterQueryParser.java b/opengrok-web/src/main/java/org/opengrok/web/api/v1/suggester/query/SuggesterQueryParser.java
index c911393e17b..f585816e460 100644
--- a/opengrok-web/src/main/java/org/opengrok/web/api/v1/suggester/query/SuggesterQueryParser.java
+++ b/opengrok-web/src/main/java/org/opengrok/web/api/v1/suggester/query/SuggesterQueryParser.java
@@ -134,10 +134,10 @@ protected Query getBooleanQuery(final List<BooleanClause> clauses) throws ParseE
             return super.getBooleanQuery(clauses);
         }
         var reducedList = clauses.stream()
-                .filter(booleanClause -> BooleanClause.Occur.SHOULD != booleanClause.getOccur())
+                .filter(booleanClause -> BooleanClause.Occur.SHOULD != booleanClause.occur())
                 .collect(Collectors.toList());
         inSuggesterClausesList.stream()
-                .filter(booleanClause -> BooleanClause.Occur.SHOULD == booleanClause.getOccur())
+                .filter(booleanClause -> BooleanClause.Occur.SHOULD == booleanClause.occur())
                 .forEach(reducedList::add);
         return super.getBooleanQuery(reducedList);
     }
@@ -145,7 +145,7 @@ protected Query getBooleanQuery(final List<BooleanClause> clauses) throws ParseE
     private boolean isInSuggesterClauses(BooleanClause clause) {
         return suggesterClauses.stream()
                 .anyMatch(itemClause ->
-                        clause.getQuery().equals(itemClause.getQuery())
+                        clause.query().equals(itemClause.query())
                 );
     }
 
diff --git a/plugins/src/main/java/opengrok/auth/entity/LdapUser.java b/plugins/src/main/java/opengrok/auth/entity/LdapUser.java
index f67dbcf738b..37d9060c251 100644
--- a/plugins/src/main/java/opengrok/auth/entity/LdapUser.java
+++ b/plugins/src/main/java/opengrok/auth/entity/LdapUser.java
@@ -36,6 +36,7 @@
 public class LdapUser implements Serializable {
 
     private String dn; // Distinguished Name
+    @SuppressWarnings("serial")
     private final Map<String, Set<String>> attributes;
 
     private static final long serialVersionUID = 1L;
diff --git a/plugins/src/main/java/opengrok/auth/plugin/configuration/Configuration.java b/plugins/src/main/java/opengrok/auth/plugin/configuration/Configuration.java
index fc211f8a168..5882e6349bc 100644
--- a/plugins/src/main/java/opengrok/auth/plugin/configuration/Configuration.java
+++ b/plugins/src/main/java/opengrok/auth/plugin/configuration/Configuration.java
@@ -55,6 +55,7 @@ public class Configuration implements Serializable {
     private static final long serialVersionUID = -1;
 
     @JsonProperty
+    @SuppressWarnings("serial")
     private List<LdapServer> servers = new ArrayList<>();
     @JsonProperty
     private int interval;
diff --git a/plugins/src/main/java/opengrok/auth/plugin/ldap/LdapFacade.java b/plugins/src/main/java/opengrok/auth/plugin/ldap/LdapFacade.java
index ba6d619be8f..0f0643ea380 100644
--- a/plugins/src/main/java/opengrok/auth/plugin/ldap/LdapFacade.java
+++ b/plugins/src/main/java/opengrok/auth/plugin/ldap/LdapFacade.java
@@ -176,6 +176,7 @@ private void addAttrToMap(Map<String, Set<String>> map, Attribute attr) throws N
         }
     }
 
+    @SuppressWarnings("this-escape")
     public LdapFacade(Configuration cfg) {
         setServers(cfg.getServers(), cfg.getConnectTimeout(), cfg.getReadTimeout());
         setInterval(cfg.getInterval());
diff --git a/plugins/src/main/java/opengrok/auth/plugin/ldap/LdapServer.java b/plugins/src/main/java/opengrok/auth/plugin/ldap/LdapServer.java
index 5d6ffb94526..3d426ccf34d 100644
--- a/plugins/src/main/java/opengrok/auth/plugin/ldap/LdapServer.java
+++ b/plugins/src/main/java/opengrok/auth/plugin/ldap/LdapServer.java
@@ -25,7 +25,6 @@
 import com.fasterxml.jackson.annotation.JsonAutoDetect;
 import com.fasterxml.jackson.annotation.JsonIgnore;
 import com.fasterxml.jackson.annotation.JsonProperty;
-import org.jetbrains.annotations.Nullable;
 
 import java.io.IOException;
 import java.io.Serializable;
@@ -83,6 +82,8 @@ public class LdapServer implements Serializable {
     private int readTimeout;
 
     private int interval = 10 * 1000;
+
+    @SuppressWarnings("serial")
     private final Map<String, String> env;
     private transient LdapContext ctx;
     private long errorTimestamp = 0;
@@ -91,11 +92,13 @@ public LdapServer() {
         this(prepareEnv());
     }
 
+    @SuppressWarnings("this-escape")
     public LdapServer(String server) {
         this(prepareEnv());
         setName(server);
     }
 
+    @SuppressWarnings("this-escape")
     public LdapServer(String server, String username, String password) {
         this(prepareEnv());
         setName(server);
@@ -208,7 +211,6 @@ public InetAddress[] getAddresses(String hostname) throws UnknownHostException {
      * Go through all IP addresses and find out if they are reachable.
      * @return true if all IP addresses are reachable, false otherwise
      */
-    @JsonIgnore
     public boolean isReachable() {
         try {
             InetAddress[] addresses = getAddresses(urlToHostname(getUrl()));
@@ -243,7 +245,6 @@ public boolean isReachable() {
      *
      * @return true if it is working
      */
-    @JsonIgnore
     public synchronized boolean isWorking() {
         if (ctx == null) {
             if (!isReachable()) {
@@ -260,7 +261,6 @@ public synchronized boolean isWorking() {
      *
      * @return the new connection or null
      */
-    @Nullable
     private synchronized LdapContext connect() {
         LOGGER.log(Level.INFO, "Connecting to LDAP server {0} ", this);
 
@@ -292,8 +292,7 @@ private synchronized LdapContext connect() {
                 LOGGER.log(Level.INFO, "Connected to LDAP server {0}", this);
                 errorTimestamp = 0;
             } catch (NamingException ex) {
-                LOGGER.log(Level.WARNING,
-                        String.format("LDAP server %s is not responding", env.get(Context.PROVIDER_URL)), ex);
+                LOGGER.log(Level.WARNING, "LDAP server {0} is not responding", env.get(Context.PROVIDER_URL));
                 errorTimestamp = System.currentTimeMillis();
                 close();
                 ctx = null;
diff --git a/pom.xml b/pom.xml
index 4dcbbb36b8d..10df0aeed4f 100644
--- a/pom.xml
+++ b/pom.xml
@@ -60,9 +60,9 @@ Portions Copyright (c) 2018, 2020, Chris Fraire <cfraire@me.com>.
     </scm>
 
     <properties>
-        <lucene.version>9.9.2</lucene.version>
+        <lucene.version>10.0.0</lucene.version>
         <mavenjavadocplugin.version>3.6.0</mavenjavadocplugin.version>
-        <java.version>11</java.version>
+        <java.version>21</java.version>
         <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
         <jersey.version>3.1.7</jersey.version>
         <!-- Jackson version needs to match the version of Jackson which Jersey
diff --git a/suggester/src/main/java/org/opengrok/suggest/SuggestResultCollector.java b/suggester/src/main/java/org/opengrok/suggest/SuggestResultCollector.java
index aefff246b3c..beac581f074 100644
--- a/suggester/src/main/java/org/opengrok/suggest/SuggestResultCollector.java
+++ b/suggester/src/main/java/org/opengrok/suggest/SuggestResultCollector.java
@@ -27,6 +27,7 @@
 import org.apache.lucene.index.StoredFields;
 import org.apache.lucene.search.CollectionTerminatedException;
 import org.apache.lucene.search.Collector;
+import org.apache.lucene.search.CollectorManager;
 import org.apache.lucene.search.IndexSearcher;
 import org.apache.lucene.search.LeafCollector;
 import org.apache.lucene.search.Scorable;
@@ -35,6 +36,7 @@
 import org.opengrok.suggest.query.data.BitIntsHolder;
 
 import java.io.IOException;
+import java.util.Collection;
 
 /**
  * Collects Suggester query results.
@@ -62,6 +64,29 @@ public LeafCollector getLeafCollector(LeafReaderContext context) throws IOExcept
         return new SuggesterLeafCollector(context);
     }
 
+    /**
+     * Creates a {@link CollectorManager} that can concurrently collect matching docs in a {@link
+     * BitIntsHolder}.
+     */
+    public static CollectorManager<SuggestResultCollector, BitIntsHolder> createManager(LeafReaderContext leafReaderContext, ComplexQueryData data,
+                                                                                        BitIntsHolder documentIds) {
+        return new CollectorManager<>() {
+            @Override
+            public SuggestResultCollector newCollector() {
+                return new SuggestResultCollector(leafReaderContext, data, documentIds);
+            }
+
+            @Override
+            public BitIntsHolder reduce(Collection<SuggestResultCollector> collectors) {
+                BitIntsHolder reduced = documentIds;
+                for (SuggestResultCollector collector : collectors) {
+                    documentIds.or(collector.documentIds); //TODO fix as per https://github.com/apache/lucene/pull/766/files
+                }
+                return reduced;
+            }
+        };
+    }
+
     /**
      * Indicates what features are required from the scorer.
      */
@@ -96,8 +121,8 @@ public void setScorer(Scorable scorer) throws IOException {
                         // it is mentioned in the documentation that #getChildren should not be called
                         // in #setScorer but no better way was found
                         for (var childScorer : scorer.getChildren()) {
-                            if (childScorer.child instanceof PhraseScorer) {
-                                data.scorer = (PhraseScorer) childScorer.child;
+                            if (childScorer.child() instanceof PhraseScorer) {
+                                data.scorer = (PhraseScorer) childScorer.child();
                             }
                         }
                     } catch (Exception e) {
diff --git a/suggester/src/main/java/org/opengrok/suggest/Suggester.java b/suggester/src/main/java/org/opengrok/suggest/Suggester.java
index 6f7073e4160..a0eacb68844 100644
--- a/suggester/src/main/java/org/opengrok/suggest/Suggester.java
+++ b/suggester/src/main/java/org/opengrok/suggest/Suggester.java
@@ -161,7 +161,7 @@ public Suggester(
                 runnable -> {
                     Thread thread = Executors.defaultThreadFactory().newThread(runnable);
                     // This should match the naming in OpenGrokThreadFactory class.
-                    thread.setName("OpenGrok-suggester-lookup-" + thread.getId());
+                    thread.setName("OpenGrok-suggester-lookup-" + thread.threadId());
                     return thread;
                 });
 
@@ -169,7 +169,7 @@ public Suggester(
                 runnable -> {
                     Thread thread = Executors.defaultThreadFactory().newThread(runnable);
                     // This should match the naming in OpenGrokThreadFactory class.
-                    thread.setName("OpenGrok-suggester-rebuild-" + thread.getId());
+                    thread.setName("OpenGrok-suggester-rebuild-" + thread.threadId());
                     return thread;
                 });
 
diff --git a/suggester/src/main/java/org/opengrok/suggest/SuggesterSearcher.java b/suggester/src/main/java/org/opengrok/suggest/SuggesterSearcher.java
index f02eefe6a9f..5990273da9c 100644
--- a/suggester/src/main/java/org/opengrok/suggest/SuggesterSearcher.java
+++ b/suggester/src/main/java/org/opengrok/suggest/SuggesterSearcher.java
@@ -238,7 +238,7 @@ private ComplexQueryData getComplexQueryData(final Query query, final LeafReader
 
         BitIntsHolder documentIds = new BitIntsHolder();
         try {
-            search(query, new SuggestResultCollector(leafReaderContext, data, documentIds));
+            search(query, SuggestResultCollector.createManager(leafReaderContext, data, documentIds));
         } catch (IOException e) {
             if (Thread.currentThread().isInterrupted()) {
                 interrupted = true;
@@ -299,7 +299,7 @@ private boolean needPositionsAndFrequencies(final Query query) {
 
         if (query instanceof BooleanQuery) {
             for (BooleanClause bc : ((BooleanQuery) query).clauses()) {
-                if (needPositionsAndFrequencies(bc.getQuery())) {
+                if (needPositionsAndFrequencies(bc.query())) {
                     return true;
                 }
             }
diff --git a/suggester/src/main/java/org/opengrok/suggest/SuggesterUtils.java b/suggester/src/main/java/org/opengrok/suggest/SuggesterUtils.java
index 801809b1b47..29a4713f025 100644
--- a/suggester/src/main/java/org/opengrok/suggest/SuggesterUtils.java
+++ b/suggester/src/main/java/org/opengrok/suggest/SuggesterUtils.java
@@ -130,7 +130,7 @@ public static List<Term> intoTerms(final Query query) {
 
             if (q instanceof BooleanQuery) {
                 for (BooleanClause bc : ((BooleanQuery) q).clauses()) {
-                    queue.add(bc.getQuery());
+                    queue.add(bc.query());
                 }
             } else if (q instanceof TermQuery) {
                 terms.add(((TermQuery) q).getTerm());
@@ -164,7 +164,7 @@ public static List<Term> intoTermsExceptPhraseQuery(final Query query) {
 
             if (q instanceof BooleanQuery) {
                 for (BooleanClause bc : ((BooleanQuery) q).clauses()) {
-                    queue.add(bc.getQuery());
+                    queue.add(bc.query());
                 }
             } else if (q instanceof TermQuery) {
                 terms.add(((TermQuery) q).getTerm());
diff --git a/suggester/src/main/java/org/opengrok/suggest/query/customized/CustomExactPhraseScorer.java b/suggester/src/main/java/org/opengrok/suggest/query/customized/CustomExactPhraseScorer.java
index 5434edbdec0..fbcda966f31 100644
--- a/suggester/src/main/java/org/opengrok/suggest/query/customized/CustomExactPhraseScorer.java
+++ b/suggester/src/main/java/org/opengrok/suggest/query/customized/CustomExactPhraseScorer.java
@@ -29,7 +29,6 @@
 import org.apache.lucene.search.DocIdSetIterator;
 import org.apache.lucene.search.Scorer;
 import org.apache.lucene.search.TwoPhaseIterator;
-import org.apache.lucene.search.Weight;
 import org.jetbrains.annotations.NotNull;
 import org.opengrok.suggest.query.PhraseScorer;
 import org.opengrok.suggest.query.data.BitIntsHolder;
@@ -66,16 +65,14 @@ private static class PostingsAndPosition {
     // custom – constructor parameters
     /**
      * Creates custom exact phrase scorer which remembers the positions of the found matches.
-     * @param weight query weight
      * @param postings postings of the terms
      * @param offset the offset that is added to the found match position
      */
     CustomExactPhraseScorer(
-            final Weight weight,
             final CustomPhraseQuery.PostingsAndFreq[] postings,
             final int offset
     ) {
-        super(weight);
+        super();
 
         this.offset = offset; // custom
 
@@ -128,7 +125,7 @@ public DocIdSetIterator iterator() {
 
     @Override
     public String toString() {
-        return "CustomExactPhraseScorer(" + weight + ")"; // custom – renamed class
+        return "CustomExactPhraseScorer(" + this.offset + ")"; // custom – renamed class
     }
 
     @Override
diff --git a/suggester/src/main/java/org/opengrok/suggest/query/customized/CustomPhraseQuery.java b/suggester/src/main/java/org/opengrok/suggest/query/customized/CustomPhraseQuery.java
index 75612cd80fc..0cce3f01a6e 100644
--- a/suggester/src/main/java/org/opengrok/suggest/query/customized/CustomPhraseQuery.java
+++ b/suggester/src/main/java/org/opengrok/suggest/query/customized/CustomPhraseQuery.java
@@ -31,7 +31,7 @@
 import org.apache.lucene.search.Query;
 import org.apache.lucene.search.QueryVisitor;
 import org.apache.lucene.search.ScoreMode;
-import org.apache.lucene.search.Scorer;
+import org.apache.lucene.search.ScorerSupplier;
 import org.apache.lucene.search.Weight;
 import org.apache.lucene.util.ArrayUtil;
 import org.apache.lucene.util.BytesRef;
@@ -263,8 +263,8 @@ public Explanation explain(LeafReaderContext leafReaderContext, int i) {
         }
 
         @Override
-        public Scorer scorer(LeafReaderContext context) throws IOException {
-            LeafReader reader = context.reader();
+        public ScorerSupplier scorerSupplier(LeafReaderContext leafReaderContext) throws IOException {
+            LeafReader reader = leafReaderContext.reader();
 
             CustomPhraseQuery.PostingsAndFreq[] postingsFreqs = new CustomPhraseQuery.PostingsAndFreq[query.terms.length];
 
@@ -281,7 +281,7 @@ public Scorer scorer(LeafReaderContext context) throws IOException {
 
                 for(int i = 0; i < query.terms.length; ++i) {
                     Term t = query.terms[i];
-                    TermState state = this.states[i].get(context);
+                    TermState state = this.states[i].get(leafReaderContext).get();
                     if (state == null) {
                         return null;
                     }
@@ -293,10 +293,13 @@ public Scorer scorer(LeafReaderContext context) throws IOException {
 
                 if (query.slop == 0) {
                     ArrayUtil.timSort(postingsFreqs);
-
-                    return new CustomExactPhraseScorer(this, postingsFreqs, query.offset);
+                    //FIXME
+                    //return new CustomExactPhraseScorer(postingsFreqs, query.offset);
+                    return scorerSupplier(leafReaderContext);
                 } else {
-                    return new CustomSloppyPhraseScorer(this, postingsFreqs, query.slop, query.offset);
+                    //FIXME
+                    //return new CustomSloppyPhraseScorer(postingsFreqs, query.slop, query.offset);
+                    return scorerSupplier(leafReaderContext);
                 }
             }
         }
diff --git a/suggester/src/main/java/org/opengrok/suggest/query/customized/CustomSloppyPhraseScorer.java b/suggester/src/main/java/org/opengrok/suggest/query/customized/CustomSloppyPhraseScorer.java
index 42fe34854f8..a5499d34cdc 100644
--- a/suggester/src/main/java/org/opengrok/suggest/query/customized/CustomSloppyPhraseScorer.java
+++ b/suggester/src/main/java/org/opengrok/suggest/query/customized/CustomSloppyPhraseScorer.java
@@ -33,7 +33,6 @@
 import org.apache.lucene.search.DocIdSetIterator;
 import org.apache.lucene.search.Scorer;
 import org.apache.lucene.search.TwoPhaseIterator;
-import org.apache.lucene.search.Weight;
 import org.apache.lucene.util.FixedBitSet;
 import org.jetbrains.annotations.NotNull;
 import org.opengrok.suggest.query.PhraseScorer;
@@ -72,18 +71,16 @@ final class CustomSloppyPhraseScorer extends Scorer implements PhraseScorer { //
     // custom – constructor parameters
     /**
      * Creates custom sloppy phrase scorer which remembers the positions of the found matches.
-     * @param weight query weight
      * @param postings postings of the terms
      * @param slop "word edit distance"
      * @param offset the offset that is added to the found match position
      */
     CustomSloppyPhraseScorer(
-            final Weight weight,
             final CustomPhraseQuery.PostingsAndFreq[] postings,
             final int slop,
             final int offset
     ) {
-        super(weight);
+        super();
         this.slop = slop;
         this.offset = offset; // custom
         this.numPostings = postings.length;
@@ -628,7 +625,7 @@ public float score() {
 
     @Override
     public String toString() {
-        return "CustomSloppyPhraseScorer(" + weight + ")"; // custom – renamed class
+        return "CustomSloppyPhraseScorer(" + this.numPostings + ")"; // custom – renamed class
     }
 
     @Override
diff --git a/suggester/src/main/java/org/opengrok/suggest/util/Progress.java b/suggester/src/main/java/org/opengrok/suggest/util/Progress.java
index e50b7ed2bc6..3c070aaa511 100644
--- a/suggester/src/main/java/org/opengrok/suggest/util/Progress.java
+++ b/suggester/src/main/java/org/opengrok/suggest/util/Progress.java
@@ -66,6 +66,7 @@ public class Progress implements AutoCloseable {
      * @param logLevel base log level
      * @param isPrintProgress whether to print the progress
      */
+    @SuppressWarnings("this-escape")
     public Progress(Logger logger, String suffix, long totalCount, Level logLevel, boolean isPrintProgress) {
         this.logger = logger;
         this.suffix = suffix;