You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@lucene.apache.org by rm...@apache.org on 2020/08/30 04:01:07 UTC

[lucene-solr] 01/01: LUCENE-9215: replace checkJavaDocs.py with doclet

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

rmuir pushed a commit to branch LUCENE-9215
in repository https://gitbox.apache.org/repos/asf/lucene-solr.git

commit 19cae4218ff605366dbae53011265c8a29add3be
Author: Robert Muir <rm...@apache.org>
AuthorDate: Sat Aug 29 23:59:23 2020 -0400

    LUCENE-9215: replace checkJavaDocs.py with doclet
    
    This has the same logic as the previous python, but no longer relies
    upon parsing HTML output, instead using java's doclet processor.
    
    The errors are reported like "normal" javadoc errors with source file
    name and line number and happen when running "gradlew javadoc"
    
    Although the "rules" are the same as the previous python, the python had
    some bugs where the checker didn't quite do exactly what we wanted, so
    some fixes were applied throughout.
---
 build.gradle                                       |   8 +-
 .../org/apache/lucene/gradle/MissingDoclet.java    | 331 +++++++++++++++++
 dev-tools/scripts/checkJavaDocs.py                 | 392 ---------------------
 dev-tools/scripts/smokeTestRelease.py              |  34 +-
 gradle/documentation/render-javadoc.gradle         |  11 +
 gradle/validation/missing-docs-check.gradle        | 127 -------
 lucene/analysis/common/build.gradle                |   6 +
 .../analysis/compound/hyphenation/TernaryTree.java |   2 +-
 lucene/analysis/icu/build.gradle                   |   9 +
 lucene/analysis/kuromoji/build.gradle              |   5 +
 lucene/analysis/nori/build.gradle                  |   6 +
 lucene/analysis/opennlp/build.gradle               |   5 +
 lucene/analysis/smartcn/build.gradle               |   5 +
 lucene/backward-codecs/build.gradle                |  13 +
 .../java/org/apache/lucene/codecs/Placeholder.java |   3 +-
 lucene/benchmark/build.gradle                      |   7 +-
 .../CachingNaiveBayesClassifier.java               |   1 +
 lucene/codecs/build.gradle                         |   5 +
 .../sharedterms/STUniformSplitTermsWriter.java     |   2 +-
 lucene/core/build.gradle                           |  14 +
 .../org/apache/lucene/index/BaseTermsEnum.java     |   1 +
 .../lucene/index/FilterSortedNumericDocValues.java |   7 +
 .../lucene/index/FilterSortedSetDocValues.java     |  11 +
 .../lucene/search/MultiCollectorManager.java       |   3 +-
 .../lucene/search/similarities/AxiomaticF1EXP.java |   2 +
 .../lucene/search/similarities/AxiomaticF1LOG.java |   2 +
 .../lucene/search/similarities/AxiomaticF2EXP.java |   2 +
 .../lucene/search/similarities/AxiomaticF2LOG.java |   2 +
 .../lucene/search/similarities/AxiomaticF3EXP.java |   2 +
 .../lucene/search/similarities/AxiomaticF3LOG.java |   2 +
 .../lucene/search/similarities/LambdaTTF.java      |   1 +
 .../lucene/search/spans/SpanContainingQuery.java   |   2 +-
 .../apache/lucene/search/spans/SpanNearQuery.java  |   2 +-
 .../apache/lucene/search/spans/SpanNotQuery.java   |   2 +-
 .../apache/lucene/search/spans/SpanOrQuery.java    |   2 +-
 .../search/spans/SpanPositionCheckQuery.java       |   2 +-
 .../apache/lucene/search/spans/SpanTermQuery.java  |   2 +-
 .../lucene/search/spans/SpanWithinQuery.java       |   2 +-
 .../java/org/apache/lucene/util/PagedBytes.java    |   4 +-
 lucene/grouping/build.gradle                       |   7 +-
 lucene/highlighter/build.gradle                    |   6 +
 lucene/luke/build.gradle                           |   2 +
 lucene/misc/build.gradle                           |  17 +-
 .../org/apache/lucene/document/LazyDocument.java   |   1 +
 lucene/monitor/build.gradle                        |   5 +
 lucene/queries/build.gradle                        |   7 +-
 .../lucene/queries/function/FunctionQuery.java     |   4 +-
 .../function/valuesource/MultiFunction.java        |   1 +
 lucene/queryparser/build.gradle                    |   6 +
 .../precedence/processors/package-info.java        |  39 ++
 .../flexible/precedence/processors/package.html    |  47 ---
 lucene/replicator/build.gradle                     |   5 +
 lucene/sandbox/build.gradle                        |  10 +
 lucene/spatial-extras/build.gradle                 |   6 +
 .../org/apache/lucene/spatial/package-info.java    |   7 +-
 .../java/org/apache/lucene/spatial/package.html    |  26 --
 .../lucene/spatial/prefix/PrefixTreeStrategy.java  |   1 +
 .../lucene/spatial/prefix/tree/QuadPrefixTree.java |   1 +
 .../apache/lucene/spatial/util/package-info.java   |   7 +-
 .../org/apache/lucene/spatial/util/package.html    |  26 --
 lucene/test-framework/build.gradle                 |  25 ++
 .../index/BaseTermVectorsFormatTestCase.java       |   2 +
 .../apache/lucene/index/PointsStackTracker.java    |   1 +
 .../lucene/search/ShardSearchingTestBase.java      |   1 +
 solr/build.gradle                                  |   8 +-
 solr/contrib/analysis-extras/build.gradle          |   7 +
 solr/contrib/analytics/build.gradle                |   9 +
 solr/contrib/langid/build.gradle                   |   5 +
 solr/contrib/velocity/build.gradle                 |   5 +
 .../java/org/apache/solr/query/package-info.java}  |   9 +-
 .../src/java/org/apache/solr/query/package.html    |  27 --
 .../apache/solr/request/json/package-info.java}    |   9 +-
 .../java/org/apache/solr/request/json/package.html |  27 --
 .../apache/solr/request/macro/package-info.java}   |   9 +-
 .../org/apache/solr/request/macro/package.html     |  27 --
 .../apache/solr/search/facet/package-info.java}    |  10 +-
 .../java/org/apache/solr/search/facet/package.html |  28 --
 .../solr/util/circuitbreaker/package-info.java}    |   9 +-
 solr/solrj/build.gradle                            |   5 +
 solr/test-framework/build.gradle                   |  13 +
 80 files changed, 695 insertions(+), 811 deletions(-)

diff --git a/build.gradle b/build.gradle
index ec5fed0..f58017f 100644
--- a/build.gradle
+++ b/build.gradle
@@ -96,6 +96,13 @@ ext {
       "python2": propertyOrDefault('python2.exe', 'python2'),
       "perl": propertyOrDefault('perl.exe', 'perl'),
   ]
+
+  // default is to require full javadocs
+  javadocMissingLevel = "method"
+  // anything in these packages is checked with level=method. This allows iteratively fixing one package at a time.
+  javadocMissingMethod = []
+  // default is not to ignore any elements, should only be used to workaround split packages
+  javadocMissingIgnore = []
 }
 
 // Include smaller chunks configuring dedicated build areas.
@@ -138,7 +145,6 @@ apply from: file('gradle/validation/rat-sources.gradle')
 apply from: file('gradle/validation/owasp-dependency-check.gradle')
 apply from: file('gradle/validation/ecj-lint.gradle')
 apply from: file('gradle/validation/gradlew-scripts-tweaked.gradle')
-apply from: file('gradle/validation/missing-docs-check.gradle')
 apply from: file('gradle/validation/validate-log-calls.gradle')
 apply from: file('gradle/validation/check-broken-links.gradle')
 
diff --git a/buildSrc/src/main/java/org/apache/lucene/gradle/MissingDoclet.java b/buildSrc/src/main/java/org/apache/lucene/gradle/MissingDoclet.java
new file mode 100644
index 0000000..812a41c
--- /dev/null
+++ b/buildSrc/src/main/java/org/apache/lucene/gradle/MissingDoclet.java
@@ -0,0 +1,331 @@
+/*
+ * 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.gradle;
+
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Locale;
+import java.util.Set;
+
+import javax.lang.model.element.Element;
+import javax.lang.model.element.ElementKind;
+import javax.lang.model.element.ModuleElement;
+import javax.lang.model.util.Elements;
+import javax.tools.Diagnostic;
+
+import com.sun.source.util.DocTrees;
+
+import jdk.javadoc.doclet.Doclet;
+import jdk.javadoc.doclet.DocletEnvironment;
+import jdk.javadoc.doclet.Reporter;
+import jdk.javadoc.doclet.StandardDoclet;
+
+/**
+ * Checks for missing javadocs, where missing also means "only whitespace" or "license header".
+ * Has option --missing-level (package, class, method) so that we can improve over time.
+ * Has option --missing-ignore to ignore individual elements (such as split packages).
+ * Has option --missing-method to apply "method" level to selected packages (fix one at a time).
+ */
+public class MissingDoclet extends StandardDoclet {
+  private static final int PACKAGE = 0;
+  private static final int CLASS = 1;
+  private static final int METHOD = 2;
+  int level = METHOD;
+  Reporter reporter;
+  DocletEnvironment docEnv;
+  DocTrees docTrees;
+  Elements elementUtils;
+  Set<String> ignored = Collections.emptySet();
+  Set<String> methodPackages = Collections.emptySet();
+  
+  @Override
+  public Set<Doclet.Option> getSupportedOptions() {
+    Set<Doclet.Option> options = new HashSet<>();
+    options.addAll(super.getSupportedOptions());
+    options.add(new Doclet.Option() {
+      @Override
+      public int getArgumentCount() {
+        return 1;
+      }
+
+      @Override
+      public String getDescription() {
+        return "level to enforce for missing javadocs: [package, class, method]";
+      }
+
+      @Override
+      public Kind getKind() {
+        return Option.Kind.STANDARD;
+      }
+
+      @Override
+      public List<String> getNames() {
+        return Collections.singletonList("--missing-level");
+      }
+
+      @Override
+      public String getParameters() {
+        return "level";
+      }
+
+      @Override
+      public boolean process(String option, List<String> arguments) {
+        switch(arguments.get(0)) {
+          case "package":
+            level = PACKAGE;
+            return true;
+          case "class":
+            level = CLASS;
+            return true;
+          case "method":
+            level = METHOD;
+            return true;
+          default:
+            return false;
+        }
+      }
+    });
+    options.add(new Doclet.Option() {
+      @Override
+      public int getArgumentCount() {
+        return 1;
+      }
+
+      @Override
+      public String getDescription() {
+        return "comma separated list of element names to ignore (e.g. as a workaround for split packages)";
+      }
+
+      @Override
+      public Kind getKind() {
+        return Option.Kind.STANDARD;
+      }
+
+      @Override
+      public List<String> getNames() {
+        return Collections.singletonList("--missing-ignore");
+      }
+
+      @Override
+      public String getParameters() {
+        return "ignoredNames";
+      }
+
+      @Override
+      public boolean process(String option, List<String> arguments) {
+        ignored = new HashSet<>(Arrays.asList(arguments.get(0).split(",")));
+        return true;
+      }
+    });
+    options.add(new Doclet.Option() {
+      @Override
+      public int getArgumentCount() {
+        return 1;
+      }
+
+      @Override
+      public String getDescription() {
+        return "comma separated list of packages to check at 'method' level";
+      }
+
+      @Override
+      public Kind getKind() {
+        return Option.Kind.STANDARD;
+      }
+
+      @Override
+      public List<String> getNames() {
+        return Collections.singletonList("--missing-method");
+      }
+
+      @Override
+      public String getParameters() {
+        return "packages";
+      }
+
+      @Override
+      public boolean process(String option, List<String> arguments) {
+        methodPackages = new HashSet<>(Arrays.asList(arguments.get(0).split(",")));
+        return true;
+      }
+    });
+    return options;
+  }
+
+  @Override
+  public void init(Locale locale, Reporter reporter) {
+    this.reporter = reporter;
+    super.init(locale, reporter);
+  }
+
+  @Override
+  public boolean run(DocletEnvironment docEnv) {
+    this.docEnv = docEnv;
+    this.docTrees = docEnv.getDocTrees();
+    this.elementUtils = docEnv.getElementUtils();
+    for (var element : docEnv.getIncludedElements()) {
+      check(element);
+    }
+
+    return super.run(docEnv);
+  }
+  
+  /**
+   * Returns effective check level for this element
+   */
+  private int level(Element element) {
+    String pkg = elementUtils.getPackageOf(element).getQualifiedName().toString();
+    if (methodPackages.contains(pkg)) {
+      return METHOD;
+    } else {
+      return level;
+    }
+  }
+  
+  /** 
+   * Check an individual element.
+   * This checks packages and types from the doctrees.
+   * It will recursively check methods/fields from encountered types when the level is "method"
+   */
+  private void check(Element element) {
+    switch(element.getKind()) {
+      case MODULE:
+        // don't check the unnamed module, it won't have javadocs
+        if (!((ModuleElement)element).isUnnamed()) {
+          checkComment(element);
+        }
+        break;
+      case PACKAGE:
+        checkComment(element);
+        break;
+      // class-like elements, check them, then recursively check their children (fields and methods)
+      case CLASS:
+      case INTERFACE:
+      case ENUM:
+      case ANNOTATION_TYPE:
+        if (level(element) >= CLASS) {
+          checkComment(element);
+          for (var subElement : element.getEnclosedElements()) {
+            // don't check enclosed types, otherwise we'll double-check since they are in the included docTree
+            if (subElement.getKind() == ElementKind.METHOD || 
+                subElement.getKind() == ElementKind.CONSTRUCTOR || 
+                subElement.getKind() == ElementKind.FIELD || 
+                subElement.getKind() == ElementKind.ENUM_CONSTANT) {
+              check(subElement);
+            }
+          }
+        }
+        break;
+      // method-like elements, check them if we are configured to do so
+      case METHOD:
+      case CONSTRUCTOR:
+      case FIELD:
+      case ENUM_CONSTANT:
+        if (level(element) >= METHOD && !isOverridden(element) && !isSyntheticEnumMethod(element)) {
+          checkComment(element);
+        }
+        break;
+      default:
+        error(element, "I don't know how to analyze " + element.getKind() + " yet.");
+    }
+  }
+  
+  /** Return true if the method is annotated with Override, if so, don't require javadocs (they'll be copied) */
+  private boolean isOverridden(Element element) {
+    for (var annotation : element.getAnnotationMirrors()) {
+      if (annotation.getAnnotationType().toString().equals(Override.class.getName())) {
+        return true;
+      }
+    }
+    return false;
+  }
+  
+  /** 
+   * Return true if the method is synthetic enum method (values/valueOf).
+   * According to the doctree documentation, the "included" set never includes synthetic elements.
+   * It should not happen but it happens!
+   */
+  private boolean isSyntheticEnumMethod(Element element) {
+    if (element.getSimpleName().toString().equals("values") || element.getSimpleName().toString().equals("valueOf")) {
+      if (element.getEnclosingElement().getKind() == ElementKind.ENUM) {
+        return true;
+      }
+    }
+    return false;
+  }
+  
+  /**
+   * Checks that an element doesn't have missing javadocs.
+   * In addition to truly "missing", check that comments aren't solely whitespace (generated by some IDEs),
+   * that they aren't a license header masquerading as a javadoc comment.
+   */
+  private void checkComment(Element element) {
+    if (!docEnv.isIncluded(element)) {
+      return;
+    }
+    if (ignored.contains(element.toString())) {
+      return;
+    }
+    var tree = docTrees.getDocCommentTree(element);
+    if (tree == null || tree.getFirstSentence().isEmpty()) {
+      error(element, "javadocs are missing");
+    } else {
+      var normalized = tree.getFirstSentence().get(0).toString()
+                       .replace('\u00A0', ' ')
+                       .trim()
+                       .toLowerCase(Locale.ROOT);
+      if (normalized.isEmpty()) {
+        error(element, "blank javadoc comment");
+      } else if (normalized.startsWith("licensed to the apache software foundation") ||
+                 normalized.startsWith("copyright 2004 the apache software foundation")) {
+        error(element, "comment is really a license");
+      }
+    }
+  }
+  
+  /** logs a new error for the particular element */
+  private void error(Element element, String message) {
+    var fullMessage = new StringBuilder();
+    switch(element.getKind()) {
+      case MODULE:
+      case PACKAGE:
+        // for modules/packages, we don't have filename + line number, fully qualify
+        fullMessage.append(element.toString());
+        break;
+      case METHOD:
+      case CONSTRUCTOR:
+      case FIELD:
+      case ENUM_CONSTANT:
+        // for method-like elements, include the enclosing type to make it easier
+        fullMessage.append(element.getEnclosingElement().getSimpleName());
+        fullMessage.append(".");
+        fullMessage.append(element.getSimpleName());
+        break;
+      default:
+        // for anything else, use a simple name
+        fullMessage.append(element.getSimpleName());
+        break;
+    }
+    fullMessage.append(" (");
+    fullMessage.append(element.getKind().toString().toLowerCase(Locale.ROOT));
+    fullMessage.append("): ");
+    fullMessage.append(message);
+    reporter.print(Diagnostic.Kind.ERROR, element, fullMessage.toString());
+  }
+}
diff --git a/dev-tools/scripts/checkJavaDocs.py b/dev-tools/scripts/checkJavaDocs.py
deleted file mode 100644
index 14ded3d..0000000
--- a/dev-tools/scripts/checkJavaDocs.py
+++ /dev/null
@@ -1,392 +0,0 @@
-# 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.
-
-import sys
-import os
-import re
-
-reHREF = re.compile('<a.*?>(.*?)</a>', re.IGNORECASE)
-
-reMarkup = re.compile('<.*?>')
-reDivBlock = re.compile('<div class="block">(.*?)</div>', re.IGNORECASE)
-reCaption = re.compile('<caption><span>(.*?)</span>', re.IGNORECASE)
-reJ8Caption = re.compile('<h[23]>(.*?) Summary</h[23]>')
-reTDLastNested = re.compile('^<td class="colLast"><code><strong><a href="[^>]*\.([^>]*?)\.html" title="class in[^>]*">', re.IGNORECASE)
-reMethod = re.compile('^<th class="colSecond" scope="row"><code><span class="memberNameLink"><a href="[^>]*#([^>]*?)">', re.IGNORECASE)
-reColOne = re.compile('^<td class="colOne"><code><strong><a href="[^>]*#([^>]*?)">', re.IGNORECASE)
-reMemberNameLink = re.compile('^<td class="colLast"><code><span class="memberNameLink"><a href="[^>]*#([^>]*?)"', re.IGNORECASE)
-reNestedClassMemberNameLink = re.compile('^<td class="colLast"><code><span class="memberNameLink"><a href="[^>]*?".*?>(.*?)</a>', re.IGNORECASE)
-reMemberNameOneLink = re.compile('^<td class="colOne"><code><span class="memberNameLink"><a href="[^>]*#([^>]*?)"', re.IGNORECASE)
-
-# the Method detail section at the end
-reMethodDetail = re.compile('^<h[23]>Method Details?</h[23]>$', re.IGNORECASE)
-reMethodDetailAnchor = re.compile('^(?:</a>)?<a id="([^>]*?)">$', re.IGNORECASE)
-reJ13MethodDetailAnchor = re.compile('^(?:<h3>|</a>)<a id="([^>]*?)">[^>]*</a></h3>$', re.IGNORECASE)
-
-reTag = re.compile("(?i)<(\/?\w+)((\s+\w+(\s*=\s*(?:\".*?\"|'.*?'|[^'\">\s]+))?)+\s*|\s*)\/?>")
-
-def verifyHTML(s):
-
-  stack = []
-  upto = 0
-  while True:
-    m = reTag.search(s, upto)
-    if m is None:
-      break
-    tag = m.group(1)
-    upto = m.end(0)
-
-    if tag[:1] == '/':
-      justTag = tag[1:]
-    else:
-      justTag = tag
-      
-    if justTag.lower() in ('br', 'li', 'p', 'col'):
-      continue
-
-    if tag[:1] == '/':
-      if len(stack) == 0:
-        raise RuntimeError('saw closing "%s" without opening <%s...>' % (m.group(0), tag[1:]))
-      elif stack[-1][0] != tag[1:].lower():
-        raise RuntimeError('closing "%s" does not match opening "%s"' % (m.group(0), stack[-1][1]))
-      stack.pop()
-    else:
-      stack.append((tag.lower(), m.group(0)))
-
-  if len(stack) != 0:
-    raise RuntimeError('"%s" was never closed' % stack[-1][1])
-
-def cleanHTML(s):
-  s = reMarkup.sub('', s)
-  s = s.replace('&nbsp;', ' ')
-  s = s.replace('&lt;', '<')
-  s = s.replace('&gt;', '>')
-  s = s.replace('&amp;', '&')
-  return s.strip()
-
-reH3 = re.compile('^<h3>(.*?)</h3>', re.IGNORECASE | re.MULTILINE)
-reH4 = re.compile('^<h4>(.*?)</h4>', re.IGNORECASE | re.MULTILINE)
-reDetailsDiv = re.compile('<div class="details">')
-reEndOfClassData = re.compile('<!--.*END OF CLASS DATA.*-->')
-reBlockList = re.compile('<ul class="blockList(?:Last)?">')
-reCloseUl = re.compile('</ul>')
-
-def checkClassDetails(fullPath):
-  """
-  Checks for invalid HTML in the full javadocs under each field/method.
-  """
-
-  # TODO: only works with java7 generated javadocs now!
-  with open(fullPath, encoding='UTF-8') as f:
-    desc = []
-    cat = None
-    item = None
-    errors = []
-    inDetailsDiv = False
-    blockListDepth = 0
-    for line in f.readlines():
-      # Skip content up until  <div class="details">
-      if not inDetailsDiv:
-        if reDetailsDiv.match(line) is not None:
-          inDetailsDiv = True
-        continue
-
-      # Stop looking at content at closing details </div>, which is just before <!-- === END OF CLASS DATA === -->
-      if reEndOfClassData.match(line) is not None:
-        if len(desc) != 0:
-          try:
-            verifyHTML(''.join(desc))
-          except RuntimeError as re:
-            #print('    FAILED: %s' % re)
-            errors.append((cat, item, str(re)))
-        break
-
-      # <ul class="blockList(Last)"> is the boundary between items
-      if reBlockList.match(line) is not None:
-        blockListDepth += 1
-        if len(desc) != 0:
-          try:
-            verifyHTML(''.join(desc))
-          except RuntimeError as re:
-            #print('    FAILED: %s' % re)
-            errors.append((cat, item, str(re)))
-          del desc[:]
-
-      if blockListDepth == 3:
-        desc.append(line)
-
-      if reCloseUl.match(line) is not None:
-        blockListDepth -= 1
-      else:
-        m = reH3.search(line)
-        if m is not None:
-          cat = m.group(1)
-        else:
-          m = reH4.search(line)
-          if m is not None:
-            item = m.group(1)
-
-  if len(errors) != 0:
-    print()
-    print(fullPath)
-    for cat, item, message in errors:
-      print('  broken details HTML: %s: %s: %s' % (cat, item, message))
-    return True
-  else:
-    return False
-
-def checkClassSummaries(fullPath):
-  #print("check %s" % fullPath)
-
-  # TODO: only works with java7 generated javadocs now!
-  f = open(fullPath, encoding='UTF-8')
-
-  missing = []
-  broken = []
-  inThing = False
-  lastCaption = None
-  lastItem = None
-
-  desc = None
-
-  foundMethodDetail = False
-  lastMethodAnchor = None
-  lineCount = 0
-  
-  for line in f.readlines():
-    m = reMethodDetail.search(line)
-    lineCount += 1
-    if m is not None:
-      foundMethodDetail = True
-      #print('  got method detail')
-      continue
-
-    # prune methods that are just @Overrides of other interface/classes,
-    # they should be specified elsewhere, if they are e.g. jdk or 
-    # external classes we cannot inherit their docs anyway
-    if foundMethodDetail:
-      m = reMethodDetailAnchor.search(line) or reJ13MethodDetailAnchor.search(line)
-      if m is not None:
-        lastMethodAnchor = m.group(1)
-        continue
-      isOverrides = '>Overrides:<' in line or '>Specified by:<' in line
-      #print('check for removing @overridden method: %s; %s; %s' % (lastMethodAnchor, isOverrides, missing))
-      if isOverrides and ('Methods', lastMethodAnchor) in missing:
-        #print('removing @overridden method: %s' % lastMethodAnchor)
-        missing.remove(('Methods', lastMethodAnchor))
-
-    m = reCaption.search(line)
-    if m is not None:
-      lastCaption = m.group(1)
-      #print('    caption %s' % lastCaption)
-    else:
-      m = reJ8Caption.search(line)
-      if m is not None:
-        lastCaption = m.group(1)
-        if not lastCaption.endswith('s'):
-          lastCaption += 's'
-        #print('    caption %s' % lastCaption)
-
-    # Try to find the item in question (method/member name):
-    for matcher in (reTDLastNested, # nested classes
-                    reMethod, # methods etc.
-                    reColOne, # ctors etc.
-                    reMemberNameLink, # java 8
-                    reNestedClassMemberNameLink, # java 8, nested class
-                    reMemberNameOneLink): # java 8 ctors
-      m = matcher.search(line)
-      if m is not None:
-        lastItem = m.group(1)
-        #print('  found item %s; inThing=%s' % (lastItem, inThing))
-        break
-
-    lineLower = line.strip().lower()
-
-    if lineLower.find('<tr class="') != -1 or lineLower.find('<tr id="') != -1:
-      inThing = True
-      hasDesc = False
-      continue
-
-    if inThing:
-      if lineLower.find('</tr>') != -1:
-        #print('  end item %s; hasDesc %s' % (lastItem, hasDesc))
-        if not hasDesc:
-          if lastItem is None:
-            raise RuntimeError('failed to locate javadoc item in %s, line %d? last line: %s' % (fullPath, lineCount, line.rstrip()))
-          missing.append((lastCaption, unEscapeURL(lastItem)))
-          #print('    add missing; now %d: %s' % (len(missing), str(missing)))
-        inThing = False
-        continue
-      else:
-        if line.find('<div class="block">') != -1:
-          desc = []
-        if desc is not None:
-          desc.append(line)
-          if line.find('</div>') != -1:
-            desc = ''.join(desc)
-
-            try:
-              verifyHTML(desc)
-            except RuntimeError as e:
-              broken.append((lastCaption, lastItem, str(e)))
-              #print('FAIL: %s: %s: %s: %s' % (lastCaption, lastItem, e, desc))
-                            
-            desc = desc.replace('<div class="block">', '')
-            desc = desc.replace('</div>', '')
-            desc = desc.strip()
-            hasDesc = len(desc) > 0
-            #print('   thing %s: %s' % (lastItem, desc))
-
-            desc = None
-  f.close()
-  if len(missing) > 0 or len(broken) > 0:
-    print()
-    print(fullPath)
-    for (caption, item) in missing:
-      print('  missing %s: %s' % (caption, item))
-    for (caption, item, why) in broken:
-      print('  broken HTML: %s: %s: %s' % (caption, item, why))
-    return True
-  else:
-    return False
-  
-def checkSummary(fullPath):
-  printed = False
-  f = open(fullPath, encoding='UTF-8')
-  anyMissing = False
-  sawPackage = False
-  desc = []
-  lastHREF = None
-  for line in f.readlines():
-    lineLower = line.strip().lower()
-    if desc is not None:
-      # TODO: also detect missing description in overview-summary
-      if lineLower.startswith('package ') or lineLower.startswith('<h1 title="package" '):
-        sawPackage = True
-      elif sawPackage:
-        if lineLower.startswith('<table ') or lineLower.startswith('<b>see: ') or lineLower.startswith('<p>see:') or lineLower.startswith('</main>'):
-          desc = ' '.join(desc)
-          desc = reMarkup.sub(' ', desc)
-          desc = desc.strip()
-          if desc == '':
-            if not printed:
-              print()
-              print(fullPath)
-              printed = True
-            print('  no package description (missing package.html in src?)')
-            anyMissing = True
-          desc = None
-        else:
-          desc.append(lineLower)
-      
-    if lineLower in ('<td>&nbsp;</td>', '<td></td>', '<td class="collast">&nbsp;</td>'):
-      if not printed:
-        print()
-        print(fullPath)
-        printed = True
-      print('  missing description: %s' % unescapeHTML(lastHREF))
-      anyMissing = True
-    elif lineLower.find('licensed to the apache software foundation') != -1 or lineLower.find('copyright 2004 the apache software foundation') != -1:
-      if not printed:
-        print()
-        print(fullPath)
-        printed = True
-      print('  license-is-javadoc: %s' % unescapeHTML(lastHREF))
-      anyMissing = True
-    m = reHREF.search(line)
-    if m is not None:
-      lastHREF = m.group(1)
-  if desc is not None and fullPath.find('/overview-summary.html') == -1:
-    raise RuntimeError('BUG: failed to locate description in %s' % fullPath)
-  f.close()
-  return anyMissing
-
-def unEscapeURL(s):
-  # Not exhaustive!!
-  s = s.replace('%20', ' ')
-  s = s.replace('%5B', '[')
-  s = s.replace('%5D', ']')
-  return s
-
-def unescapeHTML(s):
-  s = s.replace('&lt;', '<')
-  s = s.replace('&gt;', '>')
-  s = s.replace('&amp;', '&')
-  return s
-
-def checkPackageSummaries(root, level='class'):
-  """
-  Just checks for blank summary lines in package-summary.html; returns
-  True if there are problems.
-  """
-
-  if level != 'class' and level != 'package' and level != 'method' and level != 'none':
-    print('unsupported level: %s, must be "class" or "package" or "method" or "none"' % level)
-    sys.exit(1)
-  
-  #for dirPath, dirNames, fileNames in os.walk('%s/lucene/build/docs/api' % root):
-
-  if False:
-    os.chdir(root)
-    print()
-    print('Run "ant javadocs" > javadocs.log...')
-    if os.system('ant javadocs > javadocs.log 2>&1'):
-      print('  FAILED')
-      sys.exit(1)
-    
-  anyMissing = False
-  if not os.path.isdir(root):
-    checkClassSummaries(root)
-    checkClassDetails(root)
-    sys.exit(0)
-    
-  for dirPath, dirNames, fileNames in os.walk(root):
-
-    if dirPath.find('/all/') != -1:
-      # These are dups (this is a bit risk, eg, root IS this /all/ directory..)
-      continue
-
-    if 'package-summary.html' in fileNames:
-      if (level == 'class' or level == 'method') and checkSummary('%s/package-summary.html' % dirPath):
-        anyMissing = True
-      for fileName in fileNames:
-        fullPath = '%s/%s' % (dirPath, fileName)
-        if not fileName.startswith('package-') and fileName.endswith('.html') and os.path.isfile(fullPath):
-          if level == 'method':
-            if checkClassSummaries(fullPath):
-              anyMissing = True
-          # always look for broken html, regardless of level supplied
-          if checkClassDetails(fullPath):
-            anyMissing = True
-              
-    if 'overview-summary.html' in fileNames:        
-      if level != 'none' and checkSummary('%s/overview-summary.html' % dirPath):
-        anyMissing = True
-
-  return anyMissing
-
-if __name__ == '__main__':
-  if len(sys.argv) < 2 or len(sys.argv) > 3:
-    print('usage: %s <dir> [none|package|class|method]' % sys.argv[0])
-    sys.exit(1)
-  if len(sys.argv) == 2:
-    level = 'class'
-  else:
-    level = sys.argv[2]
-  if checkPackageSummaries(sys.argv[1], level):
-    print()
-    print('Missing javadocs were found!')
-    sys.exit(1)
-  sys.exit(0)
diff --git a/dev-tools/scripts/smokeTestRelease.py b/dev-tools/scripts/smokeTestRelease.py
index e2d336d..867e452 100755
--- a/dev-tools/scripts/smokeTestRelease.py
+++ b/dev-tools/scripts/smokeTestRelease.py
@@ -39,7 +39,6 @@ from collections import defaultdict
 from collections import namedtuple
 from scriptutil import download
 
-import checkJavaDocs
 import checkJavadocLinks
 
 # This tool expects to find /lucene and /solr off the base URL.  You
@@ -667,7 +666,7 @@ def verifyUnpacked(java, project, artifact, unpackPath, gitRevision, version, te
 
       print('    generate javadocs w/ Java 11...')
       java.run_java11('ant javadocs', '%s/javadocs.log' % unpackPath)
-      checkJavadocpathFull('%s/build/docs' % unpackPath)
+      checkBrokenLinks('%s/build/docs' % unpackPath)
 
       if java.run_java12:
         print("    run tests w/ Java 12 and testArgs='%s'..." % testArgs)
@@ -677,7 +676,7 @@ def verifyUnpacked(java, project, artifact, unpackPath, gitRevision, version, te
 
         #print('    generate javadocs w/ Java 12...')
         #java.run_java12('ant javadocs', '%s/javadocs.log' % unpackPath)
-        #checkJavadocpathFull('%s/build/docs' % unpackPath)
+        #checkBrokenLinks('%s/build/docs' % unpackPath)
 
     else:
       os.chdir('solr')
@@ -688,7 +687,7 @@ def verifyUnpacked(java, project, artifact, unpackPath, gitRevision, version, te
       # test javadocs
       print('    generate javadocs w/ Java 11...')
       java.run_java11('ant clean javadocs', '%s/javadocs.log' % unpackPath)
-      checkJavadocpathFull('%s/solr/build/docs' % unpackPath, False)
+      checkBrokenLinks('%s/solr/build/docs')
 
       print('    test solr example w/ Java 11...')
       java.run_java11('ant clean server', '%s/antexample.log' % unpackPath)
@@ -700,7 +699,7 @@ def verifyUnpacked(java, project, artifact, unpackPath, gitRevision, version, te
 
         #print('    generate javadocs w/ Java 12...')
         #java.run_java12('ant clean javadocs', '%s/javadocs.log' % unpackPath)
-        #checkJavadocpathFull('%s/solr/build/docs' % unpackPath, False)
+        #checkBrokenLinks('%s/solr/build/docs' % unpackPath)
 
         print('    test solr example w/ Java 12...')
         java.run_java12('ant clean server', '%s/antexample.log' % unpackPath)
@@ -719,9 +718,6 @@ def verifyUnpacked(java, project, artifact, unpackPath, gitRevision, version, te
       if java.run_java12:
         testDemo(java.run_java12, isSrc, version, '12')
 
-      print('    check Lucene\'s javadoc JAR')
-      checkJavadocpath('%s/docs' % unpackPath)
-
     else:
       print('    copying unpacked distribution for Java 11 ...')
       java11UnpackPath = '%s-java11' % unpackPath
@@ -865,26 +861,8 @@ def testSolrExample(unpackPath, javaPath, isSrc):
   else:
     os.chdir(unpackPath)
     
-# the weaker check: we can use this on java6 for some checks,
-# but its generated HTML is hopelessly broken so we cannot run
-# the link checking that checkJavadocpathFull does.
-def checkJavadocpath(path, failOnMissing=True):
-  # check for level='package'
-  # we fail here if its screwed up
-  if failOnMissing and checkJavaDocs.checkPackageSummaries(path, 'package'):
-    raise RuntimeError('missing javadocs package summaries!')
-    
-  # now check for level='class'
-  if checkJavaDocs.checkPackageSummaries(path):
-    # disabled: RM cannot fix all this, see LUCENE-3887
-    # raise RuntimeError('javadoc problems')
-    print('\n***WARNING***: javadocs want to fail!\n')
-
-# full checks
-def checkJavadocpathFull(path, failOnMissing=True):
-  # check for missing, etc
-  checkJavadocpath(path, failOnMissing)
-
+# check for broken links
+def checkBrokenLinks(path):
   # also validate html/check for broken links
   if checkJavadocLinks.checkAll(path):
     raise RuntimeError('broken javadocs links found!')
diff --git a/gradle/documentation/render-javadoc.gradle b/gradle/documentation/render-javadoc.gradle
index 0a9ddb2..b71b011 100644
--- a/gradle/documentation/render-javadoc.gradle
+++ b/gradle/documentation/render-javadoc.gradle
@@ -1,4 +1,5 @@
 import javax.annotation.Nullable
+import org.apache.lucene.gradle.MissingDoclet
 
 /*
  * Licensed to the Apache Software Foundation (ASF) under one or more
@@ -205,6 +206,16 @@ class RenderJavadocTask extends DefaultTask {
     opts << [ '-tag', 'lucene.experimental:a:WARNING: This API is experimental and might change in incompatible ways in the next release.' ]
     opts << [ '-tag', 'lucene.internal:a:NOTE: This API is for internal purposes only and might change in incompatible ways in the next release.' ]
     opts << [ '-tag', "lucene.spi:t:SPI Name (case-insensitive: if the name is 'htmlStrip', 'htmlstrip' can be used when looking up the service)." ]
+    opts << [ '-doclet', MissingDoclet.class.getName() ]
+    opts << [ '-docletpath', project.rootProject.file("buildSrc/build/classes/java/main") ]
+    opts << [ '--missing-level', project.javadocMissingLevel ]
+    if (project.javadocMissingIgnore.size > 0) {
+      opts << [ '--missing-ignore', String.join(',', project.javadocMissingIgnore) ]
+    }
+    if (project.javadocMissingMethod.size > 0) {
+      opts << [ '--missing-method', String.join(',', project.javadocMissingMethod) ]
+    }
+    opts << [ '-quiet' ]
 
     def allOfflineLinks = [:]
     allOfflineLinks.putAll(offlineLinks)
diff --git a/gradle/validation/missing-docs-check.gradle b/gradle/validation/missing-docs-check.gradle
deleted file mode 100644
index 2b24e79..0000000
--- a/gradle/validation/missing-docs-check.gradle
+++ /dev/null
@@ -1,127 +0,0 @@
-/*
- * 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.
- */
-
-allprojects {
-  plugins.withType(JavaPlugin) {
-    // Too many classes to fix overall to just enable the above to be level="method" right now,
-    // but we can prevent the modules that don't have problems from getting any worse.
-    def methodLevelProjects = [
-        ':lucene:analysis:icu',
-        ':lucene:analysis:morfologik',
-        ':lucene:analysis:phonetic',
-        ':lucene:analysis:stempel',
-        ':lucene:classification',
-        ':lucene:demo',
-        ':lucene:expressions',
-        ':lucene:facet',
-        ':lucene:join',
-        ':lucene:memory',
-        ':lucene:suggest',
-        ':lucene:spatial3d',
-    ]
-
-    task checkMissingDocsDefault(type: CheckMissingDocsTask, dependsOn: 'renderJavadoc') {
-      dirs += [ project.javadoc.destinationDir ]
-
-      onlyIf {
-        def maxSupported = JavaVersion.VERSION_14
-        def runtimeVersion = runtimeJava.javaVersion
-        if (runtimeVersion > JavaVersion.VERSION_14) {
-          logger.warn("Skipping task because runtime Java version ${runtimeVersion} is " +
-              "higher than Java ${maxSupported}.")
-          return false
-        } else {
-          return true
-        }
-      }
-
-      // TODO: add missing docs for all classes and bump this to level=class
-      if (project.path.startsWith(":solr")) {
-        level = 'package'
-      } else if (project.path in methodLevelProjects) {
-        level = 'method'
-      } else {
-        level = 'class'
-      }
-    }
-
-    task checkMissingDocs() {
-      group 'Verification'
-      description 'Check missing Javadocs'
-
-      dependsOn checkMissingDocsDefault
-    }
-  }
-}
-
-configure(project(':lucene:core')) {
-  // Defer until java plugin has been applied, otherwise we can't resolve project.javadoc.
-  plugins.withType(JavaPlugin) {
-    task checkMissingDocsMethod(type: CheckMissingDocsTask, dependsOn: 'renderJavadoc') {
-      level = 'method'
-    }
-
-    // Too much to fix core/ for now, but enforce full javadocs for key packages.
-    checkMissingDocsMethod.dirs = [
-        "org/apache/lucene/util/automaton",
-        "org/apache/lucene/analysis",
-        "org/apache/lucene/document",
-        "org/apache/lucene/search/similarities",
-        "org/apache/lucene/index",
-        "org/apache/lucene/codecs"
-    ].collect { path -> file("${project.javadoc.destinationDir}/${path}") }
-    checkMissingDocs {
-      dependsOn checkMissingDocsMethod
-    }
-  }
-}
-
-class CheckMissingDocsTask extends DefaultTask {
-  @Input
-  List<File> dirs = []
-
-  @Input
-  String level = "none"
-
-  def checkMissingJavadocs(File dir, String level) {
-    def output = new ByteArrayOutputStream()
-    def result = project.exec {
-      executable project.externalToolExecutables["python3"]
-      ignoreExitValue = true
-      standardOutput = output
-      errorOutput = output
-      args = [
-        "-B",
-        project.rootProject.file("dev-tools/scripts/checkJavaDocs.py").absolutePath,
-        dir.absolutePath,
-        level
-      ]
-    }
-
-    if (result.getExitValue() != 0) {
-      throw new GradleException("Javadoc verification failed:\n${output}")
-    }
-  }
-
-  @TaskAction
-  def lint() {
-    dirs.findAll { it.exists() }.each { dir ->
-      project.logger.info("Checking for missing docs... (dir=${dir}, level=${level})")
-      checkMissingJavadocs(dir, level)
-    }
-  }
-}
diff --git a/lucene/analysis/common/build.gradle b/lucene/analysis/common/build.gradle
index ff5857b..0fd804d 100644
--- a/lucene/analysis/common/build.gradle
+++ b/lucene/analysis/common/build.gradle
@@ -24,3 +24,9 @@ dependencies {
   testImplementation project(':lucene:test-framework')
 }
 
+ext {
+  // TODO: fix missing javadocs
+  javadocMissingLevel = "class"
+  // TODO: clean up split packages
+  javadocMissingIgnore = [ "org.apache.lucene.analysis.standard" ]
+}
diff --git a/lucene/analysis/common/src/java/org/apache/lucene/analysis/compound/hyphenation/TernaryTree.java b/lucene/analysis/common/src/java/org/apache/lucene/analysis/compound/hyphenation/TernaryTree.java
index a331d2a..6f9d72f 100644
--- a/lucene/analysis/common/src/java/org/apache/lucene/analysis/compound/hyphenation/TernaryTree.java
+++ b/lucene/analysis/common/src/java/org/apache/lucene/analysis/compound/hyphenation/TernaryTree.java
@@ -456,7 +456,7 @@ public class TernaryTree implements Cloneable {
     return new Iterator();
   }
 
-  public class Iterator implements Enumeration<String> {
+  private class Iterator implements Enumeration<String> {
 
     /**
      * current node index
diff --git a/lucene/analysis/icu/build.gradle b/lucene/analysis/icu/build.gradle
index 2eab963..6bc24e0 100644
--- a/lucene/analysis/icu/build.gradle
+++ b/lucene/analysis/icu/build.gradle
@@ -27,3 +27,12 @@ dependencies {
 
   testImplementation project(':lucene:test-framework')
 }
+
+ext {
+  // TODO: clean up split packages
+  javadocMissingIgnore = [
+    "org.apache.lucene.collation",
+    "org.apache.lucene.collation.tokenattributes"
+  ]
+}
+
diff --git a/lucene/analysis/kuromoji/build.gradle b/lucene/analysis/kuromoji/build.gradle
index af2bfa5..9c4f4c8 100644
--- a/lucene/analysis/kuromoji/build.gradle
+++ b/lucene/analysis/kuromoji/build.gradle
@@ -25,3 +25,8 @@ dependencies {
 
   testImplementation project(':lucene:test-framework')
 }
+
+ext {
+  // TODO: fix missing javadocs
+  javadocMissingLevel = "class"
+}
diff --git a/lucene/analysis/nori/build.gradle b/lucene/analysis/nori/build.gradle
index 60fe372..276d997 100644
--- a/lucene/analysis/nori/build.gradle
+++ b/lucene/analysis/nori/build.gradle
@@ -25,3 +25,9 @@ dependencies {
   
   testImplementation project(':lucene:test-framework')
 }
+
+ext {
+  // TODO: fix missing javadocs
+  javadocMissingLevel = "class"
+}
+
diff --git a/lucene/analysis/opennlp/build.gradle b/lucene/analysis/opennlp/build.gradle
index c4672c0..b7c9d01 100644
--- a/lucene/analysis/opennlp/build.gradle
+++ b/lucene/analysis/opennlp/build.gradle
@@ -26,3 +26,8 @@ dependencies {
 
   testImplementation project(':lucene:test-framework')
 }
+
+ext {
+  // TODO: fix missing javadocs
+  javadocMissingLevel = "class"
+}
diff --git a/lucene/analysis/smartcn/build.gradle b/lucene/analysis/smartcn/build.gradle
index 8d5eeb1..9f96e00 100644
--- a/lucene/analysis/smartcn/build.gradle
+++ b/lucene/analysis/smartcn/build.gradle
@@ -25,3 +25,8 @@ dependencies {
 
   testImplementation project(':lucene:test-framework')
 } 
+
+ext {
+  // TODO: fix missing javadocs
+  javadocMissingLevel = "class"
+}
diff --git a/lucene/backward-codecs/build.gradle b/lucene/backward-codecs/build.gradle
index aa47302..730d092 100644
--- a/lucene/backward-codecs/build.gradle
+++ b/lucene/backward-codecs/build.gradle
@@ -24,3 +24,16 @@ dependencies {
   api project(':lucene:core')
   testImplementation project(':lucene:test-framework')
 }
+
+ext {
+  // TODO: clean up split packages
+  javadocMissingIgnore = [
+    "org.apache.lucene.codecs",
+    "org.apache.lucene.codecs.lucene50",
+    "org.apache.lucene.codecs.lucene60",
+    "org.apache.lucene.codecs.lucene80",
+    "org.apache.lucene.codecs.lucene84",
+    "org.apache.lucene.codecs.lucene86"
+  ]
+}
+
diff --git a/lucene/backward-codecs/src/java/org/apache/lucene/codecs/Placeholder.java b/lucene/backward-codecs/src/java/org/apache/lucene/codecs/Placeholder.java
index f359369..d615da3 100644
--- a/lucene/backward-codecs/src/java/org/apache/lucene/codecs/Placeholder.java
+++ b/lucene/backward-codecs/src/java/org/apache/lucene/codecs/Placeholder.java
@@ -18,5 +18,6 @@ package org.apache.lucene.codecs;
 
 /** Remove this file when adding back compat codecs */
 public class Placeholder {
-  
+  // no instance
+  private Placeholder() {}
 }
diff --git a/lucene/benchmark/build.gradle b/lucene/benchmark/build.gradle
index d7c2457..2fded4c 100644
--- a/lucene/benchmark/build.gradle
+++ b/lucene/benchmark/build.gradle
@@ -44,6 +44,11 @@ dependencies {
   testImplementation project(':lucene:test-framework')
 }
 
+ext {
+  // TODO: fix missing javadocs
+  javadocMissingLevel = "class"
+}
+
 def tempDir = file("temp")
 def workDir = file("work")
 
@@ -155,4 +160,4 @@ task getReuters(type: Download) {
     }
   }
   outputs.dir finalPath
-}
\ No newline at end of file
+}
diff --git a/lucene/classification/src/java/org/apache/lucene/classification/CachingNaiveBayesClassifier.java b/lucene/classification/src/java/org/apache/lucene/classification/CachingNaiveBayesClassifier.java
index b069e19..700e5f7 100644
--- a/lucene/classification/src/java/org/apache/lucene/classification/CachingNaiveBayesClassifier.java
+++ b/lucene/classification/src/java/org/apache/lucene/classification/CachingNaiveBayesClassifier.java
@@ -77,6 +77,7 @@ public class CachingNaiveBayesClassifier extends SimpleNaiveBayesClassifier {
   }
 
 
+  /** Transforms values into a range between 0 and 1 */
   protected List<ClassificationResult<BytesRef>> assignClassNormalizedList(String inputDocument) throws IOException {
     String[] tokenizedText = tokenize(inputDocument);
 
diff --git a/lucene/codecs/build.gradle b/lucene/codecs/build.gradle
index ad26aae..6f6a2d5 100644
--- a/lucene/codecs/build.gradle
+++ b/lucene/codecs/build.gradle
@@ -23,3 +23,8 @@ dependencies {
     implementation project(':lucene:core')
     testImplementation project(':lucene:test-framework')
 }
+
+ext {
+  // TODO: fix missing javadocs
+  javadocMissingLevel = "class"
+}
diff --git a/lucene/codecs/src/java/org/apache/lucene/codecs/uniformsplit/sharedterms/STUniformSplitTermsWriter.java b/lucene/codecs/src/java/org/apache/lucene/codecs/uniformsplit/sharedterms/STUniformSplitTermsWriter.java
index ca15d6a..428e4f9 100755
--- a/lucene/codecs/src/java/org/apache/lucene/codecs/uniformsplit/sharedterms/STUniformSplitTermsWriter.java
+++ b/lucene/codecs/src/java/org/apache/lucene/codecs/uniformsplit/sharedterms/STUniformSplitTermsWriter.java
@@ -335,7 +335,7 @@ public class STUniformSplitTermsWriter extends UniformSplitTermsWriter {
     Collection<FieldMetadata> writeSharedTerms(STBlockWriter blockWriter, IndexDictionary.Builder dictionaryBuilder) throws IOException;
   }
 
-  protected class SegmentPostings {
+  final class SegmentPostings {
 
     final int segmentIndex;
     final BlockTermState termState;
diff --git a/lucene/core/build.gradle b/lucene/core/build.gradle
index 989c57f..71e1fe5 100644
--- a/lucene/core/build.gradle
+++ b/lucene/core/build.gradle
@@ -23,3 +23,17 @@ dependencies {
   testImplementation project(':lucene:codecs')
   testImplementation project(':lucene:test-framework')
 }
+
+ext {
+  // TODO: fix missing javadocs
+  javadocMissingLevel = "class"
+  // some packages are fixed already
+  javadocMissingMethod = [
+    "org.apache.lucene.util.automaton",
+    "org.apache.lucene.analysis",
+    "org.apache.lucene.document",
+    "org.apache.lucene.search.similarities",
+    "org.apache.lucene.index",
+    "org.apache.lucene.codecs"
+  ]
+}
diff --git a/lucene/core/src/java/org/apache/lucene/index/BaseTermsEnum.java b/lucene/core/src/java/org/apache/lucene/index/BaseTermsEnum.java
index 0b0d094..77416c2 100644
--- a/lucene/core/src/java/org/apache/lucene/index/BaseTermsEnum.java
+++ b/lucene/core/src/java/org/apache/lucene/index/BaseTermsEnum.java
@@ -66,6 +66,7 @@ public abstract class BaseTermsEnum extends TermsEnum {
     }
   }
 
+  @Override
   public AttributeSource attributes() {
     if (atts == null) {
       atts = new AttributeSource();
diff --git a/lucene/core/src/java/org/apache/lucene/index/FilterSortedNumericDocValues.java b/lucene/core/src/java/org/apache/lucene/index/FilterSortedNumericDocValues.java
index 1597eb0..2db66a5 100644
--- a/lucene/core/src/java/org/apache/lucene/index/FilterSortedNumericDocValues.java
+++ b/lucene/core/src/java/org/apache/lucene/index/FilterSortedNumericDocValues.java
@@ -33,30 +33,37 @@ public abstract class FilterSortedNumericDocValues extends SortedNumericDocValue
     this.in = in;
   }
 
+  @Override
   public boolean advanceExact(int target) throws IOException {
     return in.advanceExact(target);
   }
 
+  @Override
   public long nextValue() throws IOException {
     return in.nextValue();
   }
 
+  @Override
   public int docValueCount() {
     return in.docValueCount();
   }
 
+  @Override
   public int docID() {
     return in.docID();
   }
 
+  @Override
   public int nextDoc() throws IOException {
     return in.nextDoc();
   }
 
+  @Override
   public int advance(int target) throws IOException {
     return in.advance(target);
   }
 
+  @Override
   public long cost() {
     return in.cost();
   }
diff --git a/lucene/core/src/java/org/apache/lucene/index/FilterSortedSetDocValues.java b/lucene/core/src/java/org/apache/lucene/index/FilterSortedSetDocValues.java
index b403460..fcee9aa 100644
--- a/lucene/core/src/java/org/apache/lucene/index/FilterSortedSetDocValues.java
+++ b/lucene/core/src/java/org/apache/lucene/index/FilterSortedSetDocValues.java
@@ -36,46 +36,57 @@ public class FilterSortedSetDocValues extends SortedSetDocValues {
     this.in = in;
   }
 
+  @Override
   public boolean advanceExact(int target) throws IOException {
     return in.advanceExact(target);
   }
 
+  @Override
   public long nextOrd() throws IOException {
     return in.nextOrd();
   }
 
+  @Override
   public BytesRef lookupOrd(long ord) throws IOException {
     return in.lookupOrd(ord);
   }
 
+  @Override
   public long getValueCount() {
     return in.getValueCount();
   }
 
+  @Override
   public long lookupTerm(BytesRef key) throws IOException {
     return in.lookupTerm(key);
   }
 
+  @Override
   public TermsEnum termsEnum() throws IOException {
     return in.termsEnum();
   }
 
+  @Override
   public TermsEnum intersect(CompiledAutomaton automaton) throws IOException {
     return in.intersect(automaton);
   }
 
+  @Override
   public int docID() {
     return in.docID();
   }
 
+  @Override
   public int nextDoc() throws IOException {
     return in.nextDoc();
   }
 
+  @Override
   public int advance(int target) throws IOException {
     return in.advance(target);
   }
 
+  @Override
   public long cost() {
     return in.cost();
   }
diff --git a/lucene/core/src/java/org/apache/lucene/search/MultiCollectorManager.java b/lucene/core/src/java/org/apache/lucene/search/MultiCollectorManager.java
index 6e73c54..bb6062b 100644
--- a/lucene/core/src/java/org/apache/lucene/search/MultiCollectorManager.java
+++ b/lucene/core/src/java/org/apache/lucene/search/MultiCollectorManager.java
@@ -58,6 +58,7 @@ public class MultiCollectorManager implements CollectorManager<MultiCollectorMan
     return results;
   }
 
+  /** Wraps multiple collectors for processing */
   public class Collectors implements Collector {
 
     private final Collector[] collectors;
@@ -86,7 +87,7 @@ public class MultiCollectorManager implements CollectorManager<MultiCollectorMan
       return scoreMode;
     }
 
-    public class LeafCollectors implements LeafCollector {
+    private class LeafCollectors implements LeafCollector {
 
       private final LeafCollector[] leafCollectors;
 
diff --git a/lucene/core/src/java/org/apache/lucene/search/similarities/AxiomaticF1EXP.java b/lucene/core/src/java/org/apache/lucene/search/similarities/AxiomaticF1EXP.java
index ca5c42b..be179d1 100644
--- a/lucene/core/src/java/org/apache/lucene/search/similarities/AxiomaticF1EXP.java
+++ b/lucene/core/src/java/org/apache/lucene/search/similarities/AxiomaticF1EXP.java
@@ -114,11 +114,13 @@ public class AxiomaticF1EXP extends Axiomatic {
             "dl, length of field"));
   };
 
+  @Override
   protected Explanation tflnExplain(BasicStats stats, double freq, double docLen){
     return Explanation.match((float) tfln(stats, freq, docLen),
         "tfln, mixed term frequency and document length, equals to 1");
   };
 
+  @Override
   protected Explanation idfExplain(BasicStats stats, double freq, double docLen){
     return Explanation.match((float) idf(stats, freq, docLen),
         "idf, inverted document frequency computed as " +
diff --git a/lucene/core/src/java/org/apache/lucene/search/similarities/AxiomaticF1LOG.java b/lucene/core/src/java/org/apache/lucene/search/similarities/AxiomaticF1LOG.java
index 6ef3587..6096728 100644
--- a/lucene/core/src/java/org/apache/lucene/search/similarities/AxiomaticF1LOG.java
+++ b/lucene/core/src/java/org/apache/lucene/search/similarities/AxiomaticF1LOG.java
@@ -106,11 +106,13 @@ public class AxiomaticF1LOG extends Axiomatic {
             "dl, length of field"));
   };
 
+  @Override
   protected Explanation tflnExplain(BasicStats stats, double freq, double docLen){
     return Explanation.match((float) tfln(stats, freq, docLen),
         "tfln, mixed term frequency and document length, equals to 1");
   };
 
+  @Override
   protected Explanation idfExplain(BasicStats stats, double freq, double docLen){
     return Explanation.match((float) idf(stats, freq, docLen),
         "idf, inverted document frequency computed as log((N + 1) / n) from:",
diff --git a/lucene/core/src/java/org/apache/lucene/search/similarities/AxiomaticF2EXP.java b/lucene/core/src/java/org/apache/lucene/search/similarities/AxiomaticF2EXP.java
index bd28048..276eb47 100644
--- a/lucene/core/src/java/org/apache/lucene/search/similarities/AxiomaticF2EXP.java
+++ b/lucene/core/src/java/org/apache/lucene/search/similarities/AxiomaticF2EXP.java
@@ -106,6 +106,7 @@ public class AxiomaticF2EXP extends Axiomatic {
         "ln, document length, equals to 1");
   };
 
+  @Override
   protected Explanation tflnExplain(BasicStats stats, double freq, double docLen){
     return Explanation.match((float) tfln(stats, freq, docLen),
         "tfln, mixed term frequency and document length, " +
@@ -118,6 +119,7 @@ public class AxiomaticF2EXP extends Axiomatic {
             "avgdl, average length of field across all documents"));
   };
 
+  @Override
   protected Explanation idfExplain(BasicStats stats, double freq, double docLen){
     return Explanation.match((float) idf(stats, freq, docLen),
         "idf, inverted document frequency computed as " +
diff --git a/lucene/core/src/java/org/apache/lucene/search/similarities/AxiomaticF2LOG.java b/lucene/core/src/java/org/apache/lucene/search/similarities/AxiomaticF2LOG.java
index 4780d1e..05718f4 100644
--- a/lucene/core/src/java/org/apache/lucene/search/similarities/AxiomaticF2LOG.java
+++ b/lucene/core/src/java/org/apache/lucene/search/similarities/AxiomaticF2LOG.java
@@ -98,6 +98,7 @@ public class AxiomaticF2LOG extends Axiomatic {
         "ln, document length, equals to 1");
   };
 
+  @Override
   protected Explanation tflnExplain(BasicStats stats, double freq, double docLen){
     return Explanation.match((float) tfln(stats, freq, docLen),
         "tfln, mixed term frequency and document length, " +
@@ -110,6 +111,7 @@ public class AxiomaticF2LOG extends Axiomatic {
             "avgdl, average length of field across all documents"));
   };
 
+  @Override
   protected Explanation idfExplain(BasicStats stats, double freq, double docLen){
     return Explanation.match((float) idf(stats, freq, docLen),
         "idf, inverted document frequency computed as log((N + 1) / n) from:",
diff --git a/lucene/core/src/java/org/apache/lucene/search/similarities/AxiomaticF3EXP.java b/lucene/core/src/java/org/apache/lucene/search/similarities/AxiomaticF3EXP.java
index a54c754..6382481 100644
--- a/lucene/core/src/java/org/apache/lucene/search/similarities/AxiomaticF3EXP.java
+++ b/lucene/core/src/java/org/apache/lucene/search/similarities/AxiomaticF3EXP.java
@@ -108,11 +108,13 @@ public class AxiomaticF3EXP extends Axiomatic {
         "ln, document length, equals to 1");
   };
 
+  @Override
   protected Explanation tflnExplain(BasicStats stats, double freq, double docLen){
     return Explanation.match((float) tfln(stats, freq, docLen),
         "tfln, mixed term frequency and document length, equals to 1");
   };
 
+  @Override
   protected Explanation idfExplain(BasicStats stats, double freq, double docLen){
     return Explanation.match((float) idf(stats, freq, docLen),
         "idf, inverted document frequency computed as " +
diff --git a/lucene/core/src/java/org/apache/lucene/search/similarities/AxiomaticF3LOG.java b/lucene/core/src/java/org/apache/lucene/search/similarities/AxiomaticF3LOG.java
index 194b70a..08ec1cc 100644
--- a/lucene/core/src/java/org/apache/lucene/search/similarities/AxiomaticF3LOG.java
+++ b/lucene/core/src/java/org/apache/lucene/search/similarities/AxiomaticF3LOG.java
@@ -97,11 +97,13 @@ public class AxiomaticF3LOG extends Axiomatic {
         "ln, document length, equals to 1");
   };
 
+  @Override
   protected Explanation tflnExplain(BasicStats stats, double freq, double docLen){
     return Explanation.match((float) tfln(stats, freq, docLen),
         "tfln, mixed term frequency and document length, equals to 1");
   };
 
+  @Override
   protected Explanation idfExplain(BasicStats stats, double freq, double docLen){
     return Explanation.match((float) idf(stats, freq, docLen),
         "idf, inverted document frequency computed as log((N + 1) / n) from:",
diff --git a/lucene/core/src/java/org/apache/lucene/search/similarities/LambdaTTF.java b/lucene/core/src/java/org/apache/lucene/search/similarities/LambdaTTF.java
index 72eae4c..b13bbd1 100644
--- a/lucene/core/src/java/org/apache/lucene/search/similarities/LambdaTTF.java
+++ b/lucene/core/src/java/org/apache/lucene/search/similarities/LambdaTTF.java
@@ -38,6 +38,7 @@ public class LambdaTTF extends Lambda {
     return lambda;
   }
 
+  @Override
   public final Explanation explain(BasicStats stats) {
     return Explanation.match(
         lambda(stats),
diff --git a/lucene/core/src/java/org/apache/lucene/search/spans/SpanContainingQuery.java b/lucene/core/src/java/org/apache/lucene/search/spans/SpanContainingQuery.java
index 6366299..9bfca32 100644
--- a/lucene/core/src/java/org/apache/lucene/search/spans/SpanContainingQuery.java
+++ b/lucene/core/src/java/org/apache/lucene/search/spans/SpanContainingQuery.java
@@ -51,7 +51,7 @@ public final class SpanContainingQuery extends SpanContainQuery {
                                       bigWeight, littleWeight, boost);
   }
 
-  public class SpanContainingWeight extends SpanContainWeight {
+  private class SpanContainingWeight extends SpanContainWeight {
 
     public SpanContainingWeight(IndexSearcher searcher, Map<Term, TermStates> terms,
                                 SpanWeight bigWeight, SpanWeight littleWeight, float boost) throws IOException {
diff --git a/lucene/core/src/java/org/apache/lucene/search/spans/SpanNearQuery.java b/lucene/core/src/java/org/apache/lucene/search/spans/SpanNearQuery.java
index 2f219bf..3b628d1 100644
--- a/lucene/core/src/java/org/apache/lucene/search/spans/SpanNearQuery.java
+++ b/lucene/core/src/java/org/apache/lucene/search/spans/SpanNearQuery.java
@@ -187,7 +187,7 @@ public class SpanNearQuery extends SpanQuery implements Cloneable {
     return new SpanNearWeight(subWeights, searcher, scoreMode.needsScores() ? getTermStates(subWeights) : null, boost);
   }
 
-  public class SpanNearWeight extends SpanWeight {
+  private class SpanNearWeight extends SpanWeight {
 
     final List<SpanWeight> subWeights;
 
diff --git a/lucene/core/src/java/org/apache/lucene/search/spans/SpanNotQuery.java b/lucene/core/src/java/org/apache/lucene/search/spans/SpanNotQuery.java
index 5d998fe..8c6112a 100644
--- a/lucene/core/src/java/org/apache/lucene/search/spans/SpanNotQuery.java
+++ b/lucene/core/src/java/org/apache/lucene/search/spans/SpanNotQuery.java
@@ -106,7 +106,7 @@ public final class SpanNotQuery extends SpanQuery {
                                   includeWeight, excludeWeight, boost);
   }
 
-  public class SpanNotWeight extends SpanWeight {
+  private class SpanNotWeight extends SpanWeight {
 
     final SpanWeight includeWeight;
     final SpanWeight excludeWeight;
diff --git a/lucene/core/src/java/org/apache/lucene/search/spans/SpanOrQuery.java b/lucene/core/src/java/org/apache/lucene/search/spans/SpanOrQuery.java
index 5f589e1..07e612e 100644
--- a/lucene/core/src/java/org/apache/lucene/search/spans/SpanOrQuery.java
+++ b/lucene/core/src/java/org/apache/lucene/search/spans/SpanOrQuery.java
@@ -136,7 +136,7 @@ public final class SpanOrQuery extends SpanQuery {
     return new SpanOrWeight(searcher, scoreMode.needsScores() ? getTermStates(subWeights) : null, subWeights, boost);
   }
 
-  public class SpanOrWeight extends SpanWeight {
+  private class SpanOrWeight extends SpanWeight {
 
     final List<SpanWeight> subWeights;
 
diff --git a/lucene/core/src/java/org/apache/lucene/search/spans/SpanPositionCheckQuery.java b/lucene/core/src/java/org/apache/lucene/search/spans/SpanPositionCheckQuery.java
index 3e40c64..3dc9a3c 100644
--- a/lucene/core/src/java/org/apache/lucene/search/spans/SpanPositionCheckQuery.java
+++ b/lucene/core/src/java/org/apache/lucene/search/spans/SpanPositionCheckQuery.java
@@ -74,7 +74,7 @@ public abstract class SpanPositionCheckQuery extends SpanQuery implements Clonea
     return new SpanPositionCheckWeight(matchWeight, searcher, scoreMode.needsScores() ? getTermStates(matchWeight) : null, boost);
   }
 
-  public class SpanPositionCheckWeight extends SpanWeight {
+  private class SpanPositionCheckWeight extends SpanWeight {
 
     final SpanWeight matchWeight;
 
diff --git a/lucene/core/src/java/org/apache/lucene/search/spans/SpanTermQuery.java b/lucene/core/src/java/org/apache/lucene/search/spans/SpanTermQuery.java
index c86e7b7..08310b7 100644
--- a/lucene/core/src/java/org/apache/lucene/search/spans/SpanTermQuery.java
+++ b/lucene/core/src/java/org/apache/lucene/search/spans/SpanTermQuery.java
@@ -91,7 +91,7 @@ public class SpanTermQuery extends SpanQuery {
     }
   }
 
-  public class SpanTermWeight extends SpanWeight {
+  private class SpanTermWeight extends SpanWeight {
 
     final TermStates termStates;
 
diff --git a/lucene/core/src/java/org/apache/lucene/search/spans/SpanWithinQuery.java b/lucene/core/src/java/org/apache/lucene/search/spans/SpanWithinQuery.java
index fba85fe..6827886 100644
--- a/lucene/core/src/java/org/apache/lucene/search/spans/SpanWithinQuery.java
+++ b/lucene/core/src/java/org/apache/lucene/search/spans/SpanWithinQuery.java
@@ -52,7 +52,7 @@ public final class SpanWithinQuery extends SpanContainQuery {
                                       bigWeight, littleWeight, boost);
   }
 
-  public class SpanWithinWeight extends SpanContainWeight {
+  private class SpanWithinWeight extends SpanContainWeight {
 
     public SpanWithinWeight(IndexSearcher searcher, Map<Term, TermStates> terms,
                             SpanWeight bigWeight, SpanWeight littleWeight, float boost) throws IOException {
diff --git a/lucene/core/src/java/org/apache/lucene/util/PagedBytes.java b/lucene/core/src/java/org/apache/lucene/util/PagedBytes.java
index e07046c..a1acab5 100644
--- a/lucene/core/src/java/org/apache/lucene/util/PagedBytes.java
+++ b/lucene/core/src/java/org/apache/lucene/util/PagedBytes.java
@@ -282,7 +282,7 @@ public final class PagedBytes implements Accountable {
     return pointer;
   }
 
-  public final class PagedBytesDataInput extends DataInput {
+  private final class PagedBytesDataInput extends DataInput {
     private int currentBlockIndex;
     private int currentBlockUpto;
     private byte[] currentBlock;
@@ -350,7 +350,7 @@ public final class PagedBytes implements Accountable {
     }
   }
 
-  public final class PagedBytesDataOutput extends DataOutput {
+  private final class PagedBytesDataOutput extends DataOutput {
     @Override
     public void writeByte(byte b) {
       if (upto == blockSize) {
diff --git a/lucene/grouping/build.gradle b/lucene/grouping/build.gradle
index b0838e3..490e850 100644
--- a/lucene/grouping/build.gradle
+++ b/lucene/grouping/build.gradle
@@ -26,4 +26,9 @@ dependencies {
   implementation project(':lucene:queries')
 
   testImplementation project(':lucene:test-framework')
-}
\ No newline at end of file
+}
+
+ext {
+  // TODO: fix missing javadocs
+  javadocMissingLevel = "class"
+}
diff --git a/lucene/highlighter/build.gradle b/lucene/highlighter/build.gradle
index 6bd8426..930cb62 100644
--- a/lucene/highlighter/build.gradle
+++ b/lucene/highlighter/build.gradle
@@ -30,3 +30,9 @@ dependencies {
   testImplementation project(':lucene:analysis:common')
   testImplementation project(':lucene:queryparser')
 }
+
+ext {
+  // TODO: fix missing javadocs
+  javadocMissingLevel = "class"
+}
+
diff --git a/lucene/luke/build.gradle b/lucene/luke/build.gradle
index 9b6f47b..71d1d0d 100644
--- a/lucene/luke/build.gradle
+++ b/lucene/luke/build.gradle
@@ -24,6 +24,8 @@ description = 'Luke - Lucene Toolbox'
 
 ext {
   standaloneDistDir = file("$buildDir/${archivesBaseName}-${project.version}")
+  // TODO: fix missing javadocs
+  javadocMissingLevel = "class"
 }
 
 configurations {
diff --git a/lucene/misc/build.gradle b/lucene/misc/build.gradle
index bdf575d..05b6d75 100644
--- a/lucene/misc/build.gradle
+++ b/lucene/misc/build.gradle
@@ -22,4 +22,19 @@ description = 'Index tools and other miscellaneous code'
 dependencies {
   api project(':lucene:core')
   testImplementation project(':lucene:test-framework')
-}
\ No newline at end of file
+}
+
+ext {
+  // TODO: fix missing javadocs
+  javadocMissingLevel = "class"
+  // TODO: clean up split packages
+  javadocMissingIgnore = [
+    "org.apache.lucene.search",
+    "org.apache.lucene.search.similarity",
+    "org.apache.lucene.util",
+    "org.apache.lucene.util.fst",
+    "org.apache.lucene.store",
+    "org.apache.lucene.document",
+    "org.apache.lucene.index"
+  ]
+}
diff --git a/lucene/misc/src/java/org/apache/lucene/document/LazyDocument.java b/lucene/misc/src/java/org/apache/lucene/document/LazyDocument.java
index 7c5690e..1f2cfe8 100644
--- a/lucene/misc/src/java/org/apache/lucene/document/LazyDocument.java
+++ b/lucene/misc/src/java/org/apache/lucene/document/LazyDocument.java
@@ -125,6 +125,7 @@ public class LazyDocument {
 
 
   /** 
+   * Lazy-loaded field
    * @lucene.internal 
    */
   public class LazyField implements IndexableField {
diff --git a/lucene/monitor/build.gradle b/lucene/monitor/build.gradle
index 3dd65a4..f1efb98 100644
--- a/lucene/monitor/build.gradle
+++ b/lucene/monitor/build.gradle
@@ -28,3 +28,8 @@ dependencies {
   testImplementation project(':lucene:queryparser')
   testImplementation project(':lucene:test-framework')
 }
+
+ext {
+  // TODO: fix missing javadocs
+  javadocMissingLevel = "class"
+}
diff --git a/lucene/queries/build.gradle b/lucene/queries/build.gradle
index 64c2bf4..a5cbd99 100644
--- a/lucene/queries/build.gradle
+++ b/lucene/queries/build.gradle
@@ -24,4 +24,9 @@ dependencies {
 
   testImplementation project(':lucene:test-framework')
   testImplementation project(':lucene:expressions')
-}
\ No newline at end of file
+}
+
+ext {
+  // TODO: fix missing javadocs
+  javadocMissingLevel = "class"
+}
diff --git a/lucene/queries/src/java/org/apache/lucene/queries/function/FunctionQuery.java b/lucene/queries/src/java/org/apache/lucene/queries/function/FunctionQuery.java
index 9bd9ccc..480e549 100644
--- a/lucene/queries/src/java/org/apache/lucene/queries/function/FunctionQuery.java
+++ b/lucene/queries/src/java/org/apache/lucene/queries/function/FunctionQuery.java
@@ -53,7 +53,7 @@ public class FunctionQuery extends Query {
     return func;
   }
 
-  protected class FunctionWeight extends Weight {
+  private class FunctionWeight extends Weight {
     protected final IndexSearcher searcher;
     protected final float boost;
     protected final Map<Object, Object> context;
@@ -87,7 +87,7 @@ public class FunctionQuery extends Query {
     visitor.visitLeaf(this);
   }
 
-  protected class AllScorer extends Scorer {
+  private class AllScorer extends Scorer {
     final IndexReader reader;
     final FunctionWeight weight;
     final int maxDoc;
diff --git a/lucene/queries/src/java/org/apache/lucene/queries/function/valuesource/MultiFunction.java b/lucene/queries/src/java/org/apache/lucene/queries/function/valuesource/MultiFunction.java
index 6b587de..e702c66 100644
--- a/lucene/queries/src/java/org/apache/lucene/queries/function/valuesource/MultiFunction.java
+++ b/lucene/queries/src/java/org/apache/lucene/queries/function/valuesource/MultiFunction.java
@@ -121,6 +121,7 @@ public abstract class MultiFunction extends ValueSource {
     return valsArr;
   }
 
+  /** Base implementation that wraps multiple sources */
   public class Values extends FunctionValues {
     final FunctionValues[] valsArr;
 
diff --git a/lucene/queryparser/build.gradle b/lucene/queryparser/build.gradle
index 077eb24..e48139b 100644
--- a/lucene/queryparser/build.gradle
+++ b/lucene/queryparser/build.gradle
@@ -26,3 +26,9 @@ dependencies {
 
   testImplementation project(':lucene:test-framework')
 }
+
+ext {
+  // TODO: fix missing javadocs
+  javadocMissingLevel = "class"
+}
+
diff --git a/lucene/queryparser/src/java/org/apache/lucene/queryparser/flexible/precedence/processors/package-info.java b/lucene/queryparser/src/java/org/apache/lucene/queryparser/flexible/precedence/processors/package-info.java
new file mode 100644
index 0000000..5d0cf3c
--- /dev/null
+++ b/lucene/queryparser/src/java/org/apache/lucene/queryparser/flexible/precedence/processors/package-info.java
@@ -0,0 +1,39 @@
+/*
+ * 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.
+ */
+
+/**
+ * Lucene Precedence Query Parser Processors
+ *
+ * <p>
+ * This package contains the 2 {@link org.apache.lucene.queryparser.flexible.core.processors.QueryNodeProcessor}s used by
+ * {@link org.apache.lucene.queryparser.flexible.precedence.PrecedenceQueryParser}.
+ * </p>
+ * <p>
+ * {@link org.apache.lucene.queryparser.flexible.precedence.processors.BooleanModifiersQueryNodeProcessor}: this processor
+ * is used to apply {@link org.apache.lucene.queryparser.flexible.core.nodes.ModifierQueryNode}s on
+ * {@link org.apache.lucene.queryparser.flexible.core.nodes.BooleanQueryNode} children according to the boolean type
+ * or the default operator.
+ * </p>
+ * <p>
+ * {@link org.apache.lucene.queryparser.flexible.precedence.processors.PrecedenceQueryNodeProcessorPipeline}: this
+ * processor pipeline is used by {@link org.apache.lucene.queryparser.flexible.precedence.PrecedenceQueryParser}. It extends
+ * {@link org.apache.lucene.queryparser.flexible.standard.processors.StandardQueryNodeProcessorPipeline} and rearrange
+ * the pipeline so the boolean precedence is processed correctly. Check {@link org.apache.lucene.queryparser.flexible.precedence.processors.PrecedenceQueryNodeProcessorPipeline}
+ * for more details. 
+ * </p>
+ */
+package org.apache.lucene.queryparser.flexible.precedence.processors;
\ No newline at end of file
diff --git a/lucene/queryparser/src/java/org/apache/lucene/queryparser/flexible/precedence/processors/package.html b/lucene/queryparser/src/java/org/apache/lucene/queryparser/flexible/precedence/processors/package.html
deleted file mode 100644
index 1c4e093..0000000
--- a/lucene/queryparser/src/java/org/apache/lucene/queryparser/flexible/precedence/processors/package.html
+++ /dev/null
@@ -1,47 +0,0 @@
-<!doctype html public "-//w3c//dtd html 4.0 transitional//en">
-<!--
- 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.
--->
-<html>
-<head>
-   <meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1">
-</head>
-<body>
-
-Processors used by Precedence Query Parser
-
-<h2>Lucene Precedence Query Parser Processors</h2>
-
-<p>
-This package contains the 2 {@link org.apache.lucene.queryparser.flexible.core.processors.QueryNodeProcessor}s used by
-{@link org.apache.lucene.queryparser.flexible.precedence.PrecedenceQueryParser}.
-</p>
-<p>
-{@link org.apache.lucene.queryparser.flexible.precedence.processors.BooleanModifiersQueryNodeProcessor}: this processor
-is used to apply {@link org.apache.lucene.queryparser.flexible.core.nodes.ModifierQueryNode}s on
-{@link org.apache.lucene.queryparser.flexible.core.nodes.BooleanQueryNode} children according to the boolean type
-or the default operator.
-</p>
-<p>
-{@link org.apache.lucene.queryparser.flexible.precedence.processors.PrecedenceQueryNodeProcessorPipeline}: this
-processor pipeline is used by {@link org.apache.lucene.queryparser.flexible.precedence.PrecedenceQueryParser}. It extends
-{@link org.apache.lucene.queryparser.flexible.standard.processors.StandardQueryNodeProcessorPipeline} and rearrange
-the pipeline so the boolean precedence is processed correctly. Check {@link org.apache.lucene.queryparser.flexible.precedence.processors.PrecedenceQueryNodeProcessorPipeline}
-for more details. 
-</p>
-
-</body>
-</html>
diff --git a/lucene/replicator/build.gradle b/lucene/replicator/build.gradle
index 32d5cc7..aad6580 100644
--- a/lucene/replicator/build.gradle
+++ b/lucene/replicator/build.gradle
@@ -36,3 +36,8 @@ dependencies {
 
   testImplementation project(':lucene:test-framework')
 }
+
+ext {
+  // TODO: fix missing javadocs
+  javadocMissingLevel = "class"
+}
diff --git a/lucene/sandbox/build.gradle b/lucene/sandbox/build.gradle
index ec2d9ca..474afd7 100644
--- a/lucene/sandbox/build.gradle
+++ b/lucene/sandbox/build.gradle
@@ -23,3 +23,13 @@ dependencies {
   api project(':lucene:core')
   testImplementation project(':lucene:test-framework')
 }
+
+ext {
+  // TODO: fix missing javadocs
+  javadocMissingLevel = "class"
+  // TODO: clean up split packages
+  javadocMissingIgnore = [
+    "org.apache.lucene.search",
+    "org.apache.lucene.document"
+  ]
+}
diff --git a/lucene/spatial-extras/build.gradle b/lucene/spatial-extras/build.gradle
index 2766f7b..f5282e2 100644
--- a/lucene/spatial-extras/build.gradle
+++ b/lucene/spatial-extras/build.gradle
@@ -31,3 +31,9 @@ dependencies {
   testImplementation 'org.locationtech.jts:jts-core'
   testImplementation 'org.locationtech.spatial4j:spatial4j::tests'
 }
+
+ext {
+  // TODO: fix missing javadocs
+  javadocMissingLevel = "class"
+}
+
diff --git a/solr/build.gradle b/lucene/spatial-extras/src/java/org/apache/lucene/spatial/package-info.java
similarity index 90%
copy from solr/build.gradle
copy to lucene/spatial-extras/src/java/org/apache/lucene/spatial/package-info.java
index 9edc4d1..ce3163c 100644
--- a/solr/build.gradle
+++ b/lucene/spatial-extras/src/java/org/apache/lucene/spatial/package-info.java
@@ -15,8 +15,5 @@
  * limitations under the License.
  */
 
-description = 'Parent project for Apache Solr'
-
-subprojects {
-  group "org.apache.solr"
-}
\ No newline at end of file
+/** Lucene advanced spatial search */
+package org.apache.lucene.spatial;
\ No newline at end of file
diff --git a/lucene/spatial-extras/src/java/org/apache/lucene/spatial/package.html b/lucene/spatial-extras/src/java/org/apache/lucene/spatial/package.html
deleted file mode 100644
index b109f3a..0000000
--- a/lucene/spatial-extras/src/java/org/apache/lucene/spatial/package.html
+++ /dev/null
@@ -1,26 +0,0 @@
-<!doctype html public "-//w3c//dtd html 4.0 transitional//en">
-<!--
- 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.
--->
-<!-- not a package-info.java, because we already defined this package in spatial/ -->
-<html>
-<head>
-   <meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1">
-</head>
-<body>
-Lucene advanced spatial search
-</body>
-</html>
diff --git a/lucene/spatial-extras/src/java/org/apache/lucene/spatial/prefix/PrefixTreeStrategy.java b/lucene/spatial-extras/src/java/org/apache/lucene/spatial/prefix/PrefixTreeStrategy.java
index 99a5a4a..10b24a2 100644
--- a/lucene/spatial-extras/src/java/org/apache/lucene/spatial/prefix/PrefixTreeStrategy.java
+++ b/lucene/spatial-extras/src/java/org/apache/lucene/spatial/prefix/PrefixTreeStrategy.java
@@ -157,6 +157,7 @@ public abstract class PrefixTreeStrategy extends SpatialStrategy {
     return new Field[]{field};
   }
 
+  /** Tokenstream for indexing cells of a shape */
   public class ShapeTokenStream extends BytesRefIteratorTokenStream {
 
     public void setShape(Shape shape) {
diff --git a/lucene/spatial-extras/src/java/org/apache/lucene/spatial/prefix/tree/QuadPrefixTree.java b/lucene/spatial-extras/src/java/org/apache/lucene/spatial/prefix/tree/QuadPrefixTree.java
index d1407e1..89cf5e1 100644
--- a/lucene/spatial-extras/src/java/org/apache/lucene/spatial/prefix/tree/QuadPrefixTree.java
+++ b/lucene/spatial-extras/src/java/org/apache/lucene/spatial/prefix/tree/QuadPrefixTree.java
@@ -267,6 +267,7 @@ public class QuadPrefixTree extends LegacyPrefixTree {
     // if we actually use the range property in the query, this could be useful
   }
 
+  /** individual QuadPrefixTree grid cell */
   protected class QuadCell extends LegacyCell {
 
     QuadCell(byte[] bytes, int off, int len) {
diff --git a/solr/build.gradle b/lucene/spatial-extras/src/java/org/apache/lucene/spatial/util/package-info.java
similarity index 90%
copy from solr/build.gradle
copy to lucene/spatial-extras/src/java/org/apache/lucene/spatial/util/package-info.java
index 9edc4d1..21f90eb 100644
--- a/solr/build.gradle
+++ b/lucene/spatial-extras/src/java/org/apache/lucene/spatial/util/package-info.java
@@ -15,8 +15,5 @@
  * limitations under the License.
  */
 
-description = 'Parent project for Apache Solr'
-
-subprojects {
-  group "org.apache.solr"
-}
\ No newline at end of file
+/** Advanced spatial utilities. */
+package org.apache.lucene.spatial.util;
\ No newline at end of file
diff --git a/lucene/spatial-extras/src/java/org/apache/lucene/spatial/util/package.html b/lucene/spatial-extras/src/java/org/apache/lucene/spatial/util/package.html
deleted file mode 100644
index 83d9975..0000000
--- a/lucene/spatial-extras/src/java/org/apache/lucene/spatial/util/package.html
+++ /dev/null
@@ -1,26 +0,0 @@
-<!doctype html public "-//w3c//dtd html 4.0 transitional//en">
-<!--
- 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.
--->
-<!-- not a package-info.java, because we already defined this package in spatial/ -->
-<html>
-<head>
-   <meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1">
-</head>
-<body>
-Advanced spatial utilities. 
-</body>
-</html>
diff --git a/lucene/test-framework/build.gradle b/lucene/test-framework/build.gradle
index ce9355b..b645312 100644
--- a/lucene/test-framework/build.gradle
+++ b/lucene/test-framework/build.gradle
@@ -32,3 +32,28 @@ dependencies {
 
   implementation project(':lucene:codecs')
 }
+
+ext {
+  // TODO: fix missing javadocs
+  javadocMissingLevel = "class"
+  // TODO: clean up split packages
+  javadocMissingIgnore = [
+    "org.apache.lucene.analysis",
+    "org.apache.lucene.analysis.standard",
+    "org.apache.lucene.codecs",
+    "org.apache.lucene.codecs.blockterms",
+    "org.apache.lucene.codecs.bloom",
+    "org.apache.lucene.codecs.compressing",
+    "org.apache.lucene.codecs.uniformsplit",
+    "org.apache.lucene.codecs.uniformsplit.sharedterms",
+    "org.apache.lucene.geo",
+    "org.apache.lucene.index",
+    "org.apache.lucene.search",
+    "org.apache.lucene.search.similarities",
+    "org.apache.lucene.search.spans",
+    "org.apache.lucene.store",
+    "org.apache.lucene.util",
+    "org.apache.lucene.util.automaton",
+    "org.apache.lucene.util.fst"
+  ]
+}
diff --git a/lucene/test-framework/src/java/org/apache/lucene/index/BaseTermVectorsFormatTestCase.java b/lucene/test-framework/src/java/org/apache/lucene/index/BaseTermVectorsFormatTestCase.java
index 0955996..0e4e869 100644
--- a/lucene/test-framework/src/java/org/apache/lucene/index/BaseTermVectorsFormatTestCase.java
+++ b/lucene/test-framework/src/java/org/apache/lucene/index/BaseTermVectorsFormatTestCase.java
@@ -332,6 +332,7 @@ public abstract class BaseTermVectorsFormatTestCase extends BaseIndexFileFormatT
 
   }
 
+  /** Randomly generated document: call toDocument to index it */
   protected class RandomDocument {
 
     private final String[] fieldNames;
@@ -366,6 +367,7 @@ public abstract class BaseTermVectorsFormatTestCase extends BaseIndexFileFormatT
 
   }
 
+  /** Factory for generating random documents, call newDocument to generate each one */
   protected class RandomDocumentFactory {
 
     private final String[] fieldNames;
diff --git a/lucene/test-framework/src/java/org/apache/lucene/index/PointsStackTracker.java b/lucene/test-framework/src/java/org/apache/lucene/index/PointsStackTracker.java
index 5f3535d..9a2fa9f 100644
--- a/lucene/test-framework/src/java/org/apache/lucene/index/PointsStackTracker.java
+++ b/lucene/test-framework/src/java/org/apache/lucene/index/PointsStackTracker.java
@@ -31,6 +31,7 @@ public class PointsStackTracker {
 
   public final List<Cell> stack = new ArrayList<>();
 
+  /** Individual BKD stack frame */
   public class Cell {
     public final byte[] minPackedValue;
     public final byte[] maxPackedValue;
diff --git a/lucene/test-framework/src/java/org/apache/lucene/search/ShardSearchingTestBase.java b/lucene/test-framework/src/java/org/apache/lucene/search/ShardSearchingTestBase.java
index a8f1b7d..cd83f44 100644
--- a/lucene/test-framework/src/java/org/apache/lucene/search/ShardSearchingTestBase.java
+++ b/lucene/test-framework/src/java/org/apache/lucene/search/ShardSearchingTestBase.java
@@ -197,6 +197,7 @@ public abstract class ShardSearchingTestBase extends LuceneTestCase {
     return stats;
   }
 
+  /** Simulated shard node under test */
   protected final class NodeState implements Closeable {
     public final Directory dir;
     public final IndexWriter writer;
diff --git a/solr/build.gradle b/solr/build.gradle
index 9edc4d1..a2406f3 100644
--- a/solr/build.gradle
+++ b/solr/build.gradle
@@ -19,4 +19,10 @@ description = 'Parent project for Apache Solr'
 
 subprojects {
   group "org.apache.solr"
-}
\ No newline at end of file
+}
+
+ext {
+  // TODO: fix missing javadocs
+  javadocMissingLevel = "package"
+}
+
diff --git a/solr/contrib/analysis-extras/build.gradle b/solr/contrib/analysis-extras/build.gradle
index c39629a..305bc10 100644
--- a/solr/contrib/analysis-extras/build.gradle
+++ b/solr/contrib/analysis-extras/build.gradle
@@ -33,3 +33,10 @@ dependencies {
   testImplementation project(':solr:test-framework')
 }
 
+ext {
+  // TODO: clean up split packages
+  javadocMissingIgnore = [
+    "org.apache.solr.schema",
+    "org.apache.solr.update.processor"
+  ]
+}
diff --git a/solr/contrib/analytics/build.gradle b/solr/contrib/analytics/build.gradle
index 91e165a..26eade4 100644
--- a/solr/contrib/analytics/build.gradle
+++ b/solr/contrib/analytics/build.gradle
@@ -23,3 +23,12 @@ dependencies {
   implementation project(':solr:core')
   testImplementation project(':solr:test-framework')
 }
+
+ext {
+  // TODO: clean up split packages
+  javadocMissingIgnore = [
+    "org.apache.solr.handler",
+    "org.apache.solr.handler.component",
+    "org.apache.solr.response"
+  ]
+}
diff --git a/solr/contrib/langid/build.gradle b/solr/contrib/langid/build.gradle
index f9cfdb7..f9b41a0 100644
--- a/solr/contrib/langid/build.gradle
+++ b/solr/contrib/langid/build.gradle
@@ -32,3 +32,8 @@ dependencies {
 
   testImplementation project(':solr:test-framework')
 }
+
+ext {
+  // TODO: clean up split packages
+  javadocMissingIgnore = [ "org.apache.solr.update.processor" ]
+}
diff --git a/solr/contrib/velocity/build.gradle b/solr/contrib/velocity/build.gradle
index b17b38b..f42ade9 100644
--- a/solr/contrib/velocity/build.gradle
+++ b/solr/contrib/velocity/build.gradle
@@ -31,3 +31,8 @@ dependencies {
 
   testImplementation project(':solr:test-framework')
 }
+
+ext {
+  // TODO: clean up split packages
+  javadocMissingIgnore = [ "org.apache.solr.response" ]
+}
diff --git a/solr/build.gradle b/solr/core/src/java/org/apache/solr/query/package-info.java
similarity index 90%
copy from solr/build.gradle
copy to solr/core/src/java/org/apache/solr/query/package-info.java
index 9edc4d1..1bc0840 100644
--- a/solr/build.gradle
+++ b/solr/core/src/java/org/apache/solr/query/package-info.java
@@ -15,8 +15,7 @@
  * limitations under the License.
  */
 
-description = 'Parent project for Apache Solr'
-
-subprojects {
-  group "org.apache.solr"
-}
\ No newline at end of file
+/**
+ * Solr Queries
+ */
+package org.apache.solr.query;
\ No newline at end of file
diff --git a/solr/core/src/java/org/apache/solr/query/package.html b/solr/core/src/java/org/apache/solr/query/package.html
deleted file mode 100644
index 03ecf5f..0000000
--- a/solr/core/src/java/org/apache/solr/query/package.html
+++ /dev/null
@@ -1,27 +0,0 @@
-<!doctype html public "-//w3c//dtd html 4.0 transitional//en">
-<!--
- 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.
--->
-<html>
-<head>
-   <meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1">
-</head>
-<body>
-<p>
-Solr Queries
-</p>
-</body>
-</html>
diff --git a/solr/build.gradle b/solr/core/src/java/org/apache/solr/request/json/package-info.java
similarity index 90%
copy from solr/build.gradle
copy to solr/core/src/java/org/apache/solr/request/json/package-info.java
index 9edc4d1..2a3060d 100644
--- a/solr/build.gradle
+++ b/solr/core/src/java/org/apache/solr/request/json/package-info.java
@@ -15,8 +15,7 @@
  * limitations under the License.
  */
 
-description = 'Parent project for Apache Solr'
-
-subprojects {
-  group "org.apache.solr"
-}
\ No newline at end of file
+/**
+ * JSON related classes
+ */
+package org.apache.solr.request.json;
\ No newline at end of file
diff --git a/solr/core/src/java/org/apache/solr/request/json/package.html b/solr/core/src/java/org/apache/solr/request/json/package.html
deleted file mode 100644
index d87e4a4..0000000
--- a/solr/core/src/java/org/apache/solr/request/json/package.html
+++ /dev/null
@@ -1,27 +0,0 @@
-<!doctype html public "-//w3c//dtd html 4.0 transitional//en">
-<!--
- 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.
--->
-<html>
-<head>
-   <meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1">
-</head>
-<body>
-<p>
-JSON related classes
-</p>
-</body>
-</html>
diff --git a/solr/build.gradle b/solr/core/src/java/org/apache/solr/request/macro/package-info.java
similarity index 89%
copy from solr/build.gradle
copy to solr/core/src/java/org/apache/solr/request/macro/package-info.java
index 9edc4d1..222f4c6 100644
--- a/solr/build.gradle
+++ b/solr/core/src/java/org/apache/solr/request/macro/package-info.java
@@ -15,8 +15,7 @@
  * limitations under the License.
  */
 
-description = 'Parent project for Apache Solr'
-
-subprojects {
-  group "org.apache.solr"
-}
\ No newline at end of file
+/**
+ * Parameter substitution / macro expansion.
+ */
+package org.apache.solr.request.macro;
\ No newline at end of file
diff --git a/solr/core/src/java/org/apache/solr/request/macro/package.html b/solr/core/src/java/org/apache/solr/request/macro/package.html
deleted file mode 100644
index 21856b4..0000000
--- a/solr/core/src/java/org/apache/solr/request/macro/package.html
+++ /dev/null
@@ -1,27 +0,0 @@
-<!doctype html public "-//w3c//dtd html 4.0 transitional//en">
-<!--
- 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.
--->
-<html>
-<head>
-   <meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1">
-</head>
-<body>
-<p>
-Parameter substitution / macro expansion.
-</p>
-</body>
-</html>
diff --git a/solr/build.gradle b/solr/core/src/java/org/apache/solr/search/facet/package-info.java
similarity index 86%
copy from solr/build.gradle
copy to solr/core/src/java/org/apache/solr/search/facet/package-info.java
index 9edc4d1..fbbb625 100644
--- a/solr/build.gradle
+++ b/solr/core/src/java/org/apache/solr/search/facet/package-info.java
@@ -15,8 +15,8 @@
  * limitations under the License.
  */
 
-description = 'Parent project for Apache Solr'
-
-subprojects {
-  group "org.apache.solr"
-}
\ No newline at end of file
+/**
+ * APIs and classes for the JSON Facet API.
+ * This is currently experimental!
+ */
+package org.apache.solr.search.facet;
diff --git a/solr/core/src/java/org/apache/solr/search/facet/package.html b/solr/core/src/java/org/apache/solr/search/facet/package.html
deleted file mode 100644
index a0590cb..0000000
--- a/solr/core/src/java/org/apache/solr/search/facet/package.html
+++ /dev/null
@@ -1,28 +0,0 @@
-<!doctype html public "-//w3c//dtd html 4.0 transitional//en">
-<!--
- 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.
--->
-<html>
-<head>
-   <meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1">
-</head>
-<body>
-<p>
-APIs and classes for the JSON Facet API.
-This is currently experimental!
-</p>
-</body>
-</html>
diff --git a/solr/build.gradle b/solr/core/src/java/org/apache/solr/util/circuitbreaker/package-info.java
similarity index 87%
copy from solr/build.gradle
copy to solr/core/src/java/org/apache/solr/util/circuitbreaker/package-info.java
index 9edc4d1..75718b5 100644
--- a/solr/build.gradle
+++ b/solr/core/src/java/org/apache/solr/util/circuitbreaker/package-info.java
@@ -15,8 +15,7 @@
  * limitations under the License.
  */
 
-description = 'Parent project for Apache Solr'
-
-subprojects {
-  group "org.apache.solr"
-}
\ No newline at end of file
+/**
+ * Support for triggering exceptions on excessive resource usage
+ */
+package org.apache.solr.util.circuitbreaker;
\ No newline at end of file
diff --git a/solr/solrj/build.gradle b/solr/solrj/build.gradle
index e8c8a07..118f0be 100644
--- a/solr/solrj/build.gradle
+++ b/solr/solrj/build.gradle
@@ -74,3 +74,8 @@ dependencies {
   })
   testImplementation "org.hsqldb:hsqldb"
 }
+
+ext {
+  // TODO: clean up split packages
+  javadocMissingIgnore = [ "org.apache.solr.client.solrj.embedded" ]
+}
diff --git a/solr/test-framework/build.gradle b/solr/test-framework/build.gradle
index 62a724c..b92aa5d 100644
--- a/solr/test-framework/build.gradle
+++ b/solr/test-framework/build.gradle
@@ -31,3 +31,16 @@ dependencies {
   implementation 'io.dropwizard.metrics:metrics-jetty9'
   implementation 'com.lmax:disruptor'
 }
+
+ext {
+  // TODO: clean up split packages
+  javadocMissingIgnore = [
+    "org.apache.solr",
+    "org.apache.solr.analysis",
+    "org.apache.solr.cloud",
+    "org.apache.solr.core",
+    "org.apache.solr.handler.component",
+    "org.apache.solr.update.processor",
+    "org.apache.solr.util"
+  ]
+}