Merge branch 'stable-3.9'

* stable-3.9:
  Update git submodules
  Update git submodules
  Document missing options of ls-projects command
  Bazel: Add support for BuildBuddy RBE provider
  Specify GCP suffix explicitly in Bazel remote configuration
  Bazel: Optimize RBE execution
  Bazel: Clean up configuration options
  Update git submodules
  Bump SSHD version to 2.12.0

Change-Id: Ia19dd822a52f464e56e817eb4356a736220ff34c
Release-Notes: skip
diff --git a/.bazelrc b/.bazelrc
index 1ba7582..9662078 100644
--- a/.bazelrc
+++ b/.bazelrc
@@ -1,3 +1,7 @@
+# TODO(davido): Migrate all dependencies from WORKSPACE to MODULE.bazel
+# https://issues.gerritcodereview.com/issues/303819949
+common --noenable_bzlmod
+
 build --workspace_status_command="python3 ./tools/workspace_status.py"
 build --repository_cache=~/.gerritcodereview/bazel-cache/repository
 build --action_env=PATH
@@ -47,6 +51,29 @@
 build:remote11_bb --config=config_bb
 build:remote11_bb --config=build_java11_shared
 
+# Builds using remotejdk_21, executes using remotejdk_21 or local_jdk
+build:build_java21_shared --java_language_version=21
+build:build_java21_shared --java_runtime_version=remotejdk_21
+build:build_java21_shared --tool_java_language_version=21
+build:build_java21_shared --tool_java_runtime_version=remotejdk_21
+
+build:java21 --config=build_java21_shared
+
+# Builds and executes on RBE using remotejdk_21
+build:remote21 --config=config_gcp
+build:remote21 --config=build_java21_shared
+
+# Define remote21 configuration alias
+build:remote21_gcp --config=remote21
+
+# Builds and executes on BuildBuddy RBE using remotejdk_11
+build:remote21_bb --config=config_bb
+build:remote21_bb --config=build_java21_shared
+
+# Enable modern C++ features
+build --cxxopt=-std=c++17
+build --host_cxxopt=-std=c++17
+
 # Enable strict_action_env flag to. For more information on this feature see
 # https://groups.google.com/forum/#!topic/bazel-discuss/_VmRfMyyHBk.
 # This will be the new default behavior at some point (and the flag was flipped
@@ -60,3 +87,6 @@
 test --test_output=errors
 
 import %workspace%/tools/remote-bazelrc
+
+# User-specific .bazelrc
+try-import %workspace%/user.bazelrc
diff --git a/.bazelversion b/.bazelversion
index 91e4a9f..66ce77b 100644
--- a/.bazelversion
+++ b/.bazelversion
@@ -1 +1 @@
-6.3.2
+7.0.0
diff --git a/Documentation/access-control.txt b/Documentation/access-control.txt
index 19a19dd..2e90cff 100644
--- a/Documentation/access-control.txt
+++ b/Documentation/access-control.txt
@@ -264,6 +264,7 @@
 `-2..+2`, as the user's membership of `Foo Leads` effectively grant
 them access to the entire reference space, thanks to the wildcard.
 
+[[exclusive]]
 Gerrit also supports exclusive reference-level access control.
 
 It is possible to configure Gerrit to grant an exclusive ref level
@@ -443,13 +444,13 @@
 
 
 [[category_create]]
-=== Create Reference
+=== Create (aka Create Reference)
 
-The create reference category controls whether it is possible to
-create new references, branches or tags.  This implies that the
-reference must not already exist, it's not a destructive permission
-in that you can't overwrite or remove any previously existing
-references (and also discard any commits in the process).
+The create category controls whether it is possible to create new
+references, branches or tags.  This implies that the reference must not
+already exist, it's not a destructive permission in that you can't
+overwrite or remove any previously existing references (and also
+discard any commits in the process).
 
 It's probably most common to either permit the creation of a single
 branch in many gits (by granting permission on a parent project), or
@@ -484,9 +485,9 @@
 `${username}` are still reachable by the users.
 
 [[category_delete]]
-=== Delete Reference
+=== Delete (aka Delete Reference)
 
-The delete reference category controls whether it is possible to delete
+The delete category controls whether it is possible to delete
 references, branches or tags. It doesn't allow any other update of
 references.
 
@@ -550,29 +551,46 @@
 [[category_owner]]
 === Owner
 
-The `Owner` category controls which groups can modify the project's
-configuration.  Users who are members of an owner group can:
+The `Owner` category on `refs/*` controls which groups own the project,
+i.e. the users who are members of an owner group are called the
+`project owners`.
 
-* Change the project description
-* Grant/revoke any access rights, including `Owner`
+Project owners can change the link:config-project-config.html[project
+configuration], including:
 
-To get SSH branch access project owners must grant an access right to a group
-they are a member of, just like for any other user.
+* Granting/revoking any access rights (including the `Owner` access
+  right)
+* Changing the project description
+* Changing the link to the parent project
+* Changing the project options
+* Changing link:config-labels.html[labels]
+* Changing link:config-submit-requirements.html[submit requirements]
 
-Ownership over a particular branch subspace may be delegated by
-entering a branch pattern.  To delegate control over all branches
-that begin with `qa/` to the QA group, add `Owner` category
-for reference `+refs/heads/qa/*+`.  Members of the QA group can
-further refine access, but only for references that begin with
-`refs/heads/qa/`. See <<project_owners,project owners>> to find
-out more about this role.
+[NOTE]
+Access rights that are assigned to the magic
+link:#project_owners[Project Owners] group are resolved to the users
+that are project owners by having the `Owner` permission assigned on
+`refs/*`.
 
+[NOTE]
+To get branch access via Git project owners must grant an access right
+to a group they are a member of, just like for any other user.
+
+[NOTE]
 For the `All-Projects` root project any `Owner` access right on
 'refs/*' is ignored since this permission would allow users to edit the
 global capabilities, which is the same as being able to administrate
 the Gerrit server (e.g. the user could assign the `Administrate Server`
 capability to the own account).
 
+Ownership over a particular branch subspace may be delegated by
+entering a branch pattern. E.g. to delegate control over all branches
+that begin with `qa/` to the QA group, add the `Owner` category
+for reference `+refs/heads/qa/*+`. Members of the QA group can
+further refine access, but only for references that begin with
+`refs/heads/qa/`. See <<project_owners,project owners>> to find
+out more about this role.
+
 
 [[category_push]]
 === Push
@@ -1618,6 +1636,7 @@
 
 Access to refs can be blocked, allowed or denied.
 
+[[block-rule]]
 ==== BLOCK
 
 For blocking access, all rules marked BLOCK are tested, and if one
@@ -1643,6 +1662,7 @@
 permissions when they are processed in the ALLOW/DENY processing, as
 described in the next subsection.
 
+[[allow-rule]]
 ==== ALLOW
 
 For allowing access, all ALLOW/DENY rules that might apply to a ref
@@ -1656,6 +1676,7 @@
 This ordering lets project owners apply permissions specific to their
 project, overwriting the site defaults specified in All-Projects.
 
+[[deny-rule]]
 ==== DENY
 
 DENY is processed together with ALLOW.
diff --git a/Documentation/cmd-hook-commit-msg.txt b/Documentation/cmd-hook-commit-msg.txt
index e547822..ccb6d58 100644
--- a/Documentation/cmd-hook-commit-msg.txt
+++ b/Documentation/cmd-hook-commit-msg.txt
@@ -56,6 +56,12 @@
 The `Change-Id` will not be added if `gerrit.createChangeId` is set
 to `false` in the git config.
 
+The `Change-Id` will not be added to temporary commits created by
+`git commit --fixup` or `git commit --squash`, as well as commits
+with a subject line that begins with a lowercase word followed by
+an exclamation mark (e.g., `nopush!`). To override this behavior,
+set `gerrit.createChangeId` to `always` in the git config.
+
 If `gerrit.reviewUrl` is set to the base URL of the Gerrit server that
 changes are uploaded to (e.g. `https://gerrit-review.googlesource.com/`)
 in the git config, then instead of adding a `Change-Id` trailer, a `Link`
diff --git a/Documentation/config-gerrit.txt b/Documentation/config-gerrit.txt
index fb5904b..3ddc3ee 100644
--- a/Documentation/config-gerrit.txt
+++ b/Documentation/config-gerrit.txt
@@ -116,7 +116,11 @@
 current user is a member of are visible.
 +
 If `VISIBLE_GROUP`, only users who are members of at least one group
-that is visible to the current user are visible.
+that is visible to the current user are visible. To make an account
+visible to all users (e.g. because it is a service account) when
+`VISIBLE_GROUP` is used, create a `Public Users` group that has the
+`Make group visible to all registered users` option enabled and add the
+account as a group member.
 +
 If `NONE`, no users other than the current user are visible.
 +
@@ -526,10 +530,10 @@
 
 [[auth.cookieSecure]]auth.cookieSecure::
 +
-Sets "secure" flag of the authentication cookie.  If true, cookies
+Sets "secure" flag of the authentication cookie.  If `true`, cookies
 will be transmitted only over HTTPS protocol.
 +
-By default, false.
+By default, `false`.
 
 [[auth.emailFormat]]auth.emailFormat::
 +
@@ -558,7 +562,7 @@
 `'$site_path'/static`, so users can actually complete one or
 more agreements.
 +
-By default this is false (no agreements are used).
+By default this is `false` (no agreements are used).
 +
 To enable the actual usage of contributor agreement the project
 specific config option in the `project.config` must be set:
@@ -567,14 +571,14 @@
 
 [[auth.trustContainerAuth]]auth.trustContainerAuth::
 +
-If true then it is the responsibility of the container hosting
+If `true` then it is the responsibility of the container hosting
 Gerrit to authenticate users. In this case Gerrit will blindly trust
 the container.
 +
 This parameter only affects git over http traffic. If set to false
 then Gerrit will do the authentication (using Basic authentication).
 +
-By default this is set to false.
+By default this is set to `false`.
 
 
 [[auth.gitBasicAuthPolicy]]auth.gitBasicAuthPolicy::
@@ -652,7 +656,7 @@
 +
 This parameter only affects git over http and git over SSH traffic.
 +
-By default this is set to false.
+By default this is set to `false`.
 
 [[auth.userNameCaseInsensitive]]auth.userNameCaseInsensitive::
 +
@@ -679,29 +683,29 @@
 note name would be identical and thus conflict. These duplicates thus
 have to be deleted manually by deleting the respective external ID.
 +
-For newly initialized sites this option defaults to true.
+For newly initialized sites this option defaults to `true`.
 +
-Default is false.
+Default is `false`.
 
 [[auth.userNameCaseInsensitiveMigrationMode]]auth.userNameCaseInsensitiveMigrationMode::
 +
-Setting migration mode to true allows to fallback to case sensitive
+Setting migration mode to `true` allows to fallback to case sensitive
 behaviour if the migrated external ID cannot be found. This allows to
 trigger the migration while Gerrit process is running.
 +
-Default is false.
+Default is `false`.
 
 [[auth.enableRunAs]]auth.enableRunAs::
 +
-If true HTTP REST APIs will accept the `X-Gerrit-RunAs` HTTP request
+If `true` HTTP REST APIs will accept the `X-Gerrit-RunAs` HTTP request
 header from any users granted the link:access-control.html#capability_runAs[Run As]
 capability. The header and capability permit the authenticated user
 to impersonate another account.
 +
-If false the feature is disabled and cannot be re-enabled without
+If `false` the feature is disabled and cannot be re-enabled without
 editing gerrit.config and restarting the server.
 +
-Default is true.
+Default is `true`.
 
 [[auth.allowRegisterNewEmail]]auth.allowRegisterNewEmail::
 +
@@ -711,13 +715,13 @@
 link:#auth.httpemailheader[auth.httpemailheader] must *not* be set to
 enable registration of new email addresses.
 +
-By default, true.
+By default, `true`.
 
 [[auth.autoUpdateAccountActiveStatus]]auth.autoUpdateAccountActiveStatus::
 +
 Whether to allow automatic synchronization of an account's inactive flag upon login.
 +
-If set to true, upon login, if the authentication back-end reports the account as active,
+If set to `true`, upon login, if the authentication back-end reports the account as active,
 the account's inactive flag in NoteDb will be updated to be active.
 +
 If the authentication back-end reports the account as inactive, the account's flag will be
@@ -725,12 +729,12 @@
 should ensure that their authentication back-end is supported. Currently, only
 strict 'LDAP' authentication is supported.
 +
-In addition, if this parameter is not set, or false, the corresponding scheduled
+In addition, if this parameter is not set, or `false`, the corresponding scheduled
 task to deactivate inactive Gerrit accounts will also be disabled. If this
-parameter is set to true, users should also consider configuring the
+parameter is set to `true`, users should also consider configuring the
 link:#accountDeactivation[accountDeactivation] section appropriately.
 +
-By default, false.
+By default, `false`.
 
 [[auth.skipFullRefEvaluationIfAllRefsAreVisible]]auth.skipFullRefEvaluationIfAllRefsAreVisible::
 +
@@ -740,7 +744,7 @@
 The full ref filtering would filter out refs for pending edits, private changes
 and auto merge commits.
 +
-By default, true.
+By default, `true`.
 
 [[cache]]
 === Section cache
@@ -777,7 +781,7 @@
 Whether to enable the computation of disk statistics of persistent caches.
 This computation is expensive and requires a long time on larger installations.
 +
-By default, false.
+By default, `false`.
 
 [[cache.h2CacheSize]]cache.h2CacheSize::
 +
@@ -800,13 +804,13 @@
 
 [[cache.h2AutoServer]]cache.h2AutoServer::
 +
-If set to true, enable H2 autoserver mode for the H2-backed persistent cache
+If set to `true`, enable H2 autoserver mode for the H2-backed persistent cache
 databases.
 +
 See link:http://www.h2database.com/html/features.html#auto_mixed_mode[here,role=external,window=_blank]
 for detail.
 +
-Default is false.
+Default is `false`.
 
 [[cache.openFiles]]cache.openFiles::
 +
@@ -1310,7 +1314,7 @@
 this setting will fallback to `cache.diff.intraline` if not set in the
 configuration.
 +
-Default is true, enabled.
+Default is `true`, enabled.
 
 [[cache.projects.loadOnStartup]]cache.projects.loadOnStartup::
 +
@@ -1320,12 +1324,12 @@
 size set under <<cache.name.memoryLimit,cache.projects.memoryLimit>>
 is not smaller than the number of repos.
 +
-Default is false, disabled.
+Default is `false`, disabled.
 
 [[cache.projects.loadThreads]]cache.projects.loadThreads::
 +
 Only relevant if <<cache.projects.loadOnStartup,cache.projects.loadOnStartup>>
-is true.
+is `true`.
 +
 The number of threads to allocate for loading the cache at startup. These
 threads will die out after the cache is loaded.
@@ -1380,7 +1384,7 @@
 `administrateServer` capability assigned. This is useful to bootstrap
 the link:config-accounts.html[account data].
 +
-Default is true.
+Default is `true`.
 
 
 [[change]]
@@ -1388,9 +1392,9 @@
 
 [[change.allowBlame]]change.allowBlame::
 +
-Allow blame on side by side diff. If set to false, blame cannot be used.
+Allow blame on side by side diff. If set to `false`, blame cannot be used.
 +
-Default is true.
+Default is `true`.
 
 [[change.cacheAutomerge]]change.cacheAutomerge::
 +
@@ -1400,13 +1404,13 @@
 diff caches (`"git_modified_files"`, `modified_files`, `"git_file_diff"`,
 `"file_diff"`).
 +
-If true, automerge results are stored in the repository under
+If `true`, automerge results are stored in the repository under
 `refs/cache-automerge/*`; the results of diffing the change against its
-automerge base are stored in the diff caches. If false, no extra data is
+automerge base are stored in the diff caches. If `false`, no extra data is
 stored in the repository, only the diff caches. This can result in slight
 performance improvements by reducing the number of refs in the repo.
 +
-Default is true.
+Default is `true`.
 
 [[change.commentSizeLimit]]change.commentSizeLimit::
 +
@@ -1434,9 +1438,9 @@
 
 [[change.disablePrivateChanges]]change.disablePrivateChanges::
 +
-If set to true, users are not allowed to create private changes.
+If set to `true`, users are not allowed to create private changes.
 +
-The default is false.
+The default is `false`.
 
 [[change.maxComments]]change.maxComments::
 +
@@ -1527,12 +1531,12 @@
 This setting determines when Gerrit renders conflict changes section on change
 screen and also supports `conflicts` predicate. This computation is expensive,
 computing ConflictsPredicate has a runtime complexity of O(nˆ2) with n number
-of open changes on a branch. When set to false GUI will silently ignore the
+of open changes on a branch. When set to `false` GUI will silently ignore the
 error message and leave the conflict changes section on change screen empty.
 See also implications on rendering of conflict changes section in configuration
 section:link:#change.mergeabilityComputationBehavior[change.mergeabilityComputationBehavior].
 
-Default is true.
+Default is `true`.
 
 [[change.maxSubmittableAtOnce]]change.maxSubmittableAtOnce::
 +
@@ -1555,29 +1559,43 @@
 branch (see details in
 link:https://issues.gerritcodereview.com/issues/40009784[issue 40009784]).
 +
-By default true.
+By default `true`.
 
 [[change.enableRobotComments]]change.enableRobotComments::
 +
 Are robot comments enabled in the Gerrit UI? This setting allows phasing out
 robot comments.
 +
-By default true.
+By default `true`.
 
 [[change.propagateSubmitRequirementErrors]]change.propagateSubmitRequirementErrors::
 +
-If a SubmitRequirement evaluation for a given change results in an
-ERROR status, abort the REST response with an HTTP 500 error.
+If set, requests that access the submit requirements of a change fail with an
+HTTP 500 error if the change has a submit requirement with a non-parseable
+expression that would otherwise result in an
+link:config-submit-requirements#status-error[ERROR] status for the submit
+requirement.
 +
-The ERROR status can occur if a SubmitRequirement uses a
-plugin-provided predicate (and the plugin is not available), due to
-bugs, or due to bypassing the validation that normally happens when
-updating `refs/meta/config`.
+Submit requirement expressions can become non-parseable due to bypassing the
+validation that normally happens when updating the project configuration in
+the `refs/meta/config` branch, or due to bugs in Gerrit.
 +
-Enabling this flag  makes gerrit unusuable under such conditions, so
-it is generally not recommended. However, this makes the
-application-specific ERROR status into a generic HTTP error, and can
-thus be acted on by automated deployment and monitoring infrastructure.
+A special case are expressions that use plugin-provided predicates. If any
+plugin that provides a predicate fails to load (e.g. due to an error in the
+plugin) the predicate can no longer be resolved and expressions that are using
+it can no longer be parsed. This is an error that requires the attention of the
+team that operates Gerrit, but in order to get notified when this happens the
+operation team would need to setup custom monitoring that observes whether
+link:config-submit-requirements#status-error[ERROR] statuses are returned for
+submit requirements. Instead this config option can be used to make
+non-parseable submit requirement expressions cause HTTP 500 errors which
+triggers the automatic alerting for errors that Gerrit operation teams usually
+have in place. This allows the operation team to react quickly when this
+happens.
++
+The drawback of enabling this option is that it causes requests to fail rather
+than handling parsing errors gracefully, which can make Gerrit for impacted
+users unusable.
 
 [[change.robotCommentSizeLimit]]change.robotCommentSizeLimit::
 +
@@ -1590,18 +1608,18 @@
 
 [[change.sendNewPatchsetEmails]]change.sendNewPatchsetEmails::
 +
-When false, emails will not be sent to owners, reviewers, and cc for
+When `false`, emails will not be sent to owners, reviewers, and cc for
 creating a new patchset unless they are project watchers or have starred
 the change.
 +
-Default is true.
+Default is `true`.
 
 [[change.showAssigneeInChangesTable]]change.showAssigneeInChangesTable::
 +
-Show assignee field in changes table. If set to false, assignees will
+Show assignee field in changes table. If set to `false`, assignees will
 not be visible in changes table.
 +
-Default is false.
+Default is `false`.
 
 [[change.strictLabels]]change.strictLabels::
 +
@@ -1609,7 +1627,7 @@
 configuration option is provided for backwards compatibility and may
 be removed in future gerrit versions.
 +
-Default is false.
+Default is `false`.
 
 [[change.submitLabel]]change.submitLabel::
 +
@@ -1654,7 +1672,7 @@
 
 [[change.submitTopicTooltip]]change.submitTopicTooltip::
 +
-If `change.submitWholeTopic` is configured to true and a change has a
+If `change.submitWholeTopic` is configured to `true` and a change has a
 topic, this configuration determines the tooltip for the submit button
 instead of `change.submitTooltip`. The variable `${topicSize}` is available
 for the number of changes in the same topic to be submitted. The number of
@@ -1669,7 +1687,7 @@
 Determines if the submit button submits the whole topic instead of
 just the current change.
 +
-Default is false.
+Default is `false`.
 
 [[change.updateDelay]]change.updateDelay::
 +
@@ -1698,7 +1716,7 @@
 This setting takes effect when generating the automerge, which happens on upload.
 Changing the setting leaves existing changes unaffected.
 +
-Default is false.
+Default is `false`.
 
 [[change.maxFileSizeDiff]]change.maxFileSizeDiff::
 +
@@ -1707,20 +1725,20 @@
 +
 If not set or set to zero, no limits are applied on file sizes.
 
-[[change.skipCurrentRulesEvaluationOnClosedChanges]]
+[[change.skipCurrentRulesEvaluationOnClosedChanges]]change.skipCurrentRulesEvaluationOnClosedChanges::
 +
-If false, Gerrit will always take latest project configuration to
+If `false`, Gerrit will always take latest project configuration to
 compute submit labels. This means that, closed changes (either merged
 or abandoned) will be evaluated against the latest configuration which
 may produce different results. Especially for merged changes, they may
 look like they didn't meet the submit requirements.
 +
-When true, evaluation will be skipped and Gerrit will show the
+When `true`, evaluation will be skipped and Gerrit will show the
 exact status of submit labels when change was submitted. Post-review
 votes will only be allowed on labels that were configured when change
 was closed.
 +
-Default it false.
+Default is `false`.
 
 [[changeCleanup]]
 === Section changeCleanup
@@ -1851,6 +1869,10 @@
 will hyperlink terms such as 'bug 42' to an external bug tracker,
 supplying the argument record number '42' for display.
 
+Before matching is done the relevant contents are html-escaped. If 'match' needs
+to contain `&`, `<`, `>`, `"` or  `'`, replace them with `&amp;`, `&gt;`,
+`&lt;`, `&quot;` and `&apos;` respectively.
+
 commentlinks supports link:#reloadConfig[configuration reloads]. Though a
 link:cmd-flush-caches.html[flush-caches] of "projects" is needed for the
 commentlinks to be immediately available in the UI.
@@ -1927,7 +1949,7 @@
 section in a parent or the site-wide config that is disabled by
 specifying `enabled = true`.
 +
-By default, true.
+By default, `true`.
 +
 Note that the names and contents of disabled sections are visible even
 to anonymous users via the
@@ -1986,7 +2008,7 @@
 
 [[container.replica]]container.replica::
 +
-Used on Gerrit replica installations. If set to true the Gerrit JVM is
+Used on Gerrit replica installations. If set to `true` the Gerrit JVM is
 called with the '--replica' switch, enabling replica mode. If no value is
 set (or any other value), Gerrit defaults to primary mode enabling write
 operations.
@@ -2119,18 +2141,18 @@
 
 [[core.packedGitMmap]]core.packedGitMmap::
 +
-When true, JGit will use `mmap()` rather than `malloc()+read()`
+When `true`, JGit will use `mmap()` rather than `malloc()+read()`
 to load data from pack files.  The use of mmap can be problematic
 on some JVMs as the garbage collector must deduce that a memory
 mapped segment is no longer in use before a call to `munmap()`
 can be made by the JVM native code.
 +
 In server applications (such as Gerrit) that need to access many
-pack files, setting this to true risks artificially running out
+pack files, setting this to `true` risks artificially running out
 of virtual address space, as the garbage collector cannot reclaim
 unused mapped spaces fast enough.
 +
-Default on JGit is false. Although potentially slower, it yields
+Default on JGit is `false`. Although potentially slower, it yields
 much more predictable behavior.
 
 [[core.asyncLoggingBufferSize]]core.asyncLoggingBufferSize::
@@ -2152,7 +2174,7 @@
 blog,role=external,window=_blank], the recursive merge produces better results if the two commits
 that are merged have more than one common predecessor.
 +
-Default is true.
+Default is `true`.
 
 [[core.repositoryCacheCleanupDelay]]core.repositoryCacheCleanupDelay::
 +
@@ -2189,7 +2211,7 @@
 (lazily) if needed. This helps reduce the overhead of checking if
 the packed-refs file is outdated.
 +
-Default is true.
+Default is `true`.
 
 [[dashboard]]
 === Section dashboard
@@ -2354,7 +2376,7 @@
 collections are more expensive but may lead to significantly smaller
 repositories.
 +
-Valid values are "true" and "false," default is "false".
+Valid values are "`true`" and "`false`," default is "`false`".
 
 [[gc.startTime]]gc.startTime::
 +
@@ -2450,7 +2472,7 @@
 for editing GPG keys. If disabled, GPG keys can only be added by
 administrators with direct git access to All-Users.
 +
-Defaults to true.
+Defaults to `true`.
 
 [[gerrit.installCommitMsgHookCommand]]gerrit.installCommitMsgHookCommand::
 +
@@ -2539,14 +2561,14 @@
 Enable rendering of project list from the secondary index instead
 of purely relying on the in-memory cache.
 +
-By default false.
+By default `false`.
 +
 [NOTE]
-The in-memory cache (set to false) rendering provides an **unlimited list** as a result
+The in-memory cache (set to `false`) rendering provides an **unlimited list** as a result
 of the list project API, causing the full list of projects to be
 returned as a result of the link:rest-api-projects.html[/projects/] REST API
 or the link:cmd-ls-projects.html[gerrit ls-projects] SSH command.
-When the rendering from the secondary index (set to true),
+When the rendering from the secondary index (set to `true`),
 the **list is limited** by the global capability
 link:access-control.html#capability_queryLimit[queryLimit]
 which is defaulted to 500 entries.
@@ -2578,7 +2600,7 @@
 
 Record actual peer IP address in ref log entry for identified user.
 
-Defaults to false.
+Defaults to `false`.
 
 [[gerrit.secureStoreClass]]gerrit.secureStoreClass::
 +
@@ -2593,9 +2615,9 @@
 [[gerrit.canLoadInIFrame]]gerrit.canLoadInIFrame::
 +
 For security reasons Gerrit will always jump out of iframe.
-Setting this option to true will prevent this behavior.
+Setting this option to `true` will prevent this behavior.
 +
-By default false.
+By default `false`.
 
 [[gerrit.xframeOption]]gerrit.xframeOption::
 +
@@ -2609,7 +2631,7 @@
 1. ALLOW - The page can be displayed in a frame.
 2. SAMEORIGIN - The page can only be displayed in a frame on the same origin as the page itself.
 +
-If link:#gerrit.canLoadInIFrame is set to false this option is ignored and the
+If link:#gerrit.canLoadInIFrame is set to `false` this option is ignored and the
 `X-Frame-Options` header is always set to `DENY`.
 Setting this option to `ALLOW` will cause the `X-Frame-Options` header to be omitted
 the the page can be displayed in a frame.
@@ -2651,17 +2673,17 @@
 Allow Gerrit to start even if the underlying schema version has been bumped to
 the next Gerrit version.
 +
-Set to true if Gerrit is installed in
+Set to `true` if Gerrit is installed in
 [high-availability configuration](https://gerrit.googlesource.com/plugins/high-availability/+/refs/heads/master/README.md)
 during the rolling upgrade to the next version.
 +
-By default false.
+By default `false`.
 +
 The rolling upgrade process, at high level, assumes that Gerrit is installed
 on two or more nodes sharing the repositories over NFS. The upgrade is composed
 of the following steps:
 +
-1. Set gerrit.experimentalRollingUpgrade to true on all Gerrit masters
+1. Set gerrit.experimentalRollingUpgrade to `true` on all Gerrit masters
 2. Set the first master unhealthy
 3. Shutdown the first master and [upgrade](install.html#init) to the next version
 4. Startup the first master, wait for the online reindex to complete (where applicable)
@@ -2679,7 +2701,7 @@
 ServerId of the repositories imported from other Gerrit servers. Changes coming
 associated with the imported serverIds are indexed and displayed in the UI
 but they are not searchable by `changeNumber` therefore the
-`index.cacheQueryResultsByChangeNum` must also be set to false.
+`index.cacheQueryResultsByChangeNum` must also be set to `false`.
 Imported changes are still discoverable in any other ways, for example:
 
   project:someproject branch:main changeId:I78a7add1fe2597cad788c833d8f771f09b54cf33
@@ -2851,7 +2873,7 @@
 [[groups.auditLog.ignoreRecordsFromUnidentifiedUsers]]groups.auditLog.ignoreRecordsFromUnidentifiedUsers::
 +
 Controls whether AuditLogReader should ignore commits created by unidentified users.
-If true, then AuditLogReader ignores commits in the refs/groups/* made by unidentified users (i.e.
+If `true`, then AuditLogReader ignores commits in the refs/groups/* made by unidentified users (i.e.
 when the author of a commit can't be parsed as account id).
 +
 The current version of Gerrit writes identified users as authors for new refs/groups/* commits.
@@ -2859,9 +2881,9 @@
 <server@googlesource.com>") for such commits. Such string can't be converted to account id but
 usually the commit shouldn't be ignored.
 +
-By default, false.
+By default, `false`.
 +
-Setting it to true may lead to some unexpected results in audit log and must be set carefully.
+Setting it to `true` may lead to some unexpected results in audit log and must be set carefully.
 
 [[groups.includeExternalUsersInRegisteredUsersGroup]]groups.includeExternalUsersInRegisteredUsersGroup::
 +
@@ -2873,14 +2895,14 @@
 or a custom authentication backend). By default, Gerrit core always requires
 users to register and doesn't use external users.
 +
-By default, true.
+By default, `true`.
 
 [[groups.newGroupsVisibleToAll]]groups.newGroupsVisibleToAll::
 +
 Controls whether newly created groups should be by default visible to
 all registered users.
 +
-By default, false.
+By default, `false`.
 
 [[groups.uuid.name]]groups.<uuid>.name::
 +
@@ -2995,7 +3017,7 @@
 
 [[http.addUserAsRequestAttribute]]http.addUserAsRequestAttribute::
 +
-If true, 'User' attribute will be added to the request attributes so it
+If `true`, 'User' attribute will be added to the request attributes so it
 can be accessed outside the request scope (will be set to username or id
 if username not configured).
 +
@@ -3010,15 +3032,15 @@
 Pattern to print user in Tomcat AccessLog.
 
 +
-Default value is true.
+Default value is `true`.
 
 [[http.addUserAsResponseHeader]]http.addUserAsResponseHeader::
 +
-If true, the header 'User' will be added to the list of response headers so it
+If `true`, the header 'User' will be added to the list of response headers so it
 can be accessed from a reverse proxy for logging purposes.
 
 +
-Default value is false.
+Default value is `false`.
 
 [[httpd]]
 === Section httpd
@@ -3147,12 +3169,12 @@
 
 [[httpd.reuseAddress]]httpd.reuseAddress::
 +
-If true, permits the daemon to bind to the port even if the port
-is already in use.  If false, the daemon ensures the port is not
+If `true`, permits the daemon to bind to the port even if the port
+is already in use.  If `false`, the daemon ensures the port is not
 in use before starting.  Busy sites may need to set this to true
 to permit fast restarts.
 +
-By default, true.
+By default, `true`.
 
 [[httpd.gracefulStopTimeout]]httpd.gracefulStopTimeout::
 +
@@ -3171,11 +3193,11 @@
 
 [[httpd.inheritChannel]]httpd.inheritChannel::
 +
-If true, permits the daemon to inherit its server socket channel
-from fd0/1(stdin/stdout). When set to true, the server can be socket
+If `true`, permits the daemon to inherit its server socket channel
+from fd0/1(stdin/stdout). When set to `true`, the server can be socket
 activated via systemd or xinetd.
 +
-By default, false.
+By default, `false`.
 
 [[httpd.requestHeaderSize]]httpd.requestHeaderSize::
 +
@@ -3246,8 +3268,8 @@
 `log4j.appender` with the name `httpd_log` can be configured to overwrite
 programmatic configuration.
 +
-By default, true if httpd.listenUrl uses http:// or https://,
-and false if httpd.listenUrl uses proxy-http:// or proxy-https://.
+By default, `true` if httpd.listenUrl uses http:// or https://,
+and `false` if httpd.listenUrl uses proxy-http:// or proxy-https://.
 
 [[httpd.acceptorThreads]]httpd.acceptorThreads::
 +
@@ -3399,7 +3421,7 @@
 +
 Enable (or disable) registration of Jetty MBeans for Java JMX.
 +
-By default, false.
+By default, `false`.
 
 [[index]]
 === Section index
@@ -3443,7 +3465,7 @@
 It needs to be turned off when having Changes imported from other servers
 because of the potential conflicts of change numbers.
 +
-Defaults to true.
+Defaults to `true`.
 
 [[index.onlineUpgrade]]index.onlineUpgrade::
 +
@@ -3452,10 +3474,10 @@
 Gerrit version upgrades (avoiding the need for an offline reindex step
 using Reindex), but can add additional server load during the upgrade.
 +
-If set to false, there is no way to upgrade the index schema to take
+If set to `false`, there is no way to upgrade the index schema to take
 advantage of new search features without restarting the server.
 +
-Defaults to true.
+Defaults to `true`.
 
 [[index.excludeProjectFromChangeReindex]]index.excludeProjectFromChangeReindex::
 +
@@ -3583,11 +3605,11 @@
 [[index.autoReindexIfStale]]index.autoReindexIfStale::
 +
 Whether to automatically check if a document became stale in the index
-immediately after indexing it. If false, there is a race condition during two
+immediately after indexing it. If `false`, there is a race condition during two
 simultaneous writes that may cause one of the writes to not be reflected in the
 index. The check to avoid this does consume some resources.
 +
-Defaults to false.
+Defaults to `false`.
 
 [[index.indexChangesAsync]]index.indexChangesAsync::
 +
@@ -3597,7 +3619,7 @@
 to the write request latency) and disadvantage that the indexing result might not be
 immediately available after the write request.
 +
-Defaults to false.
+Defaults to `false`.
 
 [[index.scheduledIndexer]]
 ==== Subsection index.scheduledIndexer
@@ -3739,7 +3761,7 @@
 link:https://lucene.apache.org/core/5_5_0/core/org/apache/lucene/index/ConcurrentMergeScheduler.html#enableAutoIOThrottle()[
 Lucene documentation,role=external,window=_blank] for further details.
 +
-Defaults to true (throttling enabled).
+Defaults to `true` (throttling enabled).
 
 During offline reindexing, setting ramBufferSize greater than the size
 of index (size of specific index folder under <site_dir>/index) and
@@ -3801,7 +3823,7 @@
 specific content, e.g.: `recheck`. Jenkins Gerrit Trigger plugin and Zuul CI
 depend on this feature to trigger change verification.
 +
-By default, true.
+By default, `true`.
 
 [[event.stream-events.enableRefUpdatedEvents]]event.stream-events.enableRefUpdatedEvents::
 +
@@ -3811,7 +3833,7 @@
 Please consider switching to `batch-ref-updated` event which provides better control on grouping and
 preserving order of the ref updates.
 +
-By default, true.
+By default, `true`.
 
 [[event.stream-events.enableBatchRefUpdatedEvents]]event.stream-events.enableBatchRefUpdatedEvents::
 +
@@ -3819,9 +3841,9 @@
 refs updated during a single batch ref update operation.
 Single ref updates are also streamed as a `batch-ref-updated` events with a single ref specified.
 This allows event listeners to react on all ref updated events and disable individual `ref-updated`
-events by setting <<event.stream-events.enableRefUpdatedEvents, event.stream-events.enableRefUpdatedEvents>> to false.
+events by setting <<event.stream-events.enableRefUpdatedEvents, event.stream-events.enableRefUpdatedEvents>> to `false`.
 +
-By default, false.
+By default, `false`.
 
 [[event.stream-events.enableDraftCommentEvents]]event.stream-events.enableDraftCommentEvents::
 +
@@ -3832,7 +3854,7 @@
 The extra amount of events depends on the usage pattern of the installation. It is worth evaluating
 the amount of extra events produced before enabling this flag by counting the calls to the draft APIs.
 +
-By default, false.
+By default, `false`.
 
 [[experiments]]
 === Section experiments
@@ -3905,7 +3927,7 @@
 temporarily inaccessible by users even with LDAP membership and grants
 referenced in the ACLs.
 +
-By default, true.
+By default, `true`.
 
 [[ldap.server]]ldap.server::
 +
@@ -3924,31 +3946,31 @@
 
 [[ldap.startTls]]ldap.startTls::
 +
-If true, Gerrit will perform StartTLS extended operation.
+If `true`, Gerrit will perform StartTLS extended operation.
 +
-By default, false, StartTLS will not be enabled.
+By default, `false`, StartTLS will not be enabled.
 
 [[ldap.supportAnonymous]]ldap.supportAnonymous::
 +
-If false, Gerrit will provide credentials only at connection open, this is
+If `false`, Gerrit will provide credentials only at connection open, this is
 required for some `LDAP` implementations that do not allow anonymous bind
 for StartTLS or for reauthentication.
 +
-By default, true.
+By default, `true`.
 
 [[ldap.sslVerify]]ldap.sslVerify::
 +
-If false and ldap.server is an `ldaps://` style URL or `ldap.startTls`
-is true, Gerrit will not verify the server certificate when it connects
+If `false` and ldap.server is an `ldaps://` style URL or `ldap.startTls`
+is `true`, Gerrit will not verify the server certificate when it connects
 to perform a query.
 +
-By default, true, requiring the certificate to be verified.
+By default, `true`, requiring the certificate to be verified.
 
 [[ldap.groupsVisibleToAll]]ldap.groupsVisibleToAll::
 +
-If true, LDAP groups are visible to all registered users.
+If `true`, LDAP groups are visible to all registered users.
 +
-By default, false, LDAP groups are visible only to administrators and
+By default, `false`, LDAP groups are visible only to administrators and
 group members.
 
 [[ldap.username]]ldap.username::
@@ -4185,7 +4207,7 @@
 +
 Converts the local username, that is used to login into the Gerrit
 Web UI, to lower case before doing the LDAP authentication. By setting
-this parameter to true, a case insensitive login to the Gerrit Web UI
+this parameter to `true`, a case insensitive login to the Gerrit Web UI
 can be achieved.
 +
 If set, it must be ensured that the local usernames for all existing
@@ -4198,7 +4220,7 @@
 case can't be undone. For newly created accounts the local username
 will be directly stored in lower case.
 +
-By default, unset/false.
+By default, unset/`false`.
 
 [[ldap.authentication]]ldap.authentication::
 +
@@ -4217,7 +4239,7 @@
             required
             useTicketCache=true
             doNotPrompt=true
-            renewTGT=true;
+            renewTGT=`true`;
 };
 ----
 
@@ -4236,7 +4258,7 @@
 +
 _(Optional)_ Enable the LDAP connection pooling or not.
 +
-If it is true, the LDAP service provider maintains a pool of (possibly)
+If it is `true`, the LDAP service provider maintains a pool of (possibly)
 previously used connections and assigns them to a Context instance as
 needed. When a Context instance is done with a connection (closed or
 garbage collected), the connection is returned to the pool for future use.
@@ -4245,7 +4267,7 @@
 LDAP connection management (Pool),role=external,window=_blank] and link:http://docs.oracle.com/javase/tutorial/jndi/ldap/config.html[
 LDAP connection management (Configuration),role=external,window=_blank]
 +
-By default, false.
+By default, `false`.
 
 [[ldap.connectTimeout]]ldap.connectTimeout::
 +
@@ -4291,7 +4313,7 @@
 
 [[log.jsonLogging]]log.jsonLogging::
 +
-If set to true, enables error, ssh and http logging in JSON format (file names:
+If set to `true`, enables error, ssh and http logging in JSON format (file names:
 `logs/error_log.json`, `logs/sshd_log.json` and `logs/httpd_log.json`).
 +
 The option only applies to Gerrit built-in loggers. It is ignored when a log4j
@@ -4299,11 +4321,11 @@
 link:#container.javaOptions[container.javaOptions], for example
 `-Dlog4j.configuration=file://etc/log4j.properties`.
 +
-Defaults to false.
+Defaults to `false`.
 
 [[log.textLogging]]log.textLogging::
 +
-If set to true, enables error logging in regular plain text format. Can only be disabled
+If set to `true`, enables error logging in regular plain text format. Can only be disabled
 if `jsonLogging` is enabled.
 +
 The option only applies to Gerrit built-in loggers. It is ignored when a log4j
@@ -4311,20 +4333,20 @@
 link:#container.javaOptions[container.javaOptions], for example
 `-Dlog4j.configuration=file://etc/log4j.properties`.
 +
-Defaults to true.
+Defaults to `true`.
 
 [[log.compress]]log.compress::
 +
-If set to true, log files are compressed at server startup and then daily at 11pm
+If set to `true`, log files are compressed at server startup and then daily at 11pm
 (in the server's local time zone).
 +
-Defaults to true.
+Defaults to `true`.
 
 [[log.rotate]]log.rotate::
 +
-If set to true, log files are rotated daily at midnight (GMT).
+If set to `true`, log files are rotated daily at midnight (GMT).
 +
-Defaults to true.
+Defaults to `true`.
 
 [[metrics]]
 === Section metrics
@@ -4371,13 +4393,13 @@
 
 [[mimetype.name.safe]]mimetype.<name>.safe::
 +
-If set to true, files with the MIME type `<name>` will be sent as
+If set to `true`, files with the MIME type `<name>` will be sent as
 direct downloads to the user's browser, rather than being wrapped up
 inside of zipped archives.  The type name may be a complete type
 name, e.g. `image/gif`, a generic media type, e.g. `+image/*+`,
 or the wildcard `+*/*+` to match all types.
 +
-By default, false for all MIME types.
+By default, `false` for all MIME types.
 
 Common examples:
 ----
@@ -4442,16 +4464,16 @@
 
 [[oauth.allowEditFullName]]oauth.allowEditFullName::
 +
-If true, the full name can be edited in the contact information.
+If `true`, the full name can be edited in the contact information.
 +
-Default is false.
+Default is `false`.
 
 [[oauth.allowRegisterNewEmail]]oauth.allowRegisterNewEmail::
 +
-If true, additional email addresses can be registered in the contact
+If `true`, additional email addresses can be registered in the contact
 information.
 +
-Default is false.
+Default is `false`.
 
 [[operator-alias]]
 === Section operator alias
@@ -4497,7 +4519,7 @@
 
 [[pack.deltacompression]]pack.deltacompression::
 +
-If true, delta compression between objects is enabled.  This may
+If `true`, delta compression between objects is enabled.  This may
 result in a smaller overall transfer for the client, but requires
 more server memory and CPU time.
 +
@@ -4530,8 +4552,8 @@
 [[plugins.allowRemoteAdmin]]plugins.allowRemoteAdmin::
 +
 Enable remote installation, enable and disable of plugins over HTTP
-and SSH.  If set to true Administrators can install new plugins
-remotely, or disable existing plugins.  Defaults to false.
+and SSH.  If set to `true` Administrators can install new plugins
+remotely, or disable existing plugins.  Defaults to `false`.
 
 [[plugins.mandatory]]plugins.mandatory::
 +
@@ -4597,34 +4619,34 @@
 
 [[receive.checkMagicRefs]]receive.checkMagicRefs::
 +
-If true, Gerrit will verify the destination repository has
+If `true`, Gerrit will verify the destination repository has
 no references under the magic 'refs/for' branch namespace. Names under
 these locations confuse clients when trying to upload code reviews so
 Gerrit requires them to be empty.
 +
-If false Gerrit skips the sanity check and assumes administrators
+If `false` Gerrit skips the sanity check and assumes administrators
 have ensured the repository does not contain any magic references.
-Setting to false to skip the check can decrease latency during push.
+Setting to `false` to skip the check can decrease latency during push.
 +
-Default is true.
+Default is `true`.
 
 [[receive.allowProjectOwnersToChangeParent]]receive.allowProjectOwnersToChangeParent::
 +
-If true, Gerrit will allow project owners to change the parent of a project.
+If `true`, Gerrit will allow project owners to change the parent of a project.
 +
 By default only Gerrit administrators are allowed to change the parent
 of a project. By allowing project owners to change parents, it may
 allow the owner to circumvent certain enforced rules (like important
 BLOCK rules).
 +
-Default is false.
+Default is `false`.
 +
 This value supports configuration reloads:
 link:cmd-reload-config.html[reload-config]
 
 [[receive.checkReferencedObjectsAreReachable]]receive.checkReferencedObjectsAreReachable::
 +
-If set to true, Gerrit will validate that all referenced objects that
+If set to `true`, Gerrit will validate that all referenced objects that
 are not included in the received pack are reachable by the user.
 +
 Carrying out this check on gits with many refs and commits can be a
@@ -4634,11 +4656,11 @@
 Only disable this check if you trust the clients not to forge SHA-1
 references to access commits intended to be hidden from the user.
 +
-Default is true.
+Default is `true`.
 
 [[receive.enableInMemoryRefCache]]receive.enableInMemoryRefCache::
 +
-If true, Gerrit will cache all refs advertised during push in memory and
+If `true`, Gerrit will cache all refs advertised during push in memory and
 base later receive operations on that cache.
 +
 Turning this cache off is considered experimental.
@@ -4647,17 +4669,17 @@
 offer an inverse lookup of object ID to ref name. When RefTable is used,
 this cache can be turned off (experimental) to get speed improvements.
 +
-Default is true.
+Default is `true`.
 
 [[receive.enableSignedPush]]receive.enableSignedPush::
 +
-If true, server-side signed push validation is enabled.
+If `true`, server-side signed push validation is enabled.
 +
 When a client pushes with `git push --signed`, this ensures that the
 push certificate is valid and signed with a valid public key stored in
 the `refs/meta/gpg-keys` branch of `All-Users`.
 +
-Defaults to false.
+Defaults to `false`.
 
 [[receive.maxBatchChanges]]receive.maxBatchChanges::
 +
@@ -4708,7 +4730,7 @@
 value is inherited from the parent project. When `true`, the value is
 inherited, otherwise it is not inherited.
 +
-Default is false, the value is not inherited.
+Default is `false`, the value is not inherited.
 
 [[receive.maxTrustDepth]]receive.maxTrustDepth::
 +
@@ -4907,18 +4929,18 @@
 Whether Gerrit should automatically retry operations on failure with tracing
 enabled. The automatically generated traces can help with debugging.
 +
-By default this is set to false.
+By default this is set to `false`.
 
 [[rules]]
 === Section rules
 
 [[rules.enable]]rules.enable::
 +
-If true, Gerrit will load and execute 'rules.pl' files in each
-project's refs/meta/config branch, if present. When set to false,
+If `true`, Gerrit will load and execute 'rules.pl' files in each
+project's refs/meta/config branch, if present. When set to `false`,
 only the default internal rules will be used.
 +
-Default is true, to execute project specific rules.
+Default is `true`, to execute project specific rules.
 
 [[rules.reductionLimit]]rules.reductionLimit::
 +
@@ -4951,7 +4973,7 @@
 source files may need a larger rules.compileReductionLimit.  Consider
 using link:pgm-rulec.html[rulec] to precompile larger rule files.
 +
-A size of 0 bytes disables rules, same as rules.enable = false.
+A size of 0 bytes disables rules, same as rules.enable = `false`.
 +
 Common unit suffixes of 'k', 'm', or 'g' are supported.
 +
@@ -5045,7 +5067,7 @@
 email is delivered to the inbox. In this case, Gerrit will process the email
 immediately and will not have a fetch delay.
 +
-Defaults to false.
+Defaults to `false`.
 
 [[receiveemail.filter.mode]]receiveemail.filter.mode::
 +
@@ -5079,18 +5101,18 @@
 
 [[sendemail.enable]]sendemail.enable::
 +
-If false Gerrit will not send email messages, for any reason,
+If `false` Gerrit will not send email messages, for any reason,
 and all other properties of section sendemail are ignored.
 +
-By default, true, allowing notifications to be sent.
+By default, `true`, allowing notifications to be sent.
 
 [[sendemail.html]]sendemail.html::
 +
-If false, Gerrit will only send plain-text emails.
-If true, Gerrit will send multi-part emails with an HTML and
+If `false`, Gerrit will only send plain-text emails.
+If `true`, Gerrit will send multi-part emails with an HTML and
 plain text part.
 +
-By default, true, allowing HTML in the emails Gerrit sends.
+By default, `true`, allowing HTML in the emails Gerrit sends.
 
 [[sendemail.connectTimeout]]sendemail.connectTimeout::
 +
@@ -5182,11 +5204,11 @@
 
 [[sendemail.sslVerify]]sendemail.sslVerify::
 +
-If false and sendemail.smtpEncryption is 'ssl' or 'tls', Gerrit
+If `false` and sendemail.smtpEncryption is 'ssl' or 'tls', Gerrit
 will not verify the server certificate when it connects to send
 an email message.
 +
-By default, true, requiring the certificate to be verified.
+By default, `true`, requiring the certificate to be verified.
 
 [[sendemail.smtpUser]]sendemail.smtpUser::
 +
@@ -5223,12 +5245,12 @@
 
 [[sendemail.includeDiff]]sendemail.includeDiff::
 +
-If true, new change emails and merged change emails from Gerrit
+If `true`, new change emails and merged change emails from Gerrit
 will include the complete unified diff of the change.
 Variable maxmimumDiffSize places an upper limit on how large the
 email can get when this option is enabled.
 +
-By default, false.
+By default, `false`.
 
 [[sendemail.maximumDiffSize]]sendemail.maximumDiffSize::
 +
@@ -5278,12 +5300,12 @@
 
 [[sendemail.addInstanceNameInSubject]]sendemail.addInstanceNameInSubject::
 +
-When set to true, Gerrit will add its short name to the email subject, allowing recipients to quickly identify
+When set to `true`, Gerrit will add its short name to the email subject, allowing recipients to quickly identify
 what Gerrit instance the email came from.
 +
 The short name can be customized via the gerrit.instanceName option.
 +
-Defaults to false.
+Defaults to `false`.
 
 
 [[site]]
@@ -5303,9 +5325,9 @@
 
 [[site.refreshHeaderFooter]]site.refreshHeaderFooter::
 +
-If true the server checks the site header, footer and CSS files for
-updated versions. If false, a server restart is required to change
-any of these resources. Default is true, allowing automatic reloads.
+If `true` the server checks the site header, footer and CSS files for
+updated versions. If `false`, a server restart is required to change
+any of these resources. Default is `true`, allowing automatic reloads.
 
 [[ssh-alias]]
 === Section ssh-alias
@@ -5391,7 +5413,7 @@
 
 [[sshd.tcpKeepAlive]]sshd.tcpKeepAlive::
 +
-If true, enables TCP keepalive messages to the other side, so
+If `true`, enables TCP keepalive messages to the other side, so
 the daemon can terminate connections if the peer disappears.
 +
 Only effective when `sshd.backend` is set to `MINA`.
@@ -5728,7 +5750,7 @@
 If link:access-control.html#service_users[service users] should be skipped when
 suggesting reviewers.
 +
-By default true.
+By default `true`.
 
 [[tracing]]
 === Section tracing
@@ -5748,7 +5770,7 @@
 heap increase wasn't nearly as dramatic and the impact is most likely
 dependent on which plugin is used.
 +
-By default, false.
+By default, `false`.
 
 [[tracing.traceid]]
 ==== Subsection tracing.<trace-id>
@@ -6040,7 +6062,7 @@
 
 Note that the task will only be scheduled if the
 link:#autoUpdateAccountActiveStatus[auth.autoUpdateAccountActiveStatus]
-is set to true.
+is set to `true`.
 
 link:#schedule-configuration-examples[Schedule examples] can be found
 in the link:#schedule-configuration[Schedule Configuration] section.
@@ -6066,7 +6088,7 @@
 +
 This allows to enable the superproject subscription mechanism.
 +
-By default this is true.
+By default this is `true`.
 
 [[submodule.maxCombinedCommitMessageSize]]submodule.maxCombinedCommitMessageSize::
 +
diff --git a/Documentation/config-labels.txt b/Documentation/config-labels.txt
index ce63295..29d1b85 100644
--- a/Documentation/config-labels.txt
+++ b/Documentation/config-labels.txt
@@ -24,7 +24,7 @@
 informed about this by a message in the git output. In addition,
 outdated votes are also listed in the email notification that is sent
 for the new patch set (unless this is disabled by a custom email
-template). Note, that the uploader only get this email notification if
+template). Note, that the uploader only gets this email notification if
 they have configured `Every Comment` for `Email notifications` in their
 user preferences. With any other email preference the email sender, the
 uploader in this case, is not included in the email recipients.
@@ -251,7 +251,7 @@
 change is allowed for submission. Label functions are **deprecated** and updates
 that set `function` to a blocking value {`MaxWithBlock`, `MaxNoBlock`,
 `AnyWithBlock`} will be rejected. Existing label function definitions can only
-be updated to {`NoBlock`, `NoOp`, `PatchSetLock`}. New label defintions should
+be updated to {`NoBlock`, `NoOp`, `PatchSetLock`}. New label definitions should
 also explicitly set the `function` attribute to a non-blocking value since the
 default is `MaxWithBlock`.
 
@@ -269,6 +269,7 @@
 
 Valid values are:
 
+[[MaxWithBlock]]
 * `MaxWithBlock` (default)
 +
 The lowest possible negative value, if present, blocks a submit, while
@@ -276,22 +277,26 @@
 must be at least one positive value, or else submit will never be
 enabled. To permit blocking submits, ensure a negative value is defined.
 
+[[AnyWithBlock]]
 * `AnyWithBlock`
 +
 The label is not mandatory but the lowest possible negative value,
 if present, blocks a submit. To permit blocking submits, ensure that a
 negative value is defined.
 
+[[MaxNoBlock]]
 * `MaxNoBlock`
 +
 The highest possible positive value is required to enable submit, but
 the lowest possible negative value will not block the change.
 
+[[NoBlock]]
 * `NoBlock`/`NoOp`
 +
 The label is purely informational and values are not considered when
 determining whether a change is submittable.
 
+[[PatchSetLock]]
 * `PatchSetLock`
 +
 The `PatchSetLock` function provides a locking mechanism for patch
diff --git a/Documentation/config-project-config.txt b/Documentation/config-project-config.txt
index 187cd0f..81f9d9f 100644
--- a/Documentation/config-project-config.txt
+++ b/Documentation/config-project-config.txt
@@ -1,95 +1,496 @@
-= Gerrit Code Review - Project Configuration File Format
+= Gerrit Code Review - Project Configuration
 
-This page explains the storage format of Gerrit's project configuration
-and access control models.
+Every project has a configuration that defines access rights and controls
+project-specific behavior.
 
-The web UI access control panel is a front end for human-readable
-configuration files under the +refs/meta/config+ namespace in the
-affected project.  Direct manipulation of these files is mainly
-relevant in an automation scenario of the access controls.
-
+The project configuration is stored inside the git repository inside the
+`refs/meta/config` branch.
 
 [[refs-meta-config]]
-== The +refs/meta/config+ namespace
+== The `refs/meta/config` branch
 
-The namespace contains three different files that play different
-roles in the permission model.  With read permission to that reference,
-it is possible to fetch the +refs/meta/config+ reference to a local
-repository.  A nice side effect is that you can also upload changes
-to project permissions and review them just like with regular code
-changes. The preview changes option is also provided on the UI. Please note
-that you will have to configure push rights for the +refs/meta/config+ name
-space if you'd like to use the possibility to automate permission updates.
+The `refs/meta/config` branch contains configuration files. It is disconnected
+from the normal branches under `refs/heads/` that contain the source code.
 
-== Property inheritance
+The files inside the `refs/meta/config` branch are versioned just like any
+other file in the repository. This means from the git history of the
+`refs/meta/config` branch it can be seen how the configuration changed
+over time and which configuration was active when.
 
-If a property is set to INHERIT, then the value from the parent project is
-used. If the property is not set in any parent project, the default value is
-FALSE.
+[[configuration-files]]
+== Configuration files
+
+The project configuration is stored in the following files (these files are
+stored inside the `refs/meta/config` branch, see
+link:#refs-meta-config[above]):
+
+* link:#file-project_config[project.config]:
+  Contains the link to the parent project, the project description, access
+  rights, label definitions, submit requirements and options to control
+  project-specific behavior.
+* link:#file-groups[groups]:
+  Resolves group names that are mentioned in `project.config` to group UUIDs.
+* [DEPRECATED] link:#file-rules_pl[rules.pl]:
+  Contains Prolog rules that control when a change becomes ready to submit.
+  Note, Prolog rules are deprecated and have been replaced by
+  link:config-submit-requirements.html[submit requirements].
+
+In addition, there can be configuration files from Gerrit plugins, usually they
+are named `<PLUGIN-NAME>.config`.
+
+[[update]]
+== Updating the project configuration
+
+There are several possibilities to update the project configuration:
+
+[[update-through-web-ui]]
+* Through the Gerrit web UI:
++
+[[update-through-general-screen]]
+--
+* On the project's `General` screen:
++
+The `General` screen can be found under:
+`BROWSE` > `Repositories` > `<project-name>` > `General`
++
+In the `Configurations` section this screen allows to edit the project
+description and change repository options.
++
+Note, this screen only supports changing the most important repository options,
+but it doesn't expose all options that exist.
++
+All changes are directly applied (without code review).
+--
++
+[[update-through-access-screen]]
+--
+* On the project's `Access` screen:
++
+The `Access` screen can be found under:
+`BROWSE` > `Repositories` > `<project-name>` > `Access`
++
+This screen allows to edit the project's access rights and the parent project.
++
+Updating access rights via the `Access` screen updates the
+link:#file-groups[groups] file automatically.
++
+Modifications can either be applied directly (`SAVE` button) or saved for
+review (`SAVE FOR REVIEW` button).
++
+Saving access modification for review (`SAVE FOR REVIEW` button) creates an
+open change for the `refs/meta/config` branch where the modifications can be
+reviewed by a project owner. The modifications become effective only after a
+project owner submits the change.
+--
++
+[[update-via-online-editing]]
+--
+** Via online editing:
++
+The project's `Commands` screen, that can be found under `BROWSE` >
+`Repositories` > `<project-name>` > `Commands`, offers an `EDIT REPO CONFIG`
+command that allows to edit the `project.config` file directly in the Gerrit
+web UI.
++
+The `EDIT REPO CONFIG` command creates a new work-in-progress change with a
+change edit that contains the `project.config` file. Clicking on the command
+opens an online editor for the `project.config` file, allowing the user to make
+modifications to it.
++
+Modifications need to be saved and published ("saving" saves the modifications
+in the change edit, "publishing" publishes the change edit to make it visible
+to other users). While the change edit is not published yet, it's possible to
+add further files to it (e.g. the link:#file-groups[groups] file if it needs to
+be modified, in contrast to making access rights changes through the `Access`
+screen the `groups` file is not automatically updated). For further details how
+to work with change edits see the link:user-inline-edit.html[inline edit user
+guide]. After publishing the change edit, the change should be set to ready by
+clicking on `START REVIEW` > `SEND AND START REVIEW`. At this time you also
+want to add a project owner as a reviewer so that they can review and approve
+the change.
+--
+
+[[update-by-git-push]]
+* By pushing updates via Git:
++
+Since the configuration files are stored in a git branch, it's possible to
+update them via normal git operations:
++
+--
+1. Clone the repository if you don't have it available yet. The clone command
+   can be found in the `Download` section of project's `General` screen
+   (`BROWSE` > `Repositories` > `<project-name>` > `General`).
+2. Fetch and checkout the `refs/meta/config branch`, e.g. by `git fetch origin
+   refs/meta/config && git checkout FETCH_HEAD`.
+3. Edit the `project.config` file or other configuration files and commit the
+   changes, e.g. by `git commit --all`. Note, since the `project.config` file
+   uses the format of a git config file you can also edit it via the
+   `git config` command (e.g. to set a project description do: `git config -f
+   project.config project.description "My project description"`).
+4. Push the newly created commit, either to update the `refs/meta/config`
+   branch directly without code-review (e.g. `git push origin
+   HEAD:refs/meta/config`), or for review (e.g. `git push origin
+   HEAD:refs/for/refs/meta/config`).
+--
++
+[NOTE]
+Updates to access right may require changes in the link:#file-groups[groups]
+file. In contrast to making access rights changes through the `Access` screen
+the `groups` file is not automatically updated.
+
+[[update-through-rest-api]]
+* Through the Gerrit REST API:
++
+Gerrit offers several REST endpoints to modify the project configuration:
++
+** link:rest-api-projects.html#set-config[Set Config] REST endpoint:
+   Allows to edit the project description and change repository options.
+** link:rest-api-projects.html#set-project-description[Set Project Description]
+   REST endpoint:
+   Allows to set the project description.
+** link:rest-api-projects.html#delete-project-description[Delete Project
+   Description] REST endpoint:
+   Allows to unset the project description.
+** link:rest-api-projects.html#set-access[Add, Update and Delete Access Rights
+   for Project] REST endpoint:
+   Allows to edit the access rights of the project.
+** link:rest-api-projects.html#create-access-change[Create Access Rights Change
+   for review] REST endpoint:
+   Allows to create a change with access right modifications that can be
+   reviewed and submitted by a project owner.
+** link:rest-api-projects.html#create-label[Create Label] REST endpoint:
+   Allows to create a new label definition.
+** link:rest-api-projects.html#set-label[Set Label] REST endpoint:
+   Allows to update an existing label definition.
+** link:rest-api-projects.html#delete-label[Delete Label] REST endpoint:
+   Allows to delete an existing label definition.
+** link:rest-api-projects.html#create-submit-requirement[Create Submit
+   Requirement] REST endpoint:
+   Allows to create a new submit requirement.
+** link:rest-api-projects.html#update-submit-requirement[Update Submit
+   Requirement] REST endpoint:
+   Allows to update an existing submit requirement.
+** link:rest-api-projects.html#delete-submit-requirement[Delete Submit
+   Requirement] REST endpoint:
+   Allows to delete an existing submit requirement.
+
+[[required-permissions]]
+== Required permissions
+
+Depending on how the project configuration is changed different access rights
+are required:
+
+* Direct updates through the web UI (link:#update-through-general-screen[
+  updates through the `General` screen] and link:#update-through-access-screen[
+  direct updates of access rights through the `Access` screen via the `SAVE`
+  button]) and direct updates through the REST API (via the
+  link:rest-api-projects.html#set-config[Set Config] REST endpoint or the
+  link:rest-api-projects.html#set-access[Add, Update and Delete Access Rights
+  for Project] REST endpoint) require the user to be a project owner (have the
+  link:access-control.html#category_owner[Owner] access right assigned on
+  `refs/*` or have the link:access-control.html#capability_administrateServer[
+  Administrate Server] global capability assigned on the `All-Projects` root
+  project).
+* link:#update-by-git-push[Direct updates through `git push`] require the
+  user to have the link:access-control.html#category_push[Push] access right
+  assigned on `refs/meta/config` and be a project owner (have the
+  link:access-control.html#category_owner[Owner] access right assigned on
+  `refs/*` or have the link:access-control.html#capability_administrateServer[
+  Administrate Server] global capability assigned on the `All-Projects` root
+  project).
+* Creating changes for updates through the web UI
+  (link:#update-through-access-screen[proposing updates of access rights
+  through the `Access` screen via the `SAVE FOR REVIEW` button] and
+  link:#update-via-online-editing[proposing updates via the `EDIT REPO CONFIG`
+  command]), creating changes for updates through the REST API (via the
+  link:rest-api-projects.html#create-access-change[Create Access Rights Change
+  for review] REST endpoint) and link:#update-by-git-push[pushing changes for
+  review] require the user to be able to see the `refs/meta/config` branch
+  (have the link:access-control.html#category_read[Read] access right assigned
+  on `refs/meta/config`) and be allowed to create changes for it (have the
+  link:access-control.html#category_push[Push] access right assigned on
+  `refs/for/refs/meta/config` or be a project owner or be an administrator).
+
+[[comments]]
+=== Comments in project configuration files
+
+In principle it's possible to have comments in the project configuration files
+(lines starting with '#'), however if any Gerrit API is used that lead to
+modifications in the configuration files the comments may be dropped. This is
+because when Gerrit parses the configuration files and writes them back with
+updates, comments are not preserved.
+
+[TIP]
+When updating the project configuration use the commit message to record the
+reason for the settings so that this information is preserved in the git
+history. For example, this allows using `git blame` to inspect why
+configuration values have been set.
+
+[[inheritance]]
+== Inheritance
+
+Projects in Gerrit are organized hierarchically in a tree with the
+`All-Projects` project as the root project. The parent project is defined in
+the link:#access.inheritFrom[access section] of the `project.config` file.
+
+Projects inherit access rights and options from their parent project, but not
+all options are inheritable. See the description of the options in the
+link:#file-project_config[project.config] file to learn whether they are
+inherited or not.
+
+Options with boolean values support a special `INHERIT` value to make them
+inherit the value that is set in the parent project.
+
+Some settings can be enforced for child projects (or if set on the
+`All-Projects` root project for all projects), e.g. access right restrictions
+via link:access-control#block[BLOCK rules] or
+link:config-submit-requirements.html#submit_requirement_can_override_in_child_projects[
+non-overridable] submit requirements.
+
+[NOTE]
+The parent project for an existing project can be changed via the
+link:update-through-access-screen[Access] screen (by default this is only
+allowed for administrators).
+
+[NOTE]
+Project owners can be allowed to change the parent of projects that they own
+(see link:config-gerrit.html#receive.allowProjectOwnersToChangeParent[
+receive.allowProjectOwnersToChangeParent] setting which is `false` by default).
+In this case project owners may escape the settings that are enforced by their
+parent project by choosing a different parent project.
 
 [[file-project_config]]
-== The file +project.config+
+== The file `project.config`
 
-The +project.config+ file contains the link between groups and their
-permitted actions on reference patterns in this project and any projects
-that inherit its permissions.
+The `project.config` file contains the link to the parent project, the project
+description, access rights, link:config-labels.html[label definitions],
+link:config-submit-requirements.html[submit requirements] and options to
+control project-specific behavior.
 
-The format in this file corresponds to the Git config file format, so
-if you want to automate your permissions it is a good idea to use the
-+git config+ command when writing to the file. This way you know you
-don't accidentally break the format of the file.
+The format in this file corresponds to the Git config file format.
 
-Here follows a +git config+ command example:
+[TIP]
+--
+Since the format of the `project.config` file adheres to the Git config file
+format, utilizing the `git config` command when modifying the file is
+recommended. This way you can avoid breaking the format of the file
+accidentally.
+
+Here is an example of a `git config` command that updates the project
+description:
 
 ----
-$ git config -f project.config project.description "Rights inherited by all other projects"
+$ git config -f project.config project.description "Foo Bar"
 ----
+--
 
-Below you will find an example of the +project.config+ file format:
+This is an example of a `project.config` file:
 
 ----
 [project]
-       description = Rights inherited by all other projects
+  description = Collection of scripts for setting up foo bar.
 [access "refs/*"]
-       read = group Administrators
+  read = group Registered Users
 [access "refs/heads/*"]
-        label-Your-Label-Here = -1..+1 group Administrators
-[capability]
-       administrateServer = group Administrators
+  label-Code-Review = -2..+2 group Maintainers
+  label-Code-Review = -1..+1 group Registered Users
+  label-Your-Label = -1..+1 group Maintainers
 [receive]
-       requireContributorAgreement = false
-[label "Your-Label-Here"]
-        function = MaxWithBlock
-        value = -1 Your -1 Description
-        value =  0 Your No score Description
-        value = +1 Your +1 Description
+  requireChangeId = true
+[label "Your-Label"]
+  function = NoOp
+  value = -1 Your -1 Description
+  value =  0 Your No score Description
+  value = +1 Your +1 Description
+[submit-requirement "Your-Label"]
+  description = At least one maximum vote for label 'Your-Label' is required
+  applicableIf = -branch:refs/meta/config
+  submittableIf = label:Your-Label=MAX AND -label:Your-Label=MIN
 ----
 
-As you can see, there are several sections.
+The sections that can appear in the `project.config` file are explained below.
 
-The link:#project-section[+project+ section] appears once per project.
+[[access-section]]
+=== Access section
 
-The link:#access-subsection[+access+ section] appears once per reference pattern,
-such as `+refs/*+` or `+refs/heads/*+`. Only one access section per pattern is
-allowed.
+[[access.inheritFrom]]access.inheritFrom::
++
+Name of the parent project from which access rights are inherited.
++
+If not set, access rights are inherited from the `All-Projects` root project.
 
-The link:#receive-section[+receive+ section] appears once per project.
+[[access-subsection]]
+==== Access subsection
 
-The link:#submit-section[+submit+ section] appears once per project.
+`access` subsections define access rules for a ref or a ref namespace. The ref
+or ref namespace is specified as the subsection name and can be a concrete ref
+(e.g. `refs/heads/master`), a ref pattern (last path segment is '\*', e.g.
+`refs/heads/*`) or a regular expression (must start with '^', e.g.
+`^refs/heads/rel-.*`).
 
-The link:#capability-section[+capability+] section only appears once, and only
-in the +All-Projects+ repository.  It controls core features that are configured
-on a global level.
+[NOTE]
+For ref patterns '\*' can only appear as the last path segment. If a '*' is
+required in any other place the ref namespace must be specified as a regular
+expression (must start with '^', '\*' must follow what's being matched, e.g.
+".*" to match any string).
 
-The link:#label-section[+label+] section can appear multiple times. You can
-also redefine the text and behavior of the built in label types `Code-Review`
-and `Verified`.
+[NOTE]
+Only one access subsection per ref and per ref namespace is allowed.
 
-Optionally a +commentlink+ section can be added to define project-specific
-comment links. The +commentlink+ section has the same format as the
-link:config-gerrit.html#commentlink[+commentlink+ section in gerrit.config]
+The `access` subsections contain access rules that apply to the ref or ref
+namespace of the `access` subsections. The format of the access rules is: +
+`<accessCategoryId> = (block|deny)? <range>? group <group-name>`
+
+* `<accessCategoryId>`: ID of the link:access-control.html#access_categories[
+  access category] for which the access rule should be defined. The ID of the
+  access category is the name of the access category in lowerCamelCase (e.g.
+  `createTag`), except for label permissions where it is `label-<label-Name>`
+  (e.g. `label-Code-Review`).
+* `(block|deny)?`: `block` defines a link:access-control.html#block-rule[BLOCK]
+  rule, `deny` defines a link:access-control.html#deny-rule[DENY] rule, if
+  neither `block` or `deny` is specified an link:access-control.html#allow-rule[
+  ALLOW] rule is defined.
+* `<range>?`: Only set for label permission. The voting range in the format
+  `<min-vote>..<max-vote>` (e.g. `-1..+1`).
+* `group <group-name>`: The (local) name of the group to which the access rule
+  should apply (e.g. `group Foo Bar`). The (local) group name must exist in the
+  link:#file-groups[groups] file, so that Gerrit can resolve it to the group
+  UUID.
+
+To make access rules link:access-control.html#exclusive[exclusive] they need to
+be included into the value of the `exclusiveGroupPermissions` key: +
+`exclusiveGroupPermissions = <space-separated-list-of-access-category-ids>`
+
+.Example access subsections
+----
+  [access "refs/heads/*"]
+    create = group Administrators
+    delete = group Administrators
+    deleteChanges = group Administrators
+    label-Code-Review = -2..+2 group Maintainers
+    label-Code-Review = -1..+1 group Registered Users
+    label-Verified = -1..+1 group Registered Users
+    push = block Registered Users
+    submit = group Maintainers
+    exclusiveGroupPermissions = deleteChanges submit
+  [access "^refs/tags/rel-.*"]
+    createTag = group Maintainers
+    createSignedTag = group Maintainers
+----
+
+[[branchOrder-section]]
+=== branchOrder section
+
+Defines a branch ordering which is used for backporting of changes.
+Backporting will be offered for a change (in the Gerrit UI) for all
+more stable branches where the change can merge cleanly.
+
+[[branchOrder.branch]]branchOrder.branch::
+
+A branch name, typically multiple values will be defined. The order of branch
+names in this section defines the branch order. The topmost is considered to be
+the least stable branch (typically the master branch) and the last one the
+most stable (typically the last maintained release branch).
+
+.Example:
+----
+[branchOrder]
+  branch = master
+  branch = stable-2.9
+  branch = stable-2.8
+  branch = stable-2.7
+----
+
+The `branchOrder` section is inheritable. This is useful when multiple or all
+projects follow the same branch rules. A `branchOrder` section in a child
+project completely overrides any `branchOrder` section from a parent i.e. there
+is no merging of `branchOrder` sections. A present but empty `branchOrder`
+section removes all inherited branch order.
+
+Branches not listed in this section will not be included in the mergeability
+check. If the `branchOrder` section is not defined then the mergeability of a
+change into other branches will not be computed.
+
+[[capability-section]]
+=== Capability section
+
+The `capability` section only appears once, and only in the `project.config`
+file of the `All-Projects` root project. It controls Gerrit administration
+capabilities that are configured on a global level.
+
+.Example:
+----
+[capability]
+  administrateServer = group Administrators
+----
+
+[NOTE]
+The (local) group names for which capabilities are assigned must exist in the
+link:#file-groups[groups] file, so that Gerrit can resolve them to their group
+UUID.
+
+Please refer to the
+link:access-control.html#global_capabilities[Global Capabilities]
+documentation for a full list of available capabilities.
+
+[[change-section]]
+=== Change section
+
+The change section includes configuration for project-specific change settings:
+
+[[change.privateByDefault]]change.privateByDefault::
++
+Controls whether all new changes in the project are set as private by default.
++
+Note that a new change will be public if the `is_private` field in
+link:rest-api-changes.html#change-input[ChangeInput] is set to `false` explicitly
+when calling the link:rest-api-changes.html#create-change[CreateChange] REST API
+or the `remove-private` link:user-upload.html#private[PushOption] is used during
+the Git push.
++
+Default is `INHERIT`, which means that this property is inherited from
+the parent project.
+
+[[change.workInProgressByDefault]]change.workInProgressByDefault::
++
+Controls whether all new changes in the project are set as WIP by default.
++
+Note that a new change will be ready if the `workInProgress` field in
+link:rest-api-changes.html#change-input[ChangeInput] is set to `false` explicitly
+when calling the link:rest-api-changes.html#create-change[CreateChange] REST API
+or the `ready` link:user-upload.html#wip[PushOption] is used during
+the Git push.
++
+Default is `INHERIT`, which means that this property is inherited from
+the parent project.
+
+[[commentlink-section]]
+=== Commentlink section
+
+Optionally a `commentlink` section can be added to define project-specific
+comment links. The `commentlink` section has the same format as the
+link:config-gerrit.html#commentlink[commentlink] section in `gerrit.config`
 which is used to define global comment links.
 
+[[label-section]]
+=== Label section
+
+Please refer to link:config-labels.html#label_custom[Custom Labels] documentation.
+
+[[mimetype-section]]
+=== MIME Types section
+
+The +mimetype+ section may be configured to force the web code
+reviewer to return certain MIME types by file path. MIME types
+may be used to activate syntax highlighting.
+
+----
+[mimetype "text/x-c"]
+  path = *.pkt
+[mimetype "text/x-java"]
+  path = api/current.txt
+----
+
 [[project-section]]
 === Project section
 
@@ -281,36 +682,31 @@
 where a commit is already merged into a branch and you want to create
 a new open change for that commit on another branch.
 
-[[change-section]]
-=== Change section
+[[reviewer-section]]
+=== reviewer section
 
-The change section includes configuration for project-specific change settings:
+Defines config options to adjust a project's reviewer workflow such as enabling
+reviewers and CCs by email.
 
-[[change.privateByDefault]]change.privateByDefault::
+[[reviewer.enableByEmail]]reviewer.enableByEmail::
 +
-Controls whether all new changes in the project are set as private by default.
+A boolean indicating if reviewers and CCs that do not currently have a Gerrit
+account can be added to a change by providing their email address.
 +
-Note that a new change will be public if the `is_private` field in
-link:rest-api-changes.html#change-input[ChangeInput] is set to `false` explicitly
-when calling the link:rest-api-changes.html#create-change[CreateChange] REST API
-or the `remove-private` link:user-upload.html#private[PushOption] is used during
-the Git push.
+This setting only takes effect for changes that are readable by anonymous users.
 +
 Default is `INHERIT`, which means that this property is inherited from
-the parent project.
+the parent project. If the property is not set in any parent project, the
+default value is `FALSE`.
 
-[[change.workInProgressByDefault]]change.workInProgressByDefault::
+[[reviewer.skipAddingAuthorAndCommitterAsReviewers]]reviewer.skipAddingAuthorAndCommitterAsReviewers::
 +
-Controls whether all new changes in the project are set as WIP by default.
-+
-Note that a new change will be ready if the `workInProgress` field in
-link:rest-api-changes.html#change-input[ChangeInput] is set to `false` explicitly
-when calling the link:rest-api-changes.html#create-change[CreateChange] REST API
-or the `ready` link:user-upload.html#wip[PushOption] is used during
-the Git push.
+Whether to skip adding the Git commit author and committer as reviewers for
+a new change.
 +
 Default is `INHERIT`, which means that this property is inherited from
-the parent project.
+the parent project. If the property is not set in any parent project, the
+default value is `FALSE`.
 
 [[submit-section]]
 === Submit section
@@ -457,9 +853,9 @@
 unless merge commits are being submitted. For some people this is an advantage
 since they find the linear history easier to read.
 +
-NOTE: Rebasing merge commits is not supported. If a change with a merge commit
-is submitted the link:#merge_if_necessary[merge if necessary] submit action is
-applied.
+NOTE: Rebasing merge commits is done by rebasing their first parent commit,
+i.e. the first parent is updated to the new base while the second parent stays
+intact.
 
 [[rebase_always]]
 * 'rebase always':
@@ -476,9 +872,9 @@
 unless merge commits are being submitted. For some people this is an advantage
 since they find the linear history easier to read.
 +
-NOTE: Rebasing merge commits is not supported. If a change with a merge commit
-is submitted the link:#merge_if_necessary[merge if necessary] submit action is
-applied.
+NOTE: Rebasing merge commits is done by rebasing their first parent commit,
+i.e. the first parent is updated to the new base while the second parent stays
+intact.
 +
 When rebasing the patchset, Gerrit automatically appends onto the end of the
 commit message a short summary of the change's approvals, and a URL link back
@@ -634,132 +1030,32 @@
 is set to 'true' the merge would fail in such a case. An empty commit is still allowed as the
 initial commit on a branch.
 
-
-[[access-section]]
-=== Access section
-
-[[access.inheritFrom]]access.inheritFrom::
-+
-Name of the parent project from which access rights are inherited.
-+
-If not set, access rights are inherited from the `All-Projects` root project.
-
-[[access-subsection]]
-==== Access subsection
-
-+access+ subsections for references connect access rights to groups. Each group
-listed must exist in the link:#file-groups[+groups+ file].
-
-Please refer to the
-link:access-control.html#access_categories[Access Categories]
-documentation for a full list of available access rights.
-
-
-[[mimetype-section]]
-=== MIME Types section
-
-The +mimetype+ section may be configured to force the web code
-reviewer to return certain MIME types by file path. MIME types
-may be used to activate syntax highlighting.
-
-----
-[mimetype "text/x-c"]
-  path = *.pkt
-[mimetype "text/x-java"]
-  path = api/current.txt
-----
-
-
-[[capability-section]]
-=== Capability section
-
-The +capability+ section only appears once, and only in the +All-Projects+
-repository.  It controls Gerrit administration capabilities that are configured
-on a global level.
-
-Please refer to the
-link:access-control.html#global_capabilities[Global Capabilities]
-documentation for a full list of available capabilities.
-
-[[label-section]]
-=== Label section
-
-Please refer to link:config-labels.html#label_custom[Custom Labels] documentation.
-
 [[submit-requirement-section]]
 === Submit Requirement section
 
 Please refer to link:config-submit-requirements.html[Configuring Submit
 Requirements] documentation.
 
-[[branchOrder-section]]
-=== branchOrder section
-
-Defines a branch ordering which is used for backporting of changes.
-Backporting will be offered for a change (in the Gerrit UI) for all
-more stable branches where the change can merge cleanly.
-
-[[branchOrder.branch]]branchOrder.branch::
-+
-A branch name, typically multiple values will be defined. The order of branch
-names in this section defines the branch order. The topmost is considered to be
-the least stable branch (typically the master branch) and the last one the
-most stable (typically the last maintained release branch).
-+
-Example:
-+
-----
-[branchOrder]
-  branch = master
-  branch = stable-2.9
-  branch = stable-2.8
-  branch = stable-2.7
-----
-+
-The `branchOrder` section is inheritable. This is useful when multiple or all
-projects follow the same branch rules. A `branchOrder` section in a child
-project completely overrides any `branchOrder` section from a parent i.e. there
-is no merging of `branchOrder` sections. A present but empty `branchOrder`
-section removes all inherited branch order.
-+
-Branches not listed in this section will not be included in the mergeability
-check. If the `branchOrder` section is not defined then the mergeability of a
-change into other branches will not be done.
-
-[[reviewer-section]]
-=== reviewer section
-
-Defines config options to adjust a project's reviewer workflow such as enabling
-reviewers and CCs by email.
-
-[[reviewer.enableByEmail]]reviewer.enableByEmail::
-+
-A boolean indicating if reviewers and CCs that do not currently have a Gerrit
-account can be added to a change by providing their email address.
-+
-This setting only takes effect for changes that are readable by anonymous users.
-+
-Default is `INHERIT`, which means that this property is inherited from
-the parent project. If the property is not set in any parent project, the
-default value is `FALSE`.
-
-[[reviewer.skipAddingAuthorAndCommitterAsReviewers]]reviewer.skipAddingAuthorAndCommitterAsReviewers::
-+
-Whether to skip adding the Git commit author and committer as reviewers for
-a new change.
-+
-Default is `INHERIT`, which means that this property is inherited from
-the parent project. If the property is not set in any parent project, the
-default value is `FALSE`.
-
 [[file-groups]]
-== The file +groups+
+== The file `groups`
 
-Each group in this list is linked with its UUID so that renaming of
-groups is possible without having to rewrite every +groups+ file
-in every repository where it's used.
+The `groups` file resolves group names that are mentioned in
+link:#access-subsection[access subsections] of the link:#file-project_config[
+project.config] file to group UUIDs.
 
-This is what the default groups file for +All-Projects.git+ looks like:
+In the access subsections access rights are assigned to group names. These
+group names are local to the project configuration and do not need to match
+with the actual group names. To enable Gerrit to resolve the local group names
+they must be mapped to group UUIDs in the `groups` file.
+
+The access sections use local group names, rather than requiring the actual
+group names, to allow renaming groups in Gerrit without having to rewrite every
+`project.config` file using the group.
+
+The content of the `groups` file is a simple table of group UUID to group name,
+separated by a tab.
+
+This is how the default `groups` file for `All-Projects` project looks like:
 
 ----
 # UUID                                         Group Name
@@ -771,29 +1067,37 @@
 global:Registered-Users                        Registered Users
 ----
 
-This file can't be written to by the +git config+ command.
+Since the `groups` file has a custom format it can't be edited using the
+`git config` command.
 
-In order to reference a group in +project.config+, it must be listed in
-the +groups+ file.  When editing permissions through the web UI this
-file is maintained automatically, but when pushing updates to
-+refs/meta/config+ this must be dealt with by hand.  Gerrit will refuse
-+project.config+ files that refer to groups not listed in +groups+.
+Whenever access rights in the `project.config` file are assigned to new groups
+mapping entries for the new groups must be added to the `groups` file. The
+modifications to the `groups` file must be included in the same commit that
+updates the `project.config` file. Pushing updates to `project.config` files
+that refer to groups not listed in the `groups` file are rejected by Gerrit.
 
-The UUID of a group can be found on the General tab of the group's page
-in the web UI or via the +-v+ option to
-link:cmd-ls-groups.html[the +ls-groups+ SSH command].
+When link:#update-through-access-screen[editing access rights through the web
+UI] the `groups` file is automatically updated by Gerrit.
 
+The UUID of a group can be found on the group screen (`BROWSE` > `Groups` >
+`<group-name>` ). Alternatively the group can be looked up via the
+link:rest-api-groups.html#get-group[Get Group] REST endpoint (note that the
+`group-id` in the URL can be the group name). The group UUID is contained as
+`id` field in the return link:rest-api-groups.html#group-info[GroupInfo] JSON.
 
 [[file-rules_pl]]
-== The file +rules.pl+
+== The file `rules.pl`
 
-The +rules.pl+ files allows you to replace or amend the default Prolog
-rules that control e.g. what conditions need to be fulfilled for a
-change to be submittable.  This file content should be
-interpretable by the 'Prolog Cafe' interpreter.
+The `rules.pl` file allows to replace or amend the default Prolog rules that
+control what conditions need to be fulfilled for a change to be submittable.
+This file should be interpretable by the 'Prolog Cafe' interpreter.
 
-You can read more about the +rules.pl+ file and the prolog rules on
-link:prolog-cookbook.html[the Prolog cookbook page].
+You can read more about prolog rules on the link:prolog-cookbook.html[Prolog
+cookbook] page.
+
+[NOTE]
+Prolog rules are deprecated and have been replaced by
+link:config-submit-requirements.html[submit requirements].
 
 GERRIT
 ------
diff --git a/Documentation/config-submit-requirements.txt b/Documentation/config-submit-requirements.txt
index 1bcda63..5ab1add 100644
--- a/Documentation/config-submit-requirements.txt
+++ b/Documentation/config-submit-requirements.txt
@@ -1,32 +1,375 @@
 = Gerrit Code Review - Submit Requirements
 
-As part of the code review process, project owners need to configure rules that
-govern when changes become submittable. For example, an admin might want to
-prevent changes from being submittable until at least a “+2” vote on the
-“Code-Review” label is granted on a change. Admins can define submit
-requirements to enforce submittability rules for changes.
+Submit requirements are rules that define when a change can be submitted. This
+page describes how to configure them.
 
 [[configuring_submit_requirements]]
 == Configuring Submit Requirements
 
-Site administrators and project owners can define submit requirements in the
-link:config-project-config.html[project config]. A submit requirement has the
-following fields:
+Submit requirements are defined as link:#submit-requirement-subsection[
+submit-requirement] subsections in the
+link:config-project-config.html#file-project_config[project.config] file. The
+subsection name defines the name of the submit requirement.
 
+[NOTE]
+There are multiple options how to update `project.config` files, please refer
+to the link:config-project-config.html#update[project config documentation].
 
-[[submit_requirement_name]]
-=== submit-requirement.Name
+[TIP]
+When modifying submit requirements it's recommended to
+link:#test-submit-requirements[test] them before updating them in the project
+configuration.
 
-A name that uniquely identifies the submit requirement. Submit requirements
-can be overridden in child projects if they are defined with the same name in
-the child project. See the link:#inheritance[inheritance] section for more
-details.
+[WARNING]
+--
+When adding submit requirements think about whether they should apply to the
+link:config-project-config.html#refs-meta-config[refs/meta/config] branch
+(see the link:#submit_requirement_applicable_if[applicableIf] description on
+how to exempt the `refs/meta/config` branch from a submit requirement). Since
+submit requirements are stored as part of the project configuration in the
+`refs/meta/config` branch, changing them through code review requires to pass
+the submit requirements that apply to the `refs/meta/config` branch. Hence by
+misconfiguring submit requirements for the `refs/meta/config` branch you can
+make further updates to submit requirements through code review impossible.
+If this happens the submit requirements can be restored by a direct push to the
+`refs/meta/config` branch.
+
+[[restore-submit-requirements]]
+If direct pushes are disabled or not allowed project owners can directly update
+the submit requirements via the
+link:rest-api-projects.html#update-submit-requirement[Update Submit Requirement]
+REST endpoint.
+
+.Example:
+----
+  curl -X PUT --header "Content-Type: application/json" -d '{"name": "Foo-Review", "description": "At least one maximum vote for the Foo-Review label is required", "submittability_expression": "label:Foo-Review=MAX AND -label:Foo-Review=MIN", "applicability_expression": "-branch:refs/meta/config", "canOverrideInChildProjects": true}' "https://<HOST>/a/projects/My%2FProject/submit_requirements/Foo-Review"
+----
+
+Tip: Googlers should use `gob-curl` instead of `curl` so that authentication is
+handled automatically.
+--
+
+[[test-submit-requirements]]
+=== Testing Submit Requirements
+
+When modifying submit requirements it's recommended to test them before
+updating them in the project configuration.
+
+To test a submit requirement on a selected change
+link:rest-api-changes.html#change-id[project\~branch~changeId] use the
+link:rest-api-changes.html#check-submit-requirement[Check Submit Requirement]
+REST endpoint.
+
+.Request
+----
+  POST /changes/myProject~master~I8473b95934b5732ac55d26311a706c9c2bde9940/check.submit_requirement HTTP/1.0
+  Content-Type: application/json; charset=UTF-8
+
+    {
+      "name": "Code-Review",
+      "submittability_expression": "label:Code-Review=+2"
+    }
+----
+
+.Response
+----
+  HTTP/1.1 200 OK
+  Content-Disposition: attachment
+  Content-Type: application/json; charset=UTF-8
+
+  )]}'
+  {
+    "name": "Code-Review",
+    "status": "SATISFIED",
+    "submittability_expression_result": {
+      "expression": "label:Code-Review=+2",
+      "fulfilled": true,
+      "passingAtoms": [
+        "label:Code-Review=+2"
+      ]
+    },
+    "is_legacy": false
+  }
+----
+
+Alternatively you can make a change that updates a submit requirement in the
+`project.config` file, upload it for review to the `refs/meta/config` branch
+and then load it from that change which is in review to test it against a
+change.
+
+Request
+----
+  POST /changes/myProject~master~I8473b95934b5732ac55d26311a706c9c2bde9940/check.submit_requirement?sr-name=Code-Review&refs-config-change-id=myProject~refs/meta/config~Ibc1409aef8bf0a16a76f9fa9a928bd505228fa1d HTTP/1.0
+----
+
+In the above example `myProject\~master~I8473b95934b5732ac55d26311a706c9c2bde9940`
+is the change against which the submit requirement is tested, `Code Review` is
+the name of the submit requirement that is tested and
+`myProject\~refs/meta/config~Ibc1409aef8bf0a16a76f9fa9a928bd505228fa1d` is the
+change from which the `Code Review` submit requirement is loaded. Change
+`myProject~refs/meta/config~Ibc1409aef8bf0a16a76f9fa9a928bd505228fa1d` must be
+a change that touches the `project.config` file in the `refs/meta/config`
+branch and the `project.config` file must contain a submit requirement with the
+name `Code-Review`.
+
+[[dashboard]]
+=== Show Submit Requirements on Dashboards
+
+Gerrit offers dashboards that provide an overview over a set of changes (e.g.
+user dashboards shows changes that are relevant to the user, change list
+dashboards show changes that match a change query). To understand the state of
+the changes knowing the status of their submit requirements is important, but
+submit requirements are manifold and dashboards have only limited screen space
+available, so showing all submit requirements in dashboards is hardly possible.
+This is why administrators must decide which are the most important submit
+requirements that should be shown on dashboards. They can configure these
+submit requirements in `gerrit.config` by setting the
+link:config-gerrit.html#dashboard[dashboard.submitRequirementColumns] option.
+
+[NOTE]
+In order to save screen space submit requirement names on dashboards are
+abbreviated, e.g. a submit requirement called `Foo-Bar` is shown as `FB`.
+
+[[inheritance]]
+== Inheritance
+
+Submit requirements are inherited from parent projects. Child projects may
+override an inherited submit requirement by defining a submit requirement with
+the same name, but only if overriding the submit requirement is allowed (see
+link:#submit_requirement_can_override_in_child_projects[
+canOverrideInChildProjects] field). Overriding an inherited submit requirement
+always overrides the complete submit requirement definition, overriding single
+fields only is not possible.
+
+[NOTE]
+To remove an inherited submit requirement in a child project, set both the
+link:#submit_requirement_applicable_if[applicableIf] expression and the
+link:#submit_requirement_submittable_if[submittableIf] expression to
+`is:false`.
+
+[NOTE]
+If overriding a submit requirement is disallowed in a parent project, submit
+requirements with the same name in child projects, that would otherwise
+override the inherited submit requirement, are ignored.
+
+[[labels]]
+== Labels and Submit Requirements
+
+link:config-labels.html[Labels] define voting categories for reviewers to score
+changes. Often a label is accompanied by a submit requirement to check the votes
+on the label, e.g. with a link:#submit_requirement_submittable_if[submittableIf]
+expression that checks that:
+
+* the label was approved: `label:My-Label=MAX`
+* the label has no veto: `-label:My-Label=MIN`
+* the label was not self-approved: `label:My-Label=MAX,user=non_uploader`
+* the label was approved by multiple users: `label:My-Label,count>1`
+
+Submit requirements that check votes for a single label often have the same
+name as the label, e.g.:
+
+----
+[label "Code-Review"]
+  function = NoBlock
+  value = -2 This shall not be submitted
+  value = -1 I would prefer this is not merged as is
+  value = 0 No score
+  value = +1 Looks good to me, but someone else must approve
+  value = +2 Looks good to me, approved
+  defaultValue = 0
+[submit-requirement "Code-Review"]
+  description = At least one maximum vote for label 'Code-Review' is required
+  submittableIf = label:Code-Review=MAX,user=non_uploader AND -label:Code-Review=MIN
+  canOverrideInChildProjects = true
+----
+
+[[trigger-votes]]
+=== Trigger Votes
+
+Trigger votes are votes on labels that are not associated with any submit
+requirement expressions, i.e. the submittability of changes doesn't depend on
+these votes.
+
+Voting on labels that have no impact on the submittability of changes usually
+serves the purpose to trigger processes, e.g. a vote on a `Presubmit-Ready`
+label can be a signal to run presubmit integration tests. Hence these votes are
+called `trigger votes`.
+
+Trigger votes are displayed in a separate section in the change page.
+
+[[deprecated]]
+== Deprecated ways to control when changes can be submitted
+
+Using submit requirements is the recommended way to control when changes can be
+submitted. However, historically there are other ways for this, which are still
+working, although they are deprecated:
+
+[[label-functions]]
+* Label functions:
++
+link:config-labels.html#label_custom[Label definitions] can contain a
+link:config-labels.html#label_function[function] that impacts the
+submittability of changes (link:config-labels.html#MaxWithBlock[MaxWithBlock],
+link:config-labels.html#AnyWithBlock[AnyWithBlock],
+link:config-labels.html#MaxNoBlock[MaxNoBlock]). These functions are deprecated
+and setting them is no longer allowed, however if they are (already) set for
+existing label definitions they are still respected. For new labels the
+function should be set to link:config-labels.html#NoBlock[NoBlock] and then
+submit requirements should be used to control when changes can be submitted
+(using `submittableIf = label:My-Label=MAX AND -label:My-Label=MIN` is
+equivalent to `MaxWithBlock`, using `submittableIf = -label:My-Label=MIN` is
+equivalent to `AnyWithBlock`, using `submittableIf = label:My-Label=MAX` is
+equivalent to using `MaxNoBlock`).
+
+[[ignoreSelfApproval]]
+* `ignoreSelfApproval` flag on labels:
++
+Labels can be configured to link:config-labels.html#label_ignoreSelfApproval[
+ignore self approvals]. This flag only works in combination with the deprecated
+label functions (see link:#label-functions[above]) and hence it is deprecated
+as well. Instead use a `submittableIf` expression with the
+link:#operator_label[label] operator and the `user=non_uploader` argument. See
+the link:#code-review-example[Code Review] submit requirement example.
+
+[[prolog-rules]]
+* Prolog rules:
++
+Projects can define link:prolog-cookbook.html[prolog submit rules] that control
+when changes can be submitted. It's still possible to have Prolog submit rules,
+but they are deprecated and support for them will be dropped in future Gerrit
+releases. Hence it's recommended to use submit requirements instead.
+
+When checking whether changes can be submitted Gerrit takes results of label
+functions and Prolog submit rules into account, in addition to the submit
+requirements.
+
+[[plugin-submit-rules]]
+== Plugin provided submit rules
+
+Plugins can contribute submit rules by implementing the `SubmitRule` extension
+point (see link:dev-plugins.html#pre-submit-evaluator[Pre-submit Validation
+Plugins]).
+
+When checking whether changes can be submitted Gerrit takes results of
+plugin-provided submit rules into account, in addition to the submit
+requirements.
+
+[[evaluation]]
+== Submit Requirement Evaluation
+
+Submit requirements are evaluated whenever a change is updated. To decide
+whether changes can be submitted, the results of link:#label-functions[label
+functions], link:#prolog-rules[Prolog submit rules] and
+link:#plugin-submit-rules[plugin-provided submit rules] are taken into account,
+in addition to the submit requirements. For this the results of label
+functions, Prolog submit rules and plugin-provided submit rules are converted
+to submit requirement results.
+
+Submit requirement results are returned in the REST API when retrieving changes
+with the link:rest-api-changes.html#submit-requirements[SUBMIT_REQUIREMENTS]
+option (e.g. via the link:rest-api-changes.html#get-change-detail[Get Change
+Detail] REST endpoint or the link:rest-api-changes.html#list-changes[Query
+Changes] REST endpoint). If requested, submit requirements are included as
+link:rest-api-changes.html#submit-requirement-result-info[
+SubmitRequirementResultInfo] entities into
+link:rest-api-changes.html#change-info[ChangeInfo] (field
+`submit_requirements`).
+
+The `status` field of submit requirement results can be one of:
+
+[[status-not-applicable]]
+* `NOT_APPLICABLE`
++
+The link:#submit_requirement_applicable_if[applicableIf] expression evaluates
+to false for the change.
+
+[[status-unsatisfied]]
+* `UNSATISFIED`
++
+The submit requirement is applicable
+(link:#submit_requirement_applicable_if[applicableIf] evaluates to true), but
+the evaluation of the link:#submit_requirement_submittable_if[submittableIf] and
+link:#submit_requirement_override_if[overrideIf] expressions return false for
+the change.
+
+[[status-satisfied]]
+* `SATISFIED`
++
+The submit requirement is applicable
+(link:#submit_requirement_applicable_if[applicableIf] evaluates to true), the
+link:#submit_requirement_submittable_if[submittableIf] expression evaluates to
+true, and the link:#submit_requirement_override_if[overrideIf] evaluates to
+false for the change.
+
+[[status-overridden]]
+* `OVERRIDDEN`
++
+The submit requirement is applicable
+(link:#submit_requirement_applicable_if[applicableIf] evaluates to true) and the
+link:#submit_requirement_override_if[overrideIf] expression evaluates to true.
++
+Note that in this case, the submit requirement is overridden regardless of
+whether the link:#submit_requirement_submittable_if[submittableIf] expression
+evaluates to true or not.
+
+[[status-forced]]
+* `FORCED`
++
+The change was merged directly bypassing code review by supplying the
+link:user-upload.html#auto_merge[submit] push option while doing a git push.
+
+[[status-error]]
+* `ERROR`
++
+The evaluation of any of the
+link:#submit_requirement_applicable_if[applicableIf],
+link:#submit_requirement_submittable_if[submittableIf] or
+link:#submit_requirement_override_if[overrideIf] expressions resulted in an
+error, i.e. because the expression is not parseable.
+
+[NOTE]
+Gerrit can be configured to return a `500 internal server error` response
+instead of setting the status to `ERROR` (see the
+link:config-gerrit.html#change.propagateSubmitRequirementErrors[
+change.propagateSubmitRequirementErrors] option that can be set in
+`gerrit.config`).
+
+[[submit-requirement-subsection]]
+== submit-requirement subsection
+
+Each `submit-requirement` subsection defines a submit requirement.
+
+The name of the `submit-requirement` subsection defines the name that uniquely
+identifies the submit requirement. It is shown to the user in the web UI when
+the submit requirement is applicable.
+
+[NOTE]
+By using the same name as an inherited submit requirement, the inherited submit
+requirement can be overridden, if overriding is allowed (see
+link:#submit_requirement_can_override_in_child_projects[
+canOverrideInChildProjects] field). Details about overriding submit
+requirements are explained in the link:#inheritance[inheritance] section.
+
+Submit requirements must at least define a
+link:#submit_requirement_submittable_if[submittableIf] expression that defines
+when a change can be submitted.
+
+.Example:
+----
+[submit-requirement "Verified"]
+  description = CI result status for build and tests is passing
+  applicableIf = -branch:refs/meta/config
+  submittableIf = label:Verified=MAX AND -label:Verified=MIN
+  canOverrideInChildProjects = true
+----
+
+The fields that can be set for submit requirements are explained below.
 
 [[submit_requirement_description]]
 === submit-requirement.Name.description
 
 A detailed description of what the submit requirement is supposed to do. This
-field is optional. The description is visible to the users in the change page
+field is optional. The description is visible to the user in the change page
 upon hovering on the submit requirement to help them understand what the
 requirement is about and how it can be fulfilled.
 
@@ -34,18 +377,21 @@
 === submit-requirement.Name.applicableIf
 
 A link:#query_expression_syntax[query expression] that determines if the submit
-requirement is applicable for a change. For example, administrators can exclude
-submit requirements for certain branch patterns. See the
-link:#exempt-branch-example[exempt branch] example.
+requirement is applicable for a change. If a submit requirement is not
+applicable it is hidden in the web UI. For example, this allows to
+link:#exempt-branch-example[exempt a branch] from the submit requirement.
 
+[TIP]
+--
 Often submit requirements should only apply to branches that contain source
-code. In this case this parameter can be used to exclude the
+code. In this case the `applicableIf` condition can be used to exclude the
 link:config-project-config.html#refs-meta-config[refs/meta/config] branch from
-a submit requirement:
+the submit requirement:
 
 ----
   applicableIf = -branch:refs/meta/config
 ----
+--
 
 This field is optional, and if not specified, the submit requirement is
 considered applicable for all changes in the project.
@@ -54,7 +400,7 @@
 === submit-requirement.Name.submittableIf
 
 A link:#query_expression_syntax[query expression] that determines when the
-change becomes submittable. This field is mandatory.
+change can be submitted. This field is mandatory.
 
 
 [[submit_requirement_override_if]]
@@ -64,9 +410,10 @@
 submit requirement is overridden. When this expression is evaluated to true,
 the submit requirement state becomes `OVERRIDDEN` and the submit requirement
 is no longer blocking the change submission.
+
 This expression can be used to enable bypassing the requirement in some
-circumstances, for example if the change owner is a power user or to allow
-change submission in case of emergencies. +
+circumstances, for example if the uploader is a trusted bot user or to allow
+change submission in case of emergencies.
 
 This field is optional.
 
@@ -74,73 +421,10 @@
 === submit-requirement.Name.canOverrideInChildProjects
 
 A boolean (true, false) that determines if child projects can override the
-submit requirement. +
+submit requirement.
 
 The default value is `false`.
 
-[[evaluation_results]]
-== Evaluation Results
-
-When submit requirements are configured, their results are returned for all
-changes requested by the REST API with the
-link:rest-api-changes.html#submit-requirement-result-info[SubmitRequirementResultInfo]
-entity. +
-
-Submit requirement results are produced from the evaluation of the submit
-requirements in the project config (
-See link:#configuring_submit_requirements[Configuring Submit Requirements])
-as well as the conversion of the results of the legacy submit rules to submit
-requirement results. Legacy submit rules are label functions
-(see link:config-labels.html[config labels]), custom and
-link:prolog-cookbook.html[prolog] submit rules.
-
-The `status` field can be one of:
-
-* `NOT_APPLICABLE`
-+
-The link:#submit_requirement_applicable_if[applicableIf] expression evaluates
-to false for the change.
-
-* `UNSATISFIED`
-+
-The submit requirement is applicable
-(link:#submit_requirement_applicable_if[applicableIf] evaluates to true), but
-the evaluation of the link:#submit_requirement_submittable_if[submittableIf] and
-link:#submit_requirement_override_if[overrideIf] expressions return false for
-the change.
-
-* `SATISFIED`
-+
-The submit requirement is applicable
-(link:#submit_requirement_applicable_if[applicableIf] evaluates to true), the
-link:#submit_requirement_submittable_if[submittableIf] expression evaluates to
-true, and the link:#submit_requirement_override_if[overrideIf] evaluates to
-false for the change.
-
-* `OVERRIDDEN`
-+
-The submit requirement is applicable
-(link:#submit_requirement_applicable_if[applicableIf] evaluates to true) and the
-link:#submit_requirement_override_if[overrideIf] expression evaluates to true.
-+
-Note that in this case, the change is overridden whether the
-link:#submit_requirement_submittable_if[submittableIf] expression evaluates to
-true or not.
-
-* `FORCED`
-+
-The change was merged directly bypassing code review by supplying the
-link:user-upload.html#auto_merge[submit] push option while doing a git push.
-
-* `ERROR`
-+
-The evaluation of any of the
-link:#submit_requirement_applicable_if[applicableIf],
-link:#submit_requirement_submittable_if[submittableIf] or
-link:#submit_requirement_override_if[overrideIf] expressions resulted in an
-error.
-
-
 [[query_expression_syntax]]
 == Query Expression Syntax
 
@@ -247,13 +531,17 @@
 pattern.
 
 [[operator_label]]
-label:labelName=+1,user=non_contributor::
+label:LabelExpression::
 +
-Submit requirements support an additional `user=non_contributor` argument for
-labels that returns true if the change has a label vote matching the specified
-value and the vote is applied from a gerrit account that's not the uploader,
-author or committer of the latest patchset. See the documentation for the labels
-operator in the link:user-search.html[user search] page.
+The `label` operator allows to match changes that have votes matching the given
+`LabelExpression`. The `LabelExpression` can be anything that's supported for
+the link:user-search.html#labels[label] query operator.
++
+If used in submit requirement expressions, this operator supports an additional
+`user=non_contributor` argument. This argument works similar to the
+link:user-search.html#non_uploader["user=non_uploader"] argument and returns
+true if the change has a matching label vote that is applied by a user that's
+not the uploader, author or committer of the latest patchset.
 
 [[unsupported_operators]]
 === Unsupported Operators
@@ -265,39 +553,6 @@
 +
 Cannot be used since it will result in recursive evaluation of expressions.
 
-[[inheritance]]
-== Inheritance
-
-Child projects can override a submit requirement defined in any of their parent
-projects. Overriding a submit requirement overrides all of its properties and
-values. The overriding project needs to define all mandatory fields.
-
-Submit requirements are looked up from the current project up the inheritance
-hierarchy to “All-Projects”. The first project in the hierarchy chain that sets
-link:#submit_requirement_can_override_in_child_projects[canOverrideInChildProjects]
-to false prevents all descendant projects from overriding it.
-
-If a project disallows a submit requirement from being overridden in child
-projects, all definitions of this submit requirement in descendant projects are
-ignored.
-
-To remove a submit requirement in a child project, administrators can redefine
-the requirement with the same name in the child project and set the
-link:#submit_requirement_applicable_if[applicableIf] expression to `is:false`.
-Since the link:#submit_requirement_submittable_if[submittableIf] field is
-mandatory, administrators need to provide it in the child project but can set it
-to anything, for example `is:false` but it will have no effect anyway.
-
-
-[[trigger-votes]]
-== Trigger Votes
-
-Trigger votes are label votes that are not associated with any submit
-requirement expressions. Trigger votes are displayed in a separate section in
-the change page. For more about configuring labels, see the
-link:config-labels.html[config labels] documentation.
-
-
 [[examples]]
 == Examples
 
@@ -359,46 +614,6 @@
   submittableIf = hasfooter:\"Bug\"
 ----
 
-[[test-submit-requirements]]
-== Testing Submit Requirements
-
-The link:rest-api-changes.html#check-submit-requirement[Check Submit Requirement]
-change endpoint can be used to test submit requirements on any change. Users
-are encouraged to test submit requirements before adding them to the project
-to ensure they work as intended.
-
-.Request
-----
-  POST /changes/myProject~master~I8473b95934b5732ac55d26311a706c9c2bde9940/check.submit_requirement HTTP/1.0
-  Content-Type: application/json; charset=UTF-8
-
-    {
-      "name": "Code-Review",
-      "submittability_expression": "label:Code-Review=+2"
-    }
-----
-
-.Response
-----
-  HTTP/1.1 200 OK
-  Content-Disposition: attachment
-  Content-Type: application/json; charset=UTF-8
-
-  )]}'
-  {
-    "name": "Code-Review",
-    "status": "SATISFIED",
-    "submittability_expression_result": {
-      "expression": "label:Code-Review=+2",
-      "fulfilled": true,
-      "passingAtoms": [
-        "label:Code-Review=+2"
-      ]
-    },
-    "is_legacy": false
-  }
-----
-
 GERRIT
 ------
 Part of link:index.html[Gerrit Code Review]
diff --git a/Documentation/dev-bazel.txt b/Documentation/dev-bazel.txt
index 53d9e6b..4c224b5 100644
--- a/Documentation/dev-bazel.txt
+++ b/Documentation/dev-bazel.txt
@@ -393,6 +393,34 @@
   bazelisk test //plugins/replication/...
 ----
 
+[[known-issues]]
+=== Known Issues
+
+[[byte-buddy-not-initialized-or-unavailable]]
+==== The Byte Buddy agent is not initialized or unavailable
+
+If running tests that make use of mocks fail with the exception below, set the
+`sandbox_tmpfs_path` flag for running tests in `.bazelrc` as described in this
+link:https://github.com/mockito/mockito/issues/1879#issuecomment-922459131[
+issue], e.g. add this line: `test --sandbox_tmpfs_path=/tmp`
+
+.Exception:
+----
+...
+Caused by: org.mockito.exceptions.base.MockitoInitializationException:
+Could not initialize inline Byte Buddy mock maker.
+
+It appears as if your JDK does not supply a working agent attachment mechanism.
+...
+Caused by: java.lang.IllegalStateException: The Byte Buddy agent is not initialized or unavailable
+at net.bytebuddy.agent.ByteBuddyAgent.getInstrumentation(ByteBuddyAgent.java:230)
+at net.bytebuddy.agent.ByteBuddyAgent.install(ByteBuddyAgent.java:617)
+at net.bytebuddy.agent.ByteBuddyAgent.install(ByteBuddyAgent.java:568)
+at net.bytebuddy.agent.ByteBuddyAgent.install(ByteBuddyAgent.java:545)
+at org.mockito.internal.creation.bytebuddy.InlineDelegateByteBuddyMockMaker.<clinit>(InlineDelegateByteBuddyMockMaker.java:115)
+... 47 more
+----
+
 [[debugging-tests]]
 == Debugging Unit Tests
 In some cases it may be necessary to debug a test while running it in bazel. For example, when we
@@ -664,26 +692,16 @@
     --disk-size=200
 ```
 
-Due to outdated Git version in official RBE docker images, a custom RBE docker
-image must be used. To build custom docker imager, change to the directory
-`tools/platforms` and build and publish custom RBE docker image.
+Note, that we are using Ubuntu2204 docker image from bazel project:
 
-To build the custom RBE docker image, run:
 
 ```
-docker build -t gcr.io/api-project-164060093628/rbe-ubuntu18-04 .
+docker pull gcr.io/bazel-public/ubuntu2204-java17@sha256:ffe37746a34537d8e73cef5a20ccd3a4e3ec7af3e7410cba87387ba97c0e520f
 ```
 
-To publish the custom RBE docker image, run:
-
-```
-docker push gcr.io/api-project-164060093628/rbe-ubuntu18-04
-[...]
-latest: digest: sha256:de5186d4313630a6111f9a2449b72563d0bc59ec9fb60956f063b69a38a76834 size: 1584
-```
-
-Re-build rbe_autoconfig project conduct a new release and switch to using it
-in `WORKSPACE` file.
+Re-build rbe_autoconfig project, conduct a new release and switch to using it
+in `WORKSPACE` file. For more details see this
+link:https://github.com/davido/rbe_autoconfig[repository,role=external,window=_blank]
 
 Note, to authenticate to the gcr.io registry, the following command must be
 used:
diff --git a/Documentation/dev-community.txt b/Documentation/dev-community.txt
index 1229faf..07e3a11 100644
--- a/Documentation/dev-community.txt
+++ b/Documentation/dev-community.txt
@@ -45,7 +45,7 @@
 * link:dev-readme.html[Developer Setup]
 * link:https://gerrit.googlesource.com/gerrit/+/master/polygerrit-ui[TypeScript Frontend Developer Setup]
 * link:dev-crafting-changes.html[Crafting Changes]
-* link:dev-starter-projects.html[Starter Projects]
+* link:dev-contribution-opportunities.html[Contribution Opportunities (Help Wanted)]
 
 [[plugin-development]]
 == Plugin Development
diff --git a/Documentation/dev-contribution-opportunities.txt b/Documentation/dev-contribution-opportunities.txt
new file mode 100644
index 0000000..23f916e
--- /dev/null
+++ b/Documentation/dev-contribution-opportunities.txt
@@ -0,0 +1,45 @@
+:linkattrs:
+= Gerrit Code Review - Contribution Opportunities
+
+If you are eager to contribute to Gerrit, but you don't know where to
+start here are some opportunities to contribute.
+
+[[help-wanted]]
+== Help Wanted
+
+The link:https://issues.gerritcodereview.com/hotlists/5395287[HelpWanted,role=external,window=_blank]
+hotlist in the issue tracker lists issues that need help from the
+community and where any contribution is very welcome.
+
+If you are interested in any of the projects and you want to try
+implementing it, just assign the corresponding issue to yourself and
+reach out on the
+link:https://groups.google.com/forum/#!forum/repo-discuss[mailing list,role=external,window=_blank]
+if you have questions or need help.
+
+[[creating-help-wanted-issue]]
+=== Creating Help Wanted issues
+
+Issues on the link:https://issues.gerritcodereview.com/hotlists/5395287[HelpWanted,role=external,window=_blank]
+hotlist should be phrased as user stories and be well-scoped so that they can
+be easily picked up by new contributors:
+
+* The issue title should name the feature and be prefixed with a t-shirt size
+  in square brackets to indicate the expected effort.
+* The issue description should use Markdown and have the following sections:
+** *User Story*: Explain the use case that is being addressed and what's the
+   value of the feature.
+** *What*: Describe what should be done.
+** *Background*: Any useful background information, including ideas how the
+   feature can be done.
+** *Pointers*: Useful links to documentation and code
+
+Note, only link:dev-roles.html#maintainer[maintainers] and
+link:dev-roles.html#contributor[trusted contributors] can add issues to the `HelpWanted` hotlist.
+
+GERRIT
+------
+Part of link:index.html[Gerrit Code Review]
+
+SEARCHBOX
+---------
diff --git a/Documentation/dev-plugins.txt b/Documentation/dev-plugins.txt
index 42edc1f..bcc96b4 100644
--- a/Documentation/dev-plugins.txt
+++ b/Documentation/dev-plugins.txt
@@ -2248,6 +2248,52 @@
 e.g. a plugin can provide a list of servers on which the change was
 deployed.
 
+Plugins can filter the branches and tags that are inlcuded by implementing
+`com.google.gerrit.server.change.FilterIncludedIn`.
+
+[source, java]
+----
+import com.google.gerrit.entities.Project;
+import com.google.gerrit.server.change.FilterIncludedIn;
+import java.util.function.Predicate;
+import org.eclipse.jgit.revwalk.RevCommit;
+
+public class MyFilter implements FilterIncludedIn {
+  @Override
+  public Predicate<String> getBranchFilter(Project.NameKey project, RevCommit commit) {
+    if (project.get() != "myproject") {
+      return branch -> true;
+    }
+    return branch -> !branch.startsWith("feature/");
+  }
+
+  @Override
+  public Predicate<String> getTagFilter(Project.NameKey project, RevCommit commit) {
+    if (project.get() != "myproject") {
+      return tag -> true;
+    }
+    return tag -> tag.startsWith("v");
+  }
+}
+----
+
+And register your class:
+
+[source, java]
+----
+import com.google.gerrit.extensions.annotations.Exports;
+import com.google.gerrit.extensions.registration.DynamicSet;
+import com.google.gerrit.server.change.FilterIncludedIn;
+import com.google.inject.AbstractModule;
+
+public class MyPluginModule extends AbstractModule {
+  @Override
+  protected void configure() {
+    DynamicSet.bind(binder(), FilterIncludedIn.class).to(MyFilter.class);
+  }
+}
+----
+
 [[change-report-formatting]]
 == Change Report Formatting
 
diff --git a/Documentation/dev-processes.txt b/Documentation/dev-processes.txt
index 8b7ba4b..c2a1f86 100644
--- a/Documentation/dev-processes.txt
+++ b/Documentation/dev-processes.txt
@@ -208,19 +208,14 @@
 
 To report a security vulnerability file a
 link:https://issues.gerritcodereview.com/issues/new?component=1371046[
-security issue,role=external,window=_blank] in the Gerrit issue tracker. The visibility of issues that are
-created with the `Security Issue` template is automatically restricted to
-Gerrit maintainers and a few long-term contributors. This means as a reporter
-you may not be able to see the issue once it is created. Security issues are
-created on the `ESC` component so that they will be discussed at the next
-meeting of the link:#steering-committee[Engineering Steering Committee] which
-takes place biweekly.
+security issue,role=external,window=_blank] in the Gerrit issue tracker. Issues
+in the `Gerrit Code Review > Security` component are restricted to Gerrit
+maintainers and a few long-term contributors. The reporter becomes a
+collaborator on the issue and hence can see it as well. Security issues are
+triaged by the link:#steering-committee[Engineering Steering Committee].
 
-If an existing issue is found to be a security vulnerability it should be
-turned into a security issue by:
-
-. Setting the component to `ESC`
-. Adding the labels `Security` and `NonPublic`
+If an existing issue is found to be a security vulnerability it should be moved
+to `Gerrit Code Review > Security` component (component ID: 1371046).
 
 In case of doubt, or if an issue cannot wait until the next ESC meeting,
 contact the link:#steering-committee[Engineering Steering Committee] directly
@@ -370,6 +365,12 @@
 link:https://gerrit-review.googlesource.com/q/label:%2522Library-Compliance%253Dneed%2522+-ownerin:google-gerrit-team+status:open+project:gerrit+-age:4week+-is:wip+-is:private+label:Code-Review%252B2[change query]
 to find changes that require a `Library-Compliance` approval.
 
+To get the attention of a Googler for dependency updates file separate issues
+(use type "Task") for each dependency update on the
+link:https://issues.gerritcodereview.com/issues/new?component=1371020&template=1834212["Hosting > googlesource" component].
+Then it will show up in Google's triage queue and the current person who is on duty
+should look into this.
+
 Gerrit's library dependencies should only be upgraded if the new version contains
 something we need in Gerrit. This includes new features, API changes as well as bug
 or security fixes.
diff --git a/Documentation/dev-starter-projects.txt b/Documentation/dev-starter-projects.txt
deleted file mode 100644
index 92de84d..0000000
--- a/Documentation/dev-starter-projects.txt
+++ /dev/null
@@ -1,15 +0,0 @@
-:linkattrs:
-= Gerrit Code Review - Starter Projects
-
-We have created a
-link:https://issues.gerritcodereview.com/hotlists/5052926[StarterProject,role=external,window=_blank]
-hotlist in the issue tracker and try to assign easy hack projects to it. If in
-doubt, do not hesitate to ask on the developer
-link:https://groups.google.com/forum/#!forum/repo-discuss[mailing list,role=external,window=_blank].
-
-GERRIT
-------
-Part of link:index.html[Gerrit Code Review]
-
-SEARCHBOX
----------
diff --git a/Documentation/images/generated-suggested-edit-added.png b/Documentation/images/generated-suggested-edit-added.png
new file mode 100644
index 0000000..37300c3
--- /dev/null
+++ b/Documentation/images/generated-suggested-edit-added.png
Binary files differ
diff --git a/Documentation/images/generated-suggested-edit-preview.png b/Documentation/images/generated-suggested-edit-preview.png
new file mode 100644
index 0000000..9ca82f1
--- /dev/null
+++ b/Documentation/images/generated-suggested-edit-preview.png
Binary files differ
diff --git a/Documentation/intro-project-owner.txt b/Documentation/intro-project-owner.txt
index 97b58af..15f27db 100644
--- a/Documentation/intro-project-owner.txt
+++ b/Documentation/intro-project-owner.txt
@@ -44,7 +44,7 @@
 To see the access rights of your project
 
 - go to the Gerrit Web UI
-- click on the `Projects` > `List` menu entry
+- click on the `BROWSE` > `Repositories` menu entry
 - find your project in the project list and click on it
 - click on the `Access` menu entry
 
@@ -150,7 +150,7 @@
 group backends.
 
 The Gerrit internal groups can be seen in the Gerrit Web UI by clicking
-on the `Groups` > `List` menu entry. By clicking on a group you can
+on the `BROWSE` > `Groups` menu entry. By clicking on a group you can
 edit the group members (`Members` tab) and the group options
 (`General` tab).
 
@@ -168,8 +168,9 @@
 `Make group visible to all registered users.`, which defines whether
 non-members can see who is member of the group.
 
-New internal Gerrit groups can be created under `Groups` >
-`Create New Group`. This menu is only available if you have the global
+New internal Gerrit groups can be created under `BROWSE` > `Groups`
+and then clicking on the `CREATE NEW` button in the upper right corner.
+The `CREATE NEW` button is only available if you have the global
 capability link:access-control.html#capability_createGroup[Create Group]
 assigned.
 
@@ -238,7 +239,7 @@
 To see the options of your project:
 
 . Go to the Gerrit Web UI.
-. Click on the `Projects` > `List` menu entry.
+. Click on the `BROWSE` > `Repositories` menu entry.
 . Find your project in the project list and click it.
 . Click the `General` menu entry.
 
@@ -431,7 +432,7 @@
 == Branch Administration
 
 As project owner you can administrate the branches of your project in
-the Gerrit Web UI under `Projects` > `List` > <your project> >
+the Gerrit Web UI under `BROWSE` > `Repositories` > <your project> >
 `Branches`. In the Web UI link:project-configuration.html#branch-creation[
 branch creation] is allowed if you have
 link:access-control.html#category_create[Create Reference] access right and
@@ -450,7 +451,7 @@
 
 With Gerrit individual users control their own email subscriptions. By
 editing the link:user-notify.html#user[watched projects] in the Web UI
-under `Settings` > `Watched Projects` users can decide which events to
+under `Settings` > `Notifications` users can decide which events to
 be informed about by email. The change notifications can be filtered by
 link:user-search.html[change search expressions].
 
@@ -476,8 +477,8 @@
 link:user-dashboards.html#project-dashboards[project level]. This way
 you can define a view of the changes that are relevant for your
 project and share this dashboard with all users. The project dashboards
-can be seen in the Web UI under `Projects` > `List` > <your project> >
-`Dashboards`.
+can be seen in the Web UI under `BROWSE` > `Repositories` > <your project>
+> `Dashboards`.
 
 [[issue-tracker-integration]]
 == Issue Tracker Integration
@@ -509,7 +510,7 @@
 to Gerrit changes to the issues in the issue tracker system or to
 automatically close an issue if the corresponding change is merged.
 If installed, project owners may enable/disable the issue tracker
-integration from the Gerrit Web UI under `Projects` > `Lists` >
+integration from the Gerrit Web UI under `BROWSE` > `Repositories` >
 <your project> > `General`.
 
 [[comment-links]]
@@ -554,7 +555,7 @@
 With the link:https://gerrit-review.googlesource.com/admin/repos/plugins/reviewers[
 reviewers,role=external,window=_blank] plugin it is possible to configure default reviewers who
 will be automatically added to each change. The default reviewers can
-be configured in the Gerrit Web UI under `Projects` > `List` >
+be configured in the Gerrit Web UI under `BROWSE` > `Repositories` >
 <your project> > `General` in the `reviewers Plugin` section.
 
 The link:https://gerrit-review.googlesource.com/admin/repos/plugins/reviewers-by-blame[
@@ -565,7 +566,7 @@
 touched by the change, since these users should be familiar with the
 code and can most likely review the change. How many reviewers the
 plugin will add to a change at most can be configured in the Gerrit
-Web UI under `Projects` > `List` > <your project> > `General` in the
+Web UI under `BROWSE` > `Repositories` > <your project> > `General` in the
 `reviewers-by-blame Plugin` section.
 
 [[download-commands]]
@@ -650,9 +651,10 @@
 [[project-creation]]
 === Project Creation
 
-New projects can be created in the Gerrit Web UI under `Projects` >
-`Create Project`. The `Create Project` menu entry is only available if
-you have the link:access-control.html#capability_createProject[
+New projects can be created in the Gerrit Web UI under `BROWSE` >
+`Repositories` and then clicking on the `CREATE NEW` button in the
+upper right corner. The `CREATE NEW` button is only available if you
+have the link:access-control.html#capability_createProject[
 Create Project] global capability assigned.
 
 Projects can also be created via REST or SSH as described in the
@@ -736,7 +738,7 @@
 
 If the link:https://gerrit-review.googlesource.com/admin/repos/plugins/delete-project[
 delete-project,role=external,window=_blank] plugin is installed, projects can be deleted from the
-Gerrit Web UI under `Projects` > `List` > <project> > `General` by
+Gerrit Web UI under `BROWSE` > `Repositories` > <project> > `General` by
 clicking on the `Delete` command under `Project Commands`. The `Delete`
 command is only available if you have the `Delete Projects` global
 capability assigned, or if you own the project and you have the
diff --git a/Documentation/intro-user.txt b/Documentation/intro-user.txt
index 7825e050..108022a9 100644
--- a/Documentation/intro-user.txt
+++ b/Documentation/intro-user.txt
@@ -28,15 +28,10 @@
 Still there are some client-side tools for Gerrit, which can be used
 optionally:
 
-* link:http://eclipse.org/mylyn/[Mylyn Gerrit Connector,role=external,window=_blank]: Gerrit
-  integration with Mylyn
 * link:https://github.com/uwolfer/gerrit-intellij-plugin[Gerrit
   IntelliJ Plugin,role=external,window=_blank]: Gerrit integration with the
   link:http://www.jetbrains.com/idea/[IntelliJ Platform,role=external,window=_blank]
-* link:https://play.google.com/store/apps/details?id=com.jbirdvegas.mgerrit[
-  mGerrit,role=external,window=_blank]: Android client for Gerrit
-* link:https://github.com/stackforge/gertty[Gertty,role=external,window=_blank]: Console-based
-  interface for Gerrit
+* link:https://opendev.org/ttygroup/gertty[gertty]: Console-based interface for Gerrit
 
 [[clone]]
 == Clone Gerrit Project
diff --git a/Documentation/licenses.txt b/Documentation/licenses.txt
index 6c3c459..7e3fa9a 100644
--- a/Documentation/licenses.txt
+++ b/Documentation/licenses.txt
@@ -77,7 +77,8 @@
 * jetty:util-ajax
 * log:log4j
 * lucene:lucene-analyzers-common
-* lucene:lucene-core-and-backward-codecs-merged
+* lucene:lucene-backward-codecs
+* lucene:lucene-core
 * lucene:lucene-misc
 * lucene:lucene-queryparser
 * mime4j:core
diff --git a/Documentation/metrics.txt b/Documentation/metrics.txt
index df0cc42b..4302a35 100644
--- a/Documentation/metrics.txt
+++ b/Documentation/metrics.txt
@@ -430,6 +430,13 @@
 * `permissions/ref_filter/skip_filter_count`: Rate of ref filter operations
   where we skip full evaluation because the user can read all refs
 
+=== Validation
+
+* `validation/file_count`: Track number of files per change during commit
+  validation, if it exceeds the FILE_COUNT_WARNING_THRESHOLD threshold.
+** `file_count`: number of files in the patchset
+** `host_repo`: host and repository of the change in the format 'host/repo'
+
 === Reviewer Suggestion
 
 * `reviewer_suggestion/query_accounts`: Latency for querying accounts for
diff --git a/Documentation/rest-api-accounts.txt b/Documentation/rest-api-accounts.txt
index 9ecef3f..0d7a98c 100644
--- a/Documentation/rest-api-accounts.txt
+++ b/Documentation/rest-api-accounts.txt
@@ -1314,6 +1314,7 @@
     "publish_comments_on_push": true,
     "work_in_progress_by_default": true,
     "allow_browser_notifications": true,
+    "allow_suggest_code_while_commenting": true,
     "diff_page_sidebar": "plugin-foo",
     "default_base_for_merges": "FIRST_PARENT",
     "my": [
@@ -1368,6 +1369,7 @@
     "disable_keyboard_shortcuts": true,
     "disable_token_highlighting": true,
     "allow_browser_notifications": false,
+    "allow_suggest_code_while_commenting": false,
     "diff_page_sidebar": "NONE",
     "diff_view": "SIDE_BY_SIDE",
     "mute_common_path_prefixes": true,
@@ -2716,6 +2718,9 @@
 inline edit feature.
 |`allow_browser_notifications`  |not set if `false`|
 Whether to prompt user to enable browser notification in browser.
+|`allow_suggest_code_while_commenting`  |not set if `false`|
+Whether to receive suggested code while writing comments. This feature needs
+a plugin implementation.
 |`diff_page_sidebar`            |optional|
 String indicating which sidebar should be open on the diff page. Set to "NONE"
 if no sidebars should be open. Plugin-supplied sidebars will be prefixed with
@@ -2791,6 +2796,9 @@
 inline edit feature.
 |`allow_browser_notifications`  |not set if `false`|
 Whether to prompt user to enable browser notification in browser.
+|`allow_suggest_code_while_commenting`  |not set if `false`|
+Whether to receive suggested code while writing comments. This feature needs
+a plugin implementation.
 |`diff_page_sidebar`            |optional|
 String indicating which sidebar should be open on the diff page. Set to "NONE"
 if no sidebars should be open. Plugin-supplied sidebars will be prefixed with
diff --git a/Documentation/rest-api-changes.txt b/Documentation/rest-api-changes.txt
index df5566f..4747957 100644
--- a/Documentation/rest-api-changes.txt
+++ b/Documentation/rest-api-changes.txt
@@ -1331,6 +1331,9 @@
 
 Rebases a change.
 
+For merge commits always the first parent is rebased. This means the new base becomes the first
+parent of the rebased merge commit while the second parent stays intact.
+
 If one of the secondary emails associated with the user performing the operation was used as the
 committer email in the current patch set, the same email will be used as the committer email in the
 new patch set; otherwise, the user's preferred email will be used.
@@ -2477,7 +2480,7 @@
 ----
 
 [[list-change-robot-comments]]
-=== List Change Robot Comments
+=== List Change Robot Comments (deprecated)
 --
 'GET /changes/link:#change-id[\{change-id\}]/robotcomments'
 --
@@ -3383,6 +3386,36 @@
   HTTP/1.1 204 No Content
 ----
 
+[[put-change-edit-committer-author-identity]]
+=== Change author or committer identity in Change Edit
+--
+'PUT /changes/link:#change-id[\{change-id\}]/edit:identity'
+--
+
+Modify author or committer identity. The request body needs to include a
+link:#change-edit-identity-input[ChangeEditIdentityInput]
+entity. Either `name` or `email` must be provided. `type` must be either `AUTHOR` or `COMMITTER`.
+
+.Request
+----
+  PUT /changes/myProject~master~I8473b95934b5732ac55d26311a706c9c2bde9940/edit:identity HTTP/1.0
+  Content-Type: application/json; charset=UTF-8
+
+  {
+    "name": "John Doe",
+    "email": "john.doe@example.com",
+    "type": "COMMITTER"
+  }
+----
+
+If a change edit doesn't exist for this change yet, it is created. As
+response "`204 No Content`" is returned.
+
+.Response
+----
+  HTTP/1.1 204 No Content
+----
+
 [[get-edit-file]]
 === Retrieve file content from Change Edit
 --
@@ -3642,10 +3675,22 @@
 This REST endpoint only suggests accounts that
 
 * are active
+
 * can see the change
-* are visible to the calling user
+
+* are visible to the calling user:
++
+Whether an account is visible to the calling user depends on the
+link:config-gerrit.html#accounts.visibility[accounts.visibility] setting of the
+server. Which account visibility is configured can be checked by opening
+`https://<HOST>/config/server/info?pp=1` in a browser (see field `accounts` >
+link:rest-api-config.html#accounts-config-info[visibility] in the returned
+JSON).
+
 * are not already reviewer on the change
+
 * don't own the change
+
 * are not service users (unless
   link:config.html#suggest.skipServiceUsers[skipServiceUsers] is set to `false`)
 
@@ -5392,7 +5437,7 @@
 ----
 
 [[list-robot-comments]]
-=== List Robot Comments
+=== List Robot Comments (deprecated)
 --
 'GET /changes/link:#change-id[\{change-id\}]/revisions/link:#revision-id[\{revision-id\}]/robotcomments/'
 --
@@ -5449,7 +5494,7 @@
 ----
 
 [[get-robot-comment]]
-=== Get Robot Comment
+=== Get Robot Comment (deprecated)
 --
 'GET /changes/link:#change-id[\{change-id\}]/revisions/link:#revision-id[\{revision-id\}]/robotcomments/link:#comment-id[\{comment-id\}]'
 --
@@ -7021,6 +7066,19 @@
 |`message`     ||New commit message.
 |===========================
 
+[[change-edit-identity-input]]
+=== ChangeEditIdentityInput
+The `ChangeEditIdentityInput` entity contains information for changing
+the author or committer identity within a change edit.
+
+[options="header",cols="1,^1,5"]
+|===========================
+|Field Name    ||Description
+|`name`     |optional|The name of the author/committer. If not specified, the existing name will be used.
+|`email`    |optional|The email of the author/committer. If not specified, the existing email will be used.
+|`type`     ||Type of the identity being edited. Must be either `AUTHOR` or `COMMITTER`.
+|===========================
+
 [[change-info]]
 === ChangeInfo
 The `ChangeInfo` entity contains information about a change.
@@ -7144,11 +7202,9 @@
 |`reviewers`          |optional|
 The reviewers as a map that maps a reviewer state to a list of
 link:rest-api-accounts.html#account-info[AccountInfo] entities.
-Possible reviewer states are `REVIEWER`, `CC` and `REMOVED`. +
+Possible reviewer states are `REVIEWER`, `CC`. +
 `REVIEWER`: Users with at least one non-zero vote on the change. +
 `CC`: Users that were added to the change, but have not voted. +
-`REMOVED`: Users that were previously reviewers on the change, but have
-been removed. +
 Only set if link:#labels[labels] or
 link:#detailed-labels[detailed labels] are requested.
 |`pending_reviewers`  |optional|
@@ -7158,6 +7214,11 @@
 notified about being added to or removed from the change. +
 Only set if link:#labels[labels] or
 link:#detailed-labels[detailed labels] are requested.
+Possible states are `REVIEWER`, `CC` and `REMOVED`. +
+`REVIEWER`: Users with at least one non-zero vote on the change. +
+`CC`: Users that were added to the change, but have not voted. +
+`REMOVED`: Users that were previously reviewers on the change, but have
+been removed.
 |`reviewer_updates`|optional|
 Updates to reviewers set for the change as
 link:#review-update-info[ReviewerUpdateInfo] entities.
@@ -7465,7 +7526,8 @@
 Mime type of the file where the comment is written. Available only if the
 "enable-context" parameter (see link:#list-change-comments[List Change Comments])
 is set.
-
+|`fix_suggestions`|optional|Suggested fixes for this comment as a list of
+<<fix-suggestion-info,FixSuggestionInfo>> entities.
 |===========================
 
 [[comment-input]]
@@ -7513,6 +7575,8 @@
 Whether or not the comment must be addressed by the user. This value will
 default to false if the comment is an orphan, or the value of the `in_reply_to`
 comment if it is supplied.
+|`fix_suggestions`|optional|Suggested fixes for this comment as a list of
+<<fix-suggestion-info,FixSuggestionInfo>> entities.
 |===========================
 
 [[comment-range]]
@@ -7588,6 +7652,9 @@
 |=============================
 |Field Name      ||Description
 |`message`       ||New commit message.
+|`committer_email`|optional|
+New message is committed using this email address. Only the
+registered emails of the calling user are considered valid.
 |`notify`        |optional|
 Notify handling that defines to whom email notifications should be sent
 after the commit message was updated. +
@@ -8521,8 +8588,10 @@
 The account which modified state of the reviewer in question as
 link:rest-api-accounts.html#account-info[AccountInfo] entity.
 |`reviewer`|
-The reviewer account added or removed from the change as an
-link:rest-api-accounts.html#account-info[AccountInfo] entity.
+The reviewer added or removed from the change as an
+link:rest-api-accounts.html#account-info[AccountInfo] entity. For
+reviewers by email the `AccountInfo` doesn't contain an account ID but
+only the email and optionally a name.
 |`state`|
 The reviewer state, one of `REVIEWER`, `CC` or `REMOVED`.
 |===========================
@@ -8551,7 +8620,7 @@
 |`comments`                             |optional|
 The comments that should be added as a map that maps a file path to a
 list of link:#comment-input[CommentInput] entities.
-|`robot_comments`                       |optional|
+|`robot_comments`                       |optional, deprecated|
 The robot comments that should be added as a map that maps a file path
 to a list of link:#robot-comment-input[RobotCommentInput] entities.
 |`drafts`                               |optional|
@@ -8801,7 +8870,7 @@
 |===========================
 
 [[robot-comment-info]]
-=== RobotCommentInfo
+=== RobotCommentInfo (deprecated)
 The `RobotCommentInfo` entity contains information about a robot inline
 comment.
 
@@ -8817,12 +8886,10 @@
 |`url`            |optional|URL to more information.
 |`properties`     |optional|Robot specific properties as map that maps arbitrary
 keys to values.
-|`fix_suggestions`|optional|Suggested fixes for this robot comment as a list of
-<<fix-suggestion-info,FixSuggestionInfo>> entities.
 |===========================
 
 [[robot-comment-input]]
-=== RobotCommentInput
+=== RobotCommentInput (deprecated)
 The `RobotCommentInput` entity contains information for creating an inline
 robot comment.
 
@@ -8852,8 +8919,6 @@
 |`url`            |optional|URL to more information.
 |`properties`     |optional|Robot specific properties as map that maps arbitrary
 keys to values.
-|`fix_suggestions`|optional|Suggested fixes for this robot comment as a list of
-<<fix-suggestion-info,FixSuggestionInfo>> entities.
 |===========================
 
 [[rule-input]]
diff --git a/Documentation/rest-api-config.txt b/Documentation/rest-api-config.txt
index ec1ac03..b6cbaaa 100644
--- a/Documentation/rest-api-config.txt
+++ b/Documentation/rest-api-config.txt
@@ -1420,6 +1420,136 @@
 When `delete_missing` is set to `true` changes to be reindexed which are missing in NoteDb
 will be deleted in the index.
 
+[[list-indexes]]
+=== List Indexes
+--
+'GET /config/server/indexes'
+--
+
+Lists the indexes used by Gerrit. It provides details about the index versions,
+which index version is used to search and which versions are written to.
+
+This endpoint requires the
+link:access-control.html#capability_maintainServer[Maintain Server]
+capability.
+
+.Request
+----
+  GET /config/server/indexes/ HTTP/1.0
+----
+
+.Response
+----
+  HTTP/1.1 200 OK
+  Content-Type: application/json; charset=UTF-8
+
+  )]}'
+  {
+    "accounts": {
+      "name": "accounts",
+      "versions": {
+        "13": {
+          "write": true,
+          "search": true
+        }
+      }
+    },
+    "changes": {
+      "name": "changes",
+      "versions": {
+        "83": {
+          "write": true,
+          "search": true
+        },
+        "84": {
+          "write": true,
+          "search": false
+        }
+      }
+    },
+    "groups": {
+      "name": "groups",
+      "versions": {
+        "10": {
+          "write": true,
+          "search": true
+        }
+      }
+    },
+    "projects": {
+      "name": "projects",
+      "versions": {
+        "8": {
+          "write": true,
+          "search": true
+        }
+      }
+    }
+  }
+----
+
+[[snapshot-index]]
+=== Create Index Snapshot
+
+This endpoint allows Gerrit admins to create a snapshot of an index.
+This snapshot can be used as a backup of the index.
+
+A snapshot of all versions of an index can be created by just using
+the name of the index, e.g. `changes`. Only snapshots of indexes that
+Gerrit currently writes to can be created. An index version can be
+selected by using e.g. `changes~84`. Snapshots of all indexes can be
+created by using `all` instead of an index name.
+
+Note, that the creation of multiple snapshots, e.g. of different index
+versions, is not atomic. If a consistent state over multiple indexes is
+required, the server has to be put into read-only mode before creating
+the snapshot.
+
+The snapshots will be stored on the server at `$SITE/index/snapshots/$ID`.
+The `$ID` can be optionally provided in link:#snapshot-index-input[SnapshotIndex.Input]
+or will default to the current local time in ISO8601 format.
+
+.Request
+----
+  PUT /config/server/indexes/all/snapshot HTTP/1.0
+  Content-Type: application/json; charset=UTF-8
+
+  {
+    "id": "snapshot-1"
+  }
+----
+
+.Response
+----
+  HTTP/1.1 200 OK
+  Content-Type: application/json; charset=UTF-8
+
+  )]}'
+  {
+    "id": "snapshot-1"
+  }
+----
+
+.Request
+----
+  PUT /config/server/indexes/accounts~13/snapshot HTTP/1.0
+  Content-Type: application/json; charset=UTF-8
+
+  {
+    "id": "snapshot-1"
+  }
+----
+
+.Response
+----
+  HTTP/1.1 200 OK
+  Content-Type: application/json; charset=UTF-8
+
+  )]}'
+  {
+    "id": "snapshot-1"
+  }
+----
 
 [[ids]]
 == IDs
@@ -1787,6 +1917,9 @@
 |`url`               ||
 The URL of the download scheme, where '${project}' is used as
 placeholder for the project name.
+|`description`       |optional|
+An optional description of how the scheme works and maybe comparing
+it to other schemes, explaining the pros and cons of each option.
 |`is_auth_required`  |not set if `false`|
 Whether this download scheme requires authentication.
 |`is_auth_supported` |not set if `false`|
@@ -2067,6 +2200,19 @@
 requirements that are applicable for changes appearing in the dashboard.
 |=======================================
 
+[[snapshot-index-input]]
+=== SnapshotIndex.Input
+The `SnapshotIndex.Input` entity contains the parameters used to create an
+index snapshot.
+
+[options="header",cols="1,^1,5"]
+|=======================
+|Field Name ||Description
+|`id` | optional |
+A string ID that will be used as the folder name containing the
+snapshots. Defaults to current timestamp.
+|=======================
+
 [[sshd-info]]
 === SshdInfo
 The `SshdInfo` entity contains information about Gerrit
diff --git a/Documentation/user-search.txt b/Documentation/user-search.txt
index 0744ded..1095002 100644
--- a/Documentation/user-search.txt
+++ b/Documentation/user-search.txt
@@ -848,6 +848,7 @@
 The special "owner" parameter corresponds to the change owner.  Matches
 all changes that have a +2 vote from the change owner.
 
+[[non_uploader]]
 `label:Code-Review=+2,user=non_uploader`::
 `label:Code-Review=ok,user=non_uploader`::
 `label:Code-Review=+2,non_uploader`::
diff --git a/Documentation/user-suggest-edits.txt b/Documentation/user-suggest-edits.txt
index 9c67358..3b2c83a 100644
--- a/Documentation/user-suggest-edits.txt
+++ b/Documentation/user-suggest-edits.txt
@@ -36,6 +36,21 @@
 You can use copy to clipboard button to copy suggestion to clipboard and then you can paste it
 in your editor.
 
+== Generate Suggestion
+
+Following UI needs to be activated by a plugin that implements SuggestionsProvider. Gerrit is providing just UI.
+
+** When a user types a comment, Gerrit queries a plugin for a code snippet. When there is a snippet, the user can see a preview of snippet under comment.
+
+image::images/generated-suggested-edit-preview.png["Generate Suggested Edit", align="center", width=400]
+
+** A user needs to click on "ADD SUGGESTION TO COMMENT" button if they want to use this suggestion. Otherwise the suggestion is never used.
+
+image::images/generated-suggested-edit-added.png["Added Generated Suggested Edit", align="center", width=400]
+
+** By clicking on "ADD SUGGESTION TO COMMENT" button, the suggestion is added to end of comment. The user can then edit the suggestion, if needed.
+
+
 GERRIT
 ------
 Part of link:index.html[Gerrit Code Review]
diff --git a/MODULE.bazel b/MODULE.bazel
new file mode 100644
index 0000000..0b932b8
--- /dev/null
+++ b/MODULE.bazel
@@ -0,0 +1,2 @@
+# TODO(davido): Migrate all dependencies from WORKSPACE to MODULE.bazel
+# https://issues.gerritcodereview.com/issues/303819949
diff --git a/WORKSPACE b/WORKSPACE
index 8ce21d5..1c168c6 100644
--- a/WORKSPACE
+++ b/WORKSPACE
@@ -26,47 +26,22 @@
 
 load("@bazel_tools//tools/build_defs/repo:git.bzl", "git_repository")
 load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive", "http_file")
-load("//tools/bzl:maven_jar.bzl", "GERRIT", "MAVEN_LOCAL", "maven_jar")
 load("//plugins:external_plugin_deps.bzl", "external_plugin_deps")
 load("//tools:nongoogle.bzl", "declare_nongoogle_deps")
 load("//tools:deps.bzl", "CAFFEINE_VERS", "java_dependencies")
 
 http_archive(
-    name = "platforms",
-    sha256 = "3a561c99e7bdbe9173aa653fd579fe849f1d8d67395780ab4770b1f381431d51",
-    urls = [
-        "https://mirror.bazel.build/github.com/bazelbuild/platforms/releases/download/0.0.7/platforms-0.0.7.tar.gz",
-        "https://github.com/bazelbuild/platforms/releases/download/0.0.7/platforms-0.0.7.tar.gz",
-    ],
+    name = "rules_nodejs",
+    patch_args = ["-p1"],
+    patches = ["//tools:rules_nodejs-5.8.4-node_versions.bzl.patch"],
+    sha256 = "8fc8e300cb67b89ceebd5b8ba6896ff273c84f6099fc88d23f24e7102319d8fd",
+    urls = ["https://github.com/bazelbuild/rules_nodejs/releases/download/5.8.4/rules_nodejs-core-5.8.4.tar.gz"],
 )
 
 http_archive(
-    name = "rbe_jdk11",
-    sha256 = "dbcfd6f26589ef506b91fe03a12dc559ca9c84699e4cf6381150522287f0e6f6",
-    strip_prefix = "rbe_autoconfig-3.1.0",
-    urls = [
-        "https://gerrit-bazel.storage.googleapis.com/rbe_autoconfig/v3.1.0.tar.gz",
-        "https://github.com/davido/rbe_autoconfig/archive/v3.1.0.tar.gz",
-    ],
-)
-
-http_archive(
-    name = "com_google_protobuf",
-    sha256 = "3bd7828aa5af4b13b99c191e8b1e884ebfa9ad371b0ce264605d347f135d2568",
-    strip_prefix = "protobuf-3.19.4",
-    urls = [
-        "https://github.com/protocolbuffers/protobuf/archive/v3.19.4.tar.gz",
-    ],
-)
-
-load("@com_google_protobuf//:protobuf_deps.bzl", "protobuf_deps")
-
-protobuf_deps()
-
-http_archive(
     name = "build_bazel_rules_nodejs",
-    sha256 = "94070eff79305be05b7699207fbac5d2608054dd53e6109f7d00d923919ff45a",
-    urls = ["https://github.com/bazelbuild/rules_nodejs/releases/download/5.8.2/rules_nodejs-5.8.2.tar.gz"],
+    sha256 = "709cc0dcb51cf9028dd57c268066e5bc8f03a119ded410a13b5c3925d6e43c48",
+    urls = ["https://github.com/bazelbuild/rules_nodejs/releases/download/5.8.4/rules_nodejs-5.8.4.tar.gz"],
 )
 
 load("@build_bazel_rules_nodejs//:repositories.bzl", "build_bazel_rules_nodejs_dependencies")
@@ -99,9 +74,11 @@
     firefox = True,
 )
 
-register_toolchains("//tools:error_prone_warnings_toolchain_java11_definition")
+declare_nongoogle_deps()
 
-register_toolchains("//tools:error_prone_warnings_toolchain_java17_definition")
+load("//tools:defs.bzl", "gerrit_init")
+
+gerrit_init()
 
 # Java-Prettify external repository consumed from git submodule
 local_repository(
@@ -137,12 +114,10 @@
     ],
 )
 
-declare_nongoogle_deps()
-
 load("@build_bazel_rules_nodejs//:index.bzl", "node_repositories", "yarn_install")
 
 node_repositories(
-    node_version = "17.9.1",
+    node_version = "20.9.0",
     yarn_version = "1.22.19",
 )
 
diff --git a/java/com/google/gerrit/acceptance/AbstractDaemonTest.java b/java/com/google/gerrit/acceptance/AbstractDaemonTest.java
index 80582a4..0e45552 100644
--- a/java/com/google/gerrit/acceptance/AbstractDaemonTest.java
+++ b/java/com/google/gerrit/acceptance/AbstractDaemonTest.java
@@ -19,7 +19,6 @@
 import static com.google.common.truth.OptionalSubject.optionals;
 import static com.google.common.truth.Truth.assertThat;
 import static com.google.common.truth.Truth.assertWithMessage;
-import static com.google.common.truth.Truth8.assertThat;
 import static com.google.common.truth.TruthJUnit.assume;
 import static com.google.gerrit.acceptance.testsuite.project.TestProjectUpdate.allow;
 import static com.google.gerrit.acceptance.testsuite.project.TestProjectUpdate.block;
@@ -49,6 +48,7 @@
 import com.google.common.collect.Lists;
 import com.google.common.primitives.Chars;
 import com.google.common.testing.FakeTicker;
+import com.google.errorprone.annotations.CanIgnoreReturnValue;
 import com.google.gerrit.acceptance.AcceptanceTestRequestScope.Context;
 import com.google.gerrit.acceptance.PushOneCommit.Result;
 import com.google.gerrit.acceptance.config.ConfigAnnotationParser;
@@ -553,6 +553,19 @@
 
     baseConfig.setInt("index", null, "batchThreads", -1);
 
+    if (enableExperimentsRejectImplicitMergesOnMerge()) {
+      // When changes are merged/submitted - reject the operation if there is an implicit merge (
+      // even if rejectImplicitMerges is disabled in the project config).
+      baseConfig.setStringList(
+          "experiments",
+          null,
+          "enabled",
+          ImmutableList.of(
+              "GerritBackendFeature__check_implicit_merges_on_merge",
+              "GerritBackendFeature__reject_implicit_merges_on_merge",
+              "GerritBackendFeature__always_reject_implicit_merges_on_merge"));
+    }
+
     initServer(classDesc, methodDesc);
 
     server.getTestInjector().injectMembers(this);
@@ -568,6 +581,12 @@
         methodDesc.useSystemTime(), methodDesc.useClockStep(), methodDesc.useTimezone());
   }
 
+  protected boolean enableExperimentsRejectImplicitMergesOnMerge() {
+    // By default any attempt to make an explicit merge is rejected. This allows to check
+    // that existing workflows continue to work even if gerrit rejects implicit merges on merge.
+    return true;
+  }
+
   protected void setUpDatabase(GerritServer.Description classDesc) throws Exception {
     admin = accountCreator.admin();
     user = accountCreator.user1();
@@ -757,6 +776,7 @@
     return resourcePrefix + name;
   }
 
+  @CanIgnoreReturnValue
   protected Project.NameKey createProjectOverAPI(
       String nameSuffix,
       @Nullable Project.NameKey parent,
@@ -906,10 +926,12 @@
     return b;
   }
 
+  @CanIgnoreReturnValue
   protected PushOneCommit.Result createChange() throws Exception {
     return createChange("refs/for/master");
   }
 
+  @CanIgnoreReturnValue
   protected PushOneCommit.Result createChange(String ref) throws Exception {
     PushOneCommit push = pushFactory.create(admin.newIdent(), testRepo);
     PushOneCommit.Result result = push.to(ref);
@@ -917,6 +939,7 @@
     return result;
   }
 
+  @CanIgnoreReturnValue
   protected PushOneCommit.Result createChange(TestRepository<InMemoryRepository> repo)
       throws Exception {
     PushOneCommit push = pushFactory.create(admin.newIdent(), repo);
@@ -925,10 +948,12 @@
     return result;
   }
 
+  @CanIgnoreReturnValue
   protected PushOneCommit.Result createMergeCommitChange(String ref) throws Exception {
     return createMergeCommitChange(ref, "foo");
   }
 
+  @CanIgnoreReturnValue
   protected PushOneCommit.Result createMergeCommitChange(String ref, String file) throws Exception {
     ObjectId initial = repo().exactRef(HEAD).getLeaf().getObjectId();
 
@@ -962,6 +987,7 @@
     return result;
   }
 
+  @CanIgnoreReturnValue
   protected PushOneCommit.Result createNParentsMergeCommitChange(String ref, List<String> fileNames)
       throws Exception {
     // This method creates n different commits and creates a merge commit pointing to all n parents.
@@ -1013,6 +1039,7 @@
     return result;
   }
 
+  @CanIgnoreReturnValue
   protected PushOneCommit.Result createCommitAndPush(
       TestRepository<InMemoryRepository> repo,
       String ref,
@@ -1026,6 +1053,7 @@
     return result;
   }
 
+  @CanIgnoreReturnValue
   protected PushOneCommit.Result createChangeWithTopic(
       TestRepository<InMemoryRepository> repo,
       String topic,
@@ -1038,12 +1066,14 @@
         repo, "refs/for/master%topic=" + name(topic), commitMsg, fileName, content);
   }
 
+  @CanIgnoreReturnValue
   protected PushOneCommit.Result createChange(String subject, String fileName, String content)
       throws Exception {
     PushOneCommit push = pushFactory.create(admin.newIdent(), testRepo, subject, fileName, content);
     return push.to("refs/for/master");
   }
 
+  @CanIgnoreReturnValue
   protected PushOneCommit.Result createChange(
       TestRepository<?> repo,
       String branch,
@@ -1057,6 +1087,7 @@
         "refs/for/" + branch + (Strings.isNullOrEmpty(topic) ? "" : "%topic=" + name(topic)));
   }
 
+  @CanIgnoreReturnValue
   protected BranchApi createBranch(BranchNameKey branch) throws Exception {
     return gApi.projects()
         .name(branch.project().get())
@@ -1064,6 +1095,7 @@
         .create(new BranchInput());
   }
 
+  @CanIgnoreReturnValue
   protected BranchApi createBranchWithRevision(BranchNameKey branch, String revision)
       throws Exception {
     BranchInput in = new BranchInput();
@@ -1074,6 +1106,7 @@
   private static final List<Character> RANDOM =
       Chars.asList('a', 'b', 'c', 'd', 'e', 'f', 'g', 'h');
 
+  @CanIgnoreReturnValue
   protected PushOneCommit.Result amendChangeWithUploader(
       PushOneCommit.Result change, Project.NameKey projectName, TestAccount account)
       throws Exception {
@@ -1092,10 +1125,12 @@
     return result;
   }
 
+  @CanIgnoreReturnValue
   protected PushOneCommit.Result amendChange(String changeId) throws Exception {
     return amendChange(changeId, "refs/for/master", admin, testRepo);
   }
 
+  @CanIgnoreReturnValue
   protected PushOneCommit.Result amendChange(
       String changeId, String ref, TestAccount testAccount, TestRepository<?> repo)
       throws Exception {
@@ -1110,11 +1145,13 @@
         new String(Chars.toArray(RANDOM)));
   }
 
+  @CanIgnoreReturnValue
   protected PushOneCommit.Result amendChange(
       String changeId, String subject, String fileName, String content) throws Exception {
     return amendChange(changeId, "refs/for/master", admin, testRepo, subject, fileName, content);
   }
 
+  @CanIgnoreReturnValue
   protected PushOneCommit.Result amendChange(
       String changeId,
       String ref,
@@ -1240,6 +1277,7 @@
         .update();
   }
 
+  @CanIgnoreReturnValue
   protected PushOneCommit.Result pushTo(String ref) throws Exception {
     PushOneCommit push = pushFactory.create(admin.newIdent(), testRepo);
     return push.to(ref);
@@ -1736,6 +1774,7 @@
     assertThat(res).isEqualTo(expectedContent);
   }
 
+  @CanIgnoreReturnValue
   protected RevCommit createNewCommitWithoutChangeId(String branch, String file, String content)
       throws Exception {
     try (Repository repo = repoManager.openRepository(project);
@@ -1830,7 +1869,7 @@
     return new ProjectConfigUpdate(projectName);
   }
 
-  protected class ProjectConfigUpdate implements AutoCloseable {
+  public class ProjectConfigUpdate implements AutoCloseable {
     private final ProjectConfig projectConfig;
     private MetaDataUpdate metaDataUpdate;
 
@@ -1952,12 +1991,14 @@
     }
 
     /** Switches to system ticker */
+    @CanIgnoreReturnValue
     public Ticker useDefaultTicker() {
       this.actualTicker = Ticker.systemTicker();
       return actualTicker;
     }
 
     /** Switches to {@link FakeTicker} */
+    @CanIgnoreReturnValue
     public FakeTicker useFakeTicker() {
       if (!(this.actualTicker instanceof FakeTicker)) {
         this.actualTicker = new FakeTicker();
diff --git a/java/com/google/gerrit/acceptance/AbstractDynamicOptionsTest.java b/java/com/google/gerrit/acceptance/AbstractDynamicOptionsTest.java
index 4e8d20d..11f2a41 100644
--- a/java/com/google/gerrit/acceptance/AbstractDynamicOptionsTest.java
+++ b/java/com/google/gerrit/acceptance/AbstractDynamicOptionsTest.java
@@ -82,6 +82,10 @@
   }
 
   public static class PluginOneSshModule extends CommandModule {
+    public PluginOneSshModule() {
+      super(/* slaveMode= */ false);
+    }
+
     @Override
     public void configure() {
       command(LS_SAMPLES).to(ListSamplesCommand.class);
diff --git a/java/com/google/gerrit/acceptance/AbstractNotificationTest.java b/java/com/google/gerrit/acceptance/AbstractNotificationTest.java
index 8a9e56a..296103a 100644
--- a/java/com/google/gerrit/acceptance/AbstractNotificationTest.java
+++ b/java/com/google/gerrit/acceptance/AbstractNotificationTest.java
@@ -26,6 +26,7 @@
 import com.google.common.truth.FailureMetadata;
 import com.google.common.truth.Subject;
 import com.google.common.truth.Truth;
+import com.google.errorprone.annotations.CanIgnoreReturnValue;
 import com.google.gerrit.acceptance.testsuite.request.RequestScopeOperations;
 import com.google.gerrit.common.Nullable;
 import com.google.gerrit.entities.Address;
@@ -109,14 +110,14 @@
       fakeEmailSender = target;
     }
 
-    public FakeEmailSenderSubject didNotSend() {
+    public void didNotSend() {
       Message message = fakeEmailSender.peekMessage();
       if (message != null) {
         failWithoutActual(fact("expected no message", message));
       }
-      return this;
     }
 
+    @CanIgnoreReturnValue
     public FakeEmailSenderSubject sent(String messageType, StagedUsers users) {
       message = fakeEmailSender.nextMessage();
       if (message == null) {
@@ -183,18 +184,22 @@
       return addrList.getAddressList().stream().map(Address::email).collect(toList());
     }
 
+    @CanIgnoreReturnValue
     public FakeEmailSenderSubject to(String... emails) {
       return rcpt(users.supportReviewersByEmail ? TO : null, emails);
     }
 
+    @CanIgnoreReturnValue
     public FakeEmailSenderSubject cc(String... emails) {
       return rcpt(users.supportReviewersByEmail ? CC : null, emails);
     }
 
+    @CanIgnoreReturnValue
     public FakeEmailSenderSubject bcc(String... emails) {
       return rcpt(users.supportReviewersByEmail ? BCC : null, emails);
     }
 
+    @CanIgnoreReturnValue
     public FakeEmailSenderSubject title(String expectedEmailTitle) {
       if (!emailTitle.equals(expectedEmailTitle)) {
         failWithoutActual(
@@ -204,6 +209,7 @@
       return this;
     }
 
+    @CanIgnoreReturnValue
     private FakeEmailSenderSubject rcpt(@Nullable RecipientType type, String[] emails) {
       for (String email : emails) {
         rcpt(type, email);
@@ -230,6 +236,7 @@
       }
     }
 
+    @CanIgnoreReturnValue
     public FakeEmailSenderSubject noOneElse() {
       for (Map.Entry<NotifyType, TestAccount> watchEntry : users.watchers.entrySet()) {
         if (!accountedFor.contains(watchEntry.getValue().email())) {
@@ -257,22 +264,27 @@
       return this;
     }
 
+    @CanIgnoreReturnValue
     public FakeEmailSenderSubject notTo(String... emails) {
       return rcpt(null, emails);
     }
 
+    @CanIgnoreReturnValue
     public FakeEmailSenderSubject to(TestAccount... accounts) {
       return rcpt(TO, accounts);
     }
 
+    @CanIgnoreReturnValue
     public FakeEmailSenderSubject cc(TestAccount... accounts) {
       return rcpt(CC, accounts);
     }
 
+    @CanIgnoreReturnValue
     public FakeEmailSenderSubject bcc(TestAccount... accounts) {
       return rcpt(BCC, accounts);
     }
 
+    @CanIgnoreReturnValue
     public FakeEmailSenderSubject notTo(TestAccount... accounts) {
       return rcpt(null, accounts);
     }
@@ -288,18 +300,22 @@
       rcpt(type, account.email());
     }
 
+    @CanIgnoreReturnValue
     public FakeEmailSenderSubject to(NotifyType... watches) {
       return rcpt(TO, watches);
     }
 
+    @CanIgnoreReturnValue
     public FakeEmailSenderSubject cc(NotifyType... watches) {
       return rcpt(CC, watches);
     }
 
+    @CanIgnoreReturnValue
     public FakeEmailSenderSubject bcc(NotifyType... watches) {
       return rcpt(BCC, watches);
     }
 
+    @CanIgnoreReturnValue
     public FakeEmailSenderSubject notTo(NotifyType... watches) {
       return rcpt(null, watches);
     }
@@ -491,10 +507,12 @@
     }
   }
 
+  @CanIgnoreReturnValue
   protected StagedPreChange stagePreChange(String ref) throws Exception {
     return new StagedPreChange(ref);
   }
 
+  @CanIgnoreReturnValue
   protected StagedPreChange stagePreChange(
       String ref, @Nullable PushOptionGenerator pushOptionGenerator) throws Exception {
     return new StagedPreChange(ref, pushOptionGenerator);
diff --git a/java/com/google/gerrit/acceptance/AcceptanceTestRequestScope.java b/java/com/google/gerrit/acceptance/AcceptanceTestRequestScope.java
index c4bf20c..cac8c73 100644
--- a/java/com/google/gerrit/acceptance/AcceptanceTestRequestScope.java
+++ b/java/com/google/gerrit/acceptance/AcceptanceTestRequestScope.java
@@ -14,6 +14,7 @@
 
 package com.google.gerrit.acceptance;
 
+import com.google.errorprone.annotations.CanIgnoreReturnValue;
 import com.google.gerrit.server.CurrentUser;
 import com.google.gerrit.server.RequestCleanup;
 import com.google.gerrit.server.util.RequestContext;
@@ -134,10 +135,14 @@
     return new Context(ctx, ctx.getSession(), ctx.getUser());
   }
 
+  @CanIgnoreReturnValue
   public Context set(Context ctx) {
     Context old = current.get();
     current.set(ctx);
-    local.setContext(ctx);
+
+    @SuppressWarnings("unused")
+    var unused = local.setContext(ctx);
+
     return old;
   }
 
@@ -153,7 +158,10 @@
     Context ctx = new Context(old.session, old.user, old.created);
 
     current.set(ctx);
-    local.setContext(ctx);
+
+    @SuppressWarnings("unused")
+    var unused = local.setContext(ctx);
+
     return old;
   }
 
diff --git a/java/com/google/gerrit/acceptance/AccountCreator.java b/java/com/google/gerrit/acceptance/AccountCreator.java
index f3881f2..2bdef08 100644
--- a/java/com/google/gerrit/acceptance/AccountCreator.java
+++ b/java/com/google/gerrit/acceptance/AccountCreator.java
@@ -87,7 +87,7 @@
     List<ExternalId> extIds = new ArrayList<>(2);
     String httpPass = null;
     if (username != null) {
-      httpPass = "http-pass";
+      httpPass = externalIdFactory.arePasswordsAllowed() ? "http-pass" : null;
       extIds.add(externalIdFactory.createUsername(username, id, httpPass));
     }
 
diff --git a/java/com/google/gerrit/acceptance/EventRecorder.java b/java/com/google/gerrit/acceptance/EventRecorder.java
index 1618573..702b3b4 100644
--- a/java/com/google/gerrit/acceptance/EventRecorder.java
+++ b/java/com/google/gerrit/acceptance/EventRecorder.java
@@ -172,7 +172,8 @@
   }
 
   public void assertNoRefUpdatedEvents(String project, String branch) throws Exception {
-    getRefUpdatedEvents(project, branch, 0);
+    @SuppressWarnings("unused")
+    var unused = getRefUpdatedEvents(project, branch, 0);
   }
 
   public void assertRefUpdatedEvents(String project, String branch, String... expected)
diff --git a/java/com/google/gerrit/acceptance/ExtensionRegistry.java b/java/com/google/gerrit/acceptance/ExtensionRegistry.java
index f3527f0..d2051d5 100644
--- a/java/com/google/gerrit/acceptance/ExtensionRegistry.java
+++ b/java/com/google/gerrit/acceptance/ExtensionRegistry.java
@@ -14,6 +14,7 @@
 
 package com.google.gerrit.acceptance;
 
+import com.google.errorprone.annotations.CanIgnoreReturnValue;
 import com.google.gerrit.entities.SubmitRequirement;
 import com.google.gerrit.extensions.api.changes.ActionVisitor;
 import com.google.gerrit.extensions.config.CapabilityDefinition;
@@ -45,6 +46,7 @@
 import com.google.gerrit.server.ExceptionHook;
 import com.google.gerrit.server.account.GroupBackend;
 import com.google.gerrit.server.change.ChangeETagComputation;
+import com.google.gerrit.server.change.FilterIncludedIn;
 import com.google.gerrit.server.change.ReviewerSuggestion;
 import com.google.gerrit.server.config.ProjectConfigEntry;
 import com.google.gerrit.server.git.ChangeMessageModifier;
@@ -87,6 +89,7 @@
   private final DynamicSet<GitReferenceUpdatedListener> refUpdatedListeners;
   private final DynamicSet<GitBatchRefUpdateListener> batchRefUpdateListeners;
   private final DynamicSet<FileHistoryWebLink> fileHistoryWebLinks;
+  private final DynamicSet<FilterIncludedIn> filterIncludedIns;
   private final DynamicSet<PatchSetWebLink> patchSetWebLinks;
   private final DynamicSet<ResolveConflictsWebLink> resolveConflictsWebLinks;
   private final DynamicSet<EditWebLink> editWebLinks;
@@ -133,6 +136,7 @@
       DynamicSet<GitReferenceUpdatedListener> refUpdatedListeners,
       DynamicSet<GitBatchRefUpdateListener> batchRefUpdateListeners,
       DynamicSet<FileHistoryWebLink> fileHistoryWebLinks,
+      DynamicSet<FilterIncludedIn> filterIncludedIns,
       DynamicSet<PatchSetWebLink> patchSetWebLinks,
       DynamicSet<ResolveConflictsWebLink> resolveConflictsWebLinks,
       DynamicSet<EditWebLink> editWebLinks,
@@ -174,6 +178,7 @@
     this.refUpdatedListeners = refUpdatedListeners;
     this.batchRefUpdateListeners = batchRefUpdateListeners;
     this.fileHistoryWebLinks = fileHistoryWebLinks;
+    this.filterIncludedIns = filterIncludedIns;
     this.patchSetWebLinks = patchSetWebLinks;
     this.editWebLinks = editWebLinks;
     this.fileWebLinks = fileWebLinks;
@@ -205,172 +210,219 @@
   public class Registration implements AutoCloseable {
     private final List<RegistrationHandle> registrationHandles = new ArrayList<>();
 
+    @CanIgnoreReturnValue
     public Registration add(AccountIndexedListener accountIndexedListener) {
       return add(accountIndexedListeners, accountIndexedListener);
     }
 
+    @CanIgnoreReturnValue
     public Registration add(ChangeIndexedListener changeIndexedListener) {
       return add(changeIndexedListeners, changeIndexedListener);
     }
 
+    @CanIgnoreReturnValue
     public Registration add(GroupIndexedListener groupIndexedListener) {
       return add(groupIndexedListeners, groupIndexedListener);
     }
 
+    @CanIgnoreReturnValue
     public Registration add(ProjectIndexedListener projectIndexedListener) {
       return add(projectIndexedListeners, projectIndexedListener);
     }
 
+    @CanIgnoreReturnValue
     public Registration add(CommitValidationListener commitValidationListener) {
       return add(commitValidationListeners, commitValidationListener);
     }
 
+    @CanIgnoreReturnValue
     public Registration add(TopicEditedListener topicEditedListener) {
       return add(topicEditedListeners, topicEditedListener);
     }
 
+    @CanIgnoreReturnValue
     public Registration add(ExceptionHook exceptionHook) {
       return add(exceptionHooks, exceptionHook);
     }
 
+    @CanIgnoreReturnValue
     public Registration add(PerformanceLogger performanceLogger) {
       return add(performanceLoggers, performanceLogger);
     }
 
+    @CanIgnoreReturnValue
     public Registration add(ProjectCreationValidationListener projectCreationListener) {
       return add(projectCreationValidationListeners, projectCreationListener);
     }
 
+    @CanIgnoreReturnValue
     public Registration add(SubmitRule submitRule) {
       return add(submitRules, submitRule);
     }
 
+    @CanIgnoreReturnValue
     public Registration add(SubmitRequirement submitRequirement) {
       return add(submitRequirements, submitRequirement);
     }
 
+    @CanIgnoreReturnValue
     public Registration add(ChangeHasOperandFactory hasOperand, String exportName) {
       return add(hasOperands, hasOperand, exportName);
     }
 
+    @CanIgnoreReturnValue
     public Registration add(ChangeIsOperandFactory isOperand, String exportName) {
       return add(isOperands, isOperand, exportName);
     }
 
+    @CanIgnoreReturnValue
     public Registration add(ChangeMessageModifier changeMessageModifier) {
       return add(changeMessageModifiers, changeMessageModifier);
     }
 
+    @CanIgnoreReturnValue
     public Registration add(ChangeMessageModifier changeMessageModifier, String exportName) {
       return add(changeMessageModifiers, changeMessageModifier, exportName);
     }
 
+    @CanIgnoreReturnValue
     public Registration add(ChangeETagComputation changeETagComputation) {
       return add(changeETagComputations, changeETagComputation);
     }
 
+    @CanIgnoreReturnValue
     public Registration add(ActionVisitor actionVisitor) {
       return add(actionVisitors, actionVisitor);
     }
 
+    @CanIgnoreReturnValue
     public Registration add(DownloadScheme downloadScheme, String exportName) {
       return add(downloadSchemes, downloadScheme, exportName);
     }
 
+    @CanIgnoreReturnValue
     public Registration add(RefOperationValidationListener refOperationValidationListener) {
       return add(refOperationValidationListeners, refOperationValidationListener);
     }
 
+    @CanIgnoreReturnValue
     public Registration add(CommentAddedListener commentAddedListener) {
       return add(commentAddedListeners, commentAddedListener);
     }
 
+    @CanIgnoreReturnValue
     public Registration add(GitReferenceUpdatedListener refUpdatedListener) {
       return add(refUpdatedListeners, refUpdatedListener);
     }
 
+    @CanIgnoreReturnValue
     public Registration add(GitBatchRefUpdateListener batchRefUpdateListener) {
       return add(batchRefUpdateListeners, batchRefUpdateListener);
     }
 
+    @CanIgnoreReturnValue
     public Registration add(FileHistoryWebLink fileHistoryWebLink) {
       return add(fileHistoryWebLinks, fileHistoryWebLink);
     }
 
+    @CanIgnoreReturnValue
+    public Registration add(FilterIncludedIn filterIncludedIn) {
+      return add(filterIncludedIns, filterIncludedIn);
+    }
+
+    @CanIgnoreReturnValue
     public Registration add(PatchSetWebLink patchSetWebLink) {
       return add(patchSetWebLinks, patchSetWebLink);
     }
 
+    @CanIgnoreReturnValue
     public Registration add(ResolveConflictsWebLink resolveConflictsWebLink) {
       return add(resolveConflictsWebLinks, resolveConflictsWebLink);
     }
 
+    @CanIgnoreReturnValue
     public Registration add(EditWebLink editWebLink) {
       return add(editWebLinks, editWebLink);
     }
 
+    @CanIgnoreReturnValue
     public Registration add(FileWebLink fileWebLink) {
       return add(fileWebLinks, fileWebLink);
     }
 
+    @CanIgnoreReturnValue
     public Registration add(RevisionCreatedListener revisionCreatedListener) {
       return add(revisionCreatedListeners, revisionCreatedListener);
     }
 
+    @CanIgnoreReturnValue
     public Registration add(GroupBackend groupBackend) {
       return add(groupBackends, groupBackend);
     }
 
+    @CanIgnoreReturnValue
     public Registration add(
         AccountActivationValidationListener accountActivationValidationListener) {
       return add(accountActivationValidationListeners, accountActivationValidationListener);
     }
 
+    @CanIgnoreReturnValue
     public Registration add(AccountActivationListener accountDeactivatedListener) {
       return add(accountActivationListeners, accountDeactivatedListener);
     }
 
+    @CanIgnoreReturnValue
     public Registration add(OnSubmitValidationListener onSubmitValidationListener) {
       return add(onSubmitValidationListeners, onSubmitValidationListener);
     }
 
+    @CanIgnoreReturnValue
     public Registration add(WorkInProgressStateChangedListener workInProgressStateChangedListener) {
       return add(workInProgressStateChangedListeners, workInProgressStateChangedListener);
     }
 
+    @CanIgnoreReturnValue
     public Registration add(AttentionSetListener attentionSetListener) {
       return add(attentionSetListeners, attentionSetListener);
     }
 
+    @CanIgnoreReturnValue
     public Registration add(CapabilityDefinition capabilityDefinition, String exportName) {
       return add(capabilityDefinitions, capabilityDefinition, exportName);
     }
 
+    @CanIgnoreReturnValue
     public Registration add(
         PluginProjectPermissionDefinition pluginProjectPermissionDefinition, String exportName) {
       return add(pluginProjectPermissionDefinitions, pluginProjectPermissionDefinition, exportName);
     }
 
+    @CanIgnoreReturnValue
     public Registration add(ProjectConfigEntry pluginConfigEntry, String exportName) {
       return add(pluginConfigEntries, pluginConfigEntry, exportName);
     }
 
+    @CanIgnoreReturnValue
     public Registration add(PluginPushOption pluginPushOption) {
       return add(pluginPushOptions, pluginPushOption);
     }
 
+    @CanIgnoreReturnValue
     public Registration add(OnPostReview onPostReview) {
       return add(onPostReviews, onPostReview);
     }
 
+    @CanIgnoreReturnValue
     public Registration add(ReviewerAddedListener reviewerAddedListener) {
       return add(reviewerAddedListeners, reviewerAddedListener);
     }
 
+    @CanIgnoreReturnValue
     public Registration add(ReviewerDeletedListener reviewerDeletedListener) {
       return add(reviewerDeletedListeners, reviewerDeletedListener);
     }
 
+    @CanIgnoreReturnValue
     public Registration add(ReviewerSuggestion reviewerSuggestion, String exportName) {
       return add(reviewerSuggestions, reviewerSuggestion, exportName);
     }
diff --git a/java/com/google/gerrit/acceptance/FakeGroupAuditService.java b/java/com/google/gerrit/acceptance/FakeGroupAuditService.java
index a1c28b9..433c149 100644
--- a/java/com/google/gerrit/acceptance/FakeGroupAuditService.java
+++ b/java/com/google/gerrit/acceptance/FakeGroupAuditService.java
@@ -19,6 +19,7 @@
 
 import com.google.common.collect.ImmutableList;
 import com.google.common.primitives.Ints;
+import com.google.errorprone.annotations.CanIgnoreReturnValue;
 import com.google.gerrit.extensions.registration.DynamicSet;
 import com.google.gerrit.httpd.GitOverHttpServlet;
 import com.google.gerrit.server.AuditEvent;
@@ -77,6 +78,7 @@
     }
   }
 
+  @CanIgnoreReturnValue
   public ImmutableList<HttpAuditEvent> drainHttpAuditEvents() throws Exception {
     // Assumes that all HttpAuditEvents are produced by GitOverHttpServlet.
     int expectedSize = Ints.checkedCast(httpMetrics.getRequestsStarted() - drainedSoFar.get());
diff --git a/java/com/google/gerrit/acceptance/GitUtil.java b/java/com/google/gerrit/acceptance/GitUtil.java
index 94d329d..335e97c 100644
--- a/java/com/google/gerrit/acceptance/GitUtil.java
+++ b/java/com/google/gerrit/acceptance/GitUtil.java
@@ -20,6 +20,7 @@
 import com.google.common.collect.Iterables;
 import com.google.common.collect.Lists;
 import com.google.common.primitives.Ints;
+import com.google.errorprone.annotations.CanIgnoreReturnValue;
 import com.google.gerrit.common.FooterConstants;
 import com.google.gerrit.entities.Project;
 import java.io.IOException;
@@ -98,6 +99,7 @@
     return testRepo;
   }
 
+  @CanIgnoreReturnValue
   public static Ref createAnnotatedTag(TestRepository<?> testRepo, String name, PersonIdent tagger)
       throws GitAPIException {
     TagCommand cmd =
@@ -105,6 +107,7 @@
     return cmd.call();
   }
 
+  @CanIgnoreReturnValue
   public static Ref updateAnnotatedTag(TestRepository<?> testRepo, String name, PersonIdent tagger)
       throws GitAPIException {
     TagCommand tc = testRepo.git().tag().setName(name);
@@ -117,21 +120,25 @@
     fetch.call();
   }
 
+  @CanIgnoreReturnValue
   public static PushResult pushHead(TestRepository<?> testRepo, String ref) throws GitAPIException {
     return pushHead(testRepo, ref, false);
   }
 
+  @CanIgnoreReturnValue
   public static PushResult pushHead(TestRepository<?> testRepo, String ref, boolean pushTags)
       throws GitAPIException {
     return pushHead(testRepo, ref, pushTags, false);
   }
 
+  @CanIgnoreReturnValue
   public static PushResult pushHead(
       TestRepository<?> testRepo, String ref, boolean pushTags, boolean force)
       throws GitAPIException {
     return pushOne(testRepo, "HEAD", ref, pushTags, force, null);
   }
 
+  @CanIgnoreReturnValue
   public static PushResult pushHead(
       TestRepository<?> testRepo,
       String ref,
@@ -142,11 +149,13 @@
     return pushOne(testRepo, "HEAD", ref, pushTags, force, pushOptions);
   }
 
+  @CanIgnoreReturnValue
   public static PushResult deleteRef(TestRepository<?> testRepo, String ref)
       throws GitAPIException {
     return pushOne(testRepo, "", ref, false, true, null);
   }
 
+  @CanIgnoreReturnValue
   public static PushResult pushOne(
       TestRepository<?> testRepo,
       String source,
diff --git a/java/com/google/gerrit/acceptance/InProcessProtocol.java b/java/com/google/gerrit/acceptance/InProcessProtocol.java
index 17ce595..abcc108 100644
--- a/java/com/google/gerrit/acceptance/InProcessProtocol.java
+++ b/java/com/google/gerrit/acceptance/InProcessProtocol.java
@@ -230,7 +230,9 @@
       // have an easy way to run code when this instance is done being used.
       // Each operation is run in its own thread, so we don't need to recover
       // its original context anyway.
-      threadContext.setContext(req);
+      @SuppressWarnings("unused")
+      var unused = threadContext.setContext(req);
+
       current.set(req);
 
       PermissionBackend.ForProject perm = permissionBackend.currentUser().project(req.project);
@@ -300,7 +302,9 @@
       // have an easy way to run code when this instance is done being used.
       // Each operation is run in its own thread, so we don't need to recover
       // its original context anyway.
-      threadContext.setContext(req);
+      @SuppressWarnings("unused")
+      var unused = threadContext.setContext(req);
+
       current.set(req);
       try {
         permissionBackend
diff --git a/java/com/google/gerrit/acceptance/PushOneCommit.java b/java/com/google/gerrit/acceptance/PushOneCommit.java
index a61fa46..00d4e43 100644
--- a/java/com/google/gerrit/acceptance/PushOneCommit.java
+++ b/java/com/google/gerrit/acceptance/PushOneCommit.java
@@ -95,6 +95,9 @@
     PushOneCommit create(PersonIdent i, TestRepository<?> testRepo);
 
     PushOneCommit create(
+        PersonIdent i, TestRepository<?> testRepo, boolean insertChangeIdIfNotExist);
+
+    PushOneCommit create(
         PersonIdent i, TestRepository<?> testRepo, @Assisted("changeId") String changeId);
 
     PushOneCommit create(
@@ -185,6 +188,23 @@
       Result.Factory pushResultFactory,
       @Assisted PersonIdent i,
       @Assisted TestRepository<?> testRepo,
+      @Assisted boolean insertChangeIdIfNotExist)
+      throws Exception {
+    this(
+        pushResultFactory,
+        i,
+        testRepo,
+        SUBJECT,
+        ImmutableMap.of(FILE_NAME, FILE_CONTENT),
+        /* changeId= */ null,
+        insertChangeIdIfNotExist);
+  }
+
+  @AssistedInject
+  PushOneCommit(
+      Result.Factory pushResultFactory,
+      @Assisted PersonIdent i,
+      @Assisted TestRepository<?> testRepo,
       @Assisted("changeId") String changeId)
       throws Exception {
     this(pushResultFactory, i, testRepo, SUBJECT, FILE_NAME, FILE_CONTENT, changeId);
@@ -210,7 +230,8 @@
       @Assisted String subject,
       @Assisted Map<String, String> files)
       throws Exception {
-    this(pushResultFactory, i, testRepo, subject, files, null);
+    this(
+        pushResultFactory, i, testRepo, subject, files, null, /* insertChangeIdIfNotExist= */ true);
   }
 
   @AssistedInject
@@ -223,7 +244,14 @@
       @Assisted("content") String content,
       @Nullable @Assisted("changeId") String changeId)
       throws Exception {
-    this(pushResultFactory, i, testRepo, subject, ImmutableMap.of(fileName, content), changeId);
+    this(
+        pushResultFactory,
+        i,
+        testRepo,
+        subject,
+        ImmutableMap.of(fileName, content),
+        changeId,
+        /* insertChangeIdIfNotExist= */ true);
   }
 
   @AssistedInject
@@ -235,6 +263,26 @@
       @Assisted Map<String, String> files,
       @Nullable @Assisted("changeId") String changeId)
       throws Exception {
+    this(
+        pushResultFactory,
+        i,
+        testRepo,
+        subject,
+        files,
+        changeId,
+        /* insertChangeIdIfNotExist= */ true);
+  }
+
+  @AssistedInject
+  PushOneCommit(
+      Result.Factory pushResultFactory,
+      @Assisted PersonIdent i,
+      @Assisted TestRepository<?> testRepo,
+      @Assisted("subject") String subject,
+      @Assisted Map<String, String> files,
+      @Nullable @Assisted("changeId") String changeId,
+      @Assisted boolean insertChangeIdIfNotExist)
+      throws Exception {
     this.testRepo = testRepo;
     this.subject = subject;
     this.files = files;
@@ -242,16 +290,19 @@
     this.pushResultFactory = pushResultFactory;
     if (changeId != null) {
       commitBuilder = testRepo.amendRef("HEAD").insertChangeId(changeId.substring(1));
-    } else {
+    } else if (insertChangeIdIfNotExist) {
       if (subject.contains("\nChange-Id: ")) {
         commitBuilder = testRepo.amendRef("HEAD");
       } else {
         commitBuilder = testRepo.branch("HEAD").commit().insertChangeId(nextChangeId());
       }
+    } else {
+      commitBuilder = testRepo.amendRef("HEAD");
     }
     commitBuilder.message(subject).author(i).committer(new PersonIdent(i, testRepo.getDate()));
   }
 
+  @CanIgnoreReturnValue
   public PushOneCommit setParents(List<RevCommit> parents) throws Exception {
     commitBuilder.noParents();
     for (RevCommit p : parents) {
@@ -266,17 +317,20 @@
     return this;
   }
 
+  @CanIgnoreReturnValue
   public PushOneCommit setParent(RevCommit parent) throws Exception {
     commitBuilder.noParents();
     commitBuilder.parent(parent);
     return this;
   }
 
+  @CanIgnoreReturnValue
   public PushOneCommit noParent() {
     commitBuilder.noParents();
     return this;
   }
 
+  @CanIgnoreReturnValue
   public PushOneCommit addFile(String path, String content, int fileMode) throws Exception {
     RevBlob blobId = testRepo.blob(content);
     commitBuilder.edit(
@@ -290,6 +344,7 @@
     return this;
   }
 
+  @CanIgnoreReturnValue
   public PushOneCommit addSymlink(String path, String target) throws Exception {
     RevBlob blobId = testRepo.blob(target);
     commitBuilder.edit(
@@ -303,6 +358,7 @@
     return this;
   }
 
+  @CanIgnoreReturnValue
   public PushOneCommit addGitSubmodule(String modulePath, ObjectId commitId) {
     commitBuilder.edit(
         new PathEdit(modulePath) {
@@ -315,11 +371,13 @@
     return this;
   }
 
+  @CanIgnoreReturnValue
   public PushOneCommit rmFile(String filename) {
     commitBuilder.rm(filename);
     return this;
   }
 
+  @CanIgnoreReturnValue
   public Result to(String ref) throws Exception {
     for (Map.Entry<String, String> e : files.entrySet()) {
       commitBuilder.add(e.getKey(), e.getValue());
@@ -327,6 +385,7 @@
     return execute(ref);
   }
 
+  @CanIgnoreReturnValue
   public Result rm(String ref) throws Exception {
     for (String fileName : files.keySet()) {
       commitBuilder.rm(fileName);
@@ -334,10 +393,11 @@
     return execute(ref);
   }
 
+  @CanIgnoreReturnValue
   public Result execute(String ref) throws Exception {
     RevCommit c = commitBuilder.create();
     if (changeId == null) {
-      changeId = GitUtil.getChangeId(testRepo, c).get();
+      changeId = GitUtil.getChangeId(testRepo, c).orElse(null);
     }
     if (tag != null) {
       TagCommand tagCommand = testRepo.git().tag().setName(tag.name);
@@ -387,7 +447,7 @@
       Result create(
           @Assisted("ref") String ref,
           @Assisted("subject") String subject,
-          @Assisted("changeId") String changeId,
+          @Nullable @Assisted("changeId") String changeId,
           @Nullable PushResult resSubj,
           @Nullable RevCommit commit,
           @Nullable List<String> pushOptions);
@@ -413,7 +473,7 @@
         Provider<InternalChangeQuery> queryProvider,
         @Assisted("ref") String ref,
         @Assisted("subject") String subject,
-        @Assisted("changeId") String changeId,
+        @Assisted("changeId") @Nullable String changeId,
         @Assisted @Nullable PushResult resSubj,
         @Assisted @Nullable RevCommit commit,
         @Assisted @Nullable List<String> pushOptions) {
diff --git a/java/com/google/gerrit/acceptance/SshSession.java b/java/com/google/gerrit/acceptance/SshSession.java
index 23bfd0b..ac1a73d 100644
--- a/java/com/google/gerrit/acceptance/SshSession.java
+++ b/java/com/google/gerrit/acceptance/SshSession.java
@@ -17,6 +17,7 @@
 import static com.google.common.truth.Truth.assertThat;
 import static com.google.common.truth.Truth.assertWithMessage;
 
+import com.google.errorprone.annotations.CanIgnoreReturnValue;
 import com.google.gerrit.acceptance.testsuite.account.TestAccount;
 import com.google.gerrit.acceptance.testsuite.account.TestSshKeys;
 import java.io.Reader;
@@ -38,6 +39,7 @@
 
   public abstract void close();
 
+  @CanIgnoreReturnValue
   public abstract String exec(String command) throws Exception;
 
   public abstract int execAndReturnStatus(String command) throws Exception;
diff --git a/java/com/google/gerrit/acceptance/SshSessionMina.java b/java/com/google/gerrit/acceptance/SshSessionMina.java
index 89096e4..bac4ed6 100644
--- a/java/com/google/gerrit/acceptance/SshSessionMina.java
+++ b/java/com/google/gerrit/acceptance/SshSessionMina.java
@@ -79,7 +79,8 @@
 
   @Override
   public void open() throws Exception {
-    getMinaSession();
+    @SuppressWarnings("unused")
+    var unused = getMinaSession();
   }
 
   @Override
diff --git a/java/com/google/gerrit/acceptance/StandaloneSiteTest.java b/java/com/google/gerrit/acceptance/StandaloneSiteTest.java
index dcb49a5..01e705c 100644
--- a/java/com/google/gerrit/acceptance/StandaloneSiteTest.java
+++ b/java/com/google/gerrit/acceptance/StandaloneSiteTest.java
@@ -24,6 +24,7 @@
 import com.google.common.collect.ImmutableMap;
 import com.google.common.collect.Streams;
 import com.google.common.io.ByteStreams;
+import com.google.errorprone.annotations.CanIgnoreReturnValue;
 import com.google.gerrit.common.Nullable;
 import com.google.gerrit.extensions.api.GerritApi;
 import com.google.gerrit.extensions.api.groups.GroupInput;
@@ -75,7 +76,8 @@
 
       try {
         // ServerContext ctor is called multiple times but the group can be only created once
-        gApi.groups().id("Group");
+        @SuppressWarnings("unused")
+        var unused = gApi.groups().id("Group");
       } catch (ResourceNotFoundException e) {
         GroupInput in = new GroupInput();
         in.members = Collections.singletonList("admin");
@@ -211,11 +213,13 @@
     runGerrit(Arrays.stream(multiArgs).flatMap(Streams::stream).toArray(String[]::new));
   }
 
+  @CanIgnoreReturnValue
   protected static String execute(
       ImmutableList<String> cmd, File dir, ImmutableMap<String, String> env) throws IOException {
     return execute(cmd, dir, env, null);
   }
 
+  @CanIgnoreReturnValue
   protected static String execute(
       ImmutableList<String> cmd,
       File dir,
diff --git a/java/com/google/gerrit/acceptance/config/BUILD b/java/com/google/gerrit/acceptance/config/BUILD
index 0da68b0..1da975f 100644
--- a/java/com/google/gerrit/acceptance/config/BUILD
+++ b/java/com/google/gerrit/acceptance/config/BUILD
@@ -12,5 +12,6 @@
         "//lib:jgit",
         "//lib/auto:auto-value",
         "//lib/auto:auto-value-annotations",
+        "//lib/errorprone:annotations",
     ],
 )
diff --git a/java/com/google/gerrit/server/StarredChangesUtil.java b/java/com/google/gerrit/acceptance/config/package-info.java
similarity index 64%
copy from java/com/google/gerrit/server/StarredChangesUtil.java
copy to java/com/google/gerrit/acceptance/config/package-info.java
index 0709b86..753b75a 100644
--- a/java/com/google/gerrit/server/StarredChangesUtil.java
+++ b/java/com/google/gerrit/acceptance/config/package-info.java
@@ -1,4 +1,4 @@
-// Copyright (C) 2015 The Android Open Source Project
+// Copyright (C) 2023 The Android Open Source Project
 //
 // Licensed under the Apache License, Version 2.0 (the "License");
 // you may not use this file except in compliance with the License.
@@ -12,8 +12,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package com.google.gerrit.server;
+@CheckReturnValue
+package com.google.gerrit.acceptance.config;
 
-// TODO - delete this class. It cannot be deleted as part of the original refactoring since it's
-// used by a plugin.
-public interface StarredChangesUtil extends StarredChangesReader, StarredChangesWriter {}
+import com.google.errorprone.annotations.CheckReturnValue;
diff --git a/java/com/google/gerrit/server/StarredChangesUtil.java b/java/com/google/gerrit/acceptance/package-info.java
similarity index 64%
copy from java/com/google/gerrit/server/StarredChangesUtil.java
copy to java/com/google/gerrit/acceptance/package-info.java
index 0709b86..428b5fb 100644
--- a/java/com/google/gerrit/server/StarredChangesUtil.java
+++ b/java/com/google/gerrit/acceptance/package-info.java
@@ -1,4 +1,4 @@
-// Copyright (C) 2015 The Android Open Source Project
+// Copyright (C) 2023 The Android Open Source Project
 //
 // Licensed under the Apache License, Version 2.0 (the "License");
 // you may not use this file except in compliance with the License.
@@ -12,8 +12,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package com.google.gerrit.server;
+@CheckReturnValue
+package com.google.gerrit.acceptance;
 
-// TODO - delete this class. It cannot be deleted as part of the original refactoring since it's
-// used by a plugin.
-public interface StarredChangesUtil extends StarredChangesReader, StarredChangesWriter {}
+import com.google.errorprone.annotations.CheckReturnValue;
diff --git a/java/com/google/gerrit/server/StarredChangesUtil.java b/java/com/google/gerrit/acceptance/rest/package-info.java
similarity index 64%
copy from java/com/google/gerrit/server/StarredChangesUtil.java
copy to java/com/google/gerrit/acceptance/rest/package-info.java
index 0709b86..923b0c5 100644
--- a/java/com/google/gerrit/server/StarredChangesUtil.java
+++ b/java/com/google/gerrit/acceptance/rest/package-info.java
@@ -1,4 +1,4 @@
-// Copyright (C) 2015 The Android Open Source Project
+// Copyright (C) 2023 The Android Open Source Project
 //
 // Licensed under the Apache License, Version 2.0 (the "License");
 // you may not use this file except in compliance with the License.
@@ -12,8 +12,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package com.google.gerrit.server;
+@CheckReturnValue
+package com.google.gerrit.acceptance.rest;
 
-// TODO - delete this class. It cannot be deleted as part of the original refactoring since it's
-// used by a plugin.
-public interface StarredChangesUtil extends StarredChangesReader, StarredChangesWriter {}
+import com.google.errorprone.annotations.CheckReturnValue;
diff --git a/java/com/google/gerrit/acceptance/ssh/TestSshCommandModule.java b/java/com/google/gerrit/acceptance/ssh/TestSshCommandModule.java
index 626092b..f20851c 100644
--- a/java/com/google/gerrit/acceptance/ssh/TestSshCommandModule.java
+++ b/java/com/google/gerrit/acceptance/ssh/TestSshCommandModule.java
@@ -17,6 +17,10 @@
 import com.google.gerrit.sshd.CommandModule;
 
 public class TestSshCommandModule extends CommandModule {
+  public TestSshCommandModule() {
+    super(/* slaveMode= */ false);
+  }
+
   @Override
   protected void configure() {
     command("graceful").to(GracefulCommand.class);
diff --git a/java/com/google/gerrit/server/StarredChangesUtil.java b/java/com/google/gerrit/acceptance/ssh/package-info.java
similarity index 64%
copy from java/com/google/gerrit/server/StarredChangesUtil.java
copy to java/com/google/gerrit/acceptance/ssh/package-info.java
index 0709b86..940b42e 100644
--- a/java/com/google/gerrit/server/StarredChangesUtil.java
+++ b/java/com/google/gerrit/acceptance/ssh/package-info.java
@@ -1,4 +1,4 @@
-// Copyright (C) 2015 The Android Open Source Project
+// Copyright (C) 2023 The Android Open Source Project
 //
 // Licensed under the Apache License, Version 2.0 (the "License");
 // you may not use this file except in compliance with the License.
@@ -12,8 +12,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package com.google.gerrit.server;
+@CheckReturnValue
+package com.google.gerrit.acceptance.ssh;
 
-// TODO - delete this class. It cannot be deleted as part of the original refactoring since it's
-// used by a plugin.
-public interface StarredChangesUtil extends StarredChangesReader, StarredChangesWriter {}
+import com.google.errorprone.annotations.CheckReturnValue;
diff --git a/java/com/google/gerrit/acceptance/testsuite/account/AccountOperationsImpl.java b/java/com/google/gerrit/acceptance/testsuite/account/AccountOperationsImpl.java
index edbb1ee..577cda8 100644
--- a/java/com/google/gerrit/acceptance/testsuite/account/AccountOperationsImpl.java
+++ b/java/com/google/gerrit/acceptance/testsuite/account/AccountOperationsImpl.java
@@ -19,6 +19,7 @@
 
 import com.google.common.collect.ImmutableSet;
 import com.google.common.collect.Sets;
+import com.google.errorprone.annotations.CanIgnoreReturnValue;
 import com.google.gerrit.entities.Account;
 import com.google.gerrit.server.Sequences;
 import com.google.gerrit.server.ServerInitiated;
@@ -66,7 +67,8 @@
 
   @Override
   public TestAccountCreation.Builder newAccount() {
-    return TestAccountCreation.builder(this::createAccount);
+    return TestAccountCreation.builder(
+        this::createAccount, externalIdFactory.arePasswordsAllowed());
   }
 
   private Account.Id createAccount(TestAccountCreation testAccountCreation) throws Exception {
@@ -159,6 +161,7 @@
       checkState(updatedAccount.isPresent(), "Tried to update non-existing test account");
     }
 
+    @CanIgnoreReturnValue
     private Optional<AccountState> updateAccount(ConfigureDeltaFromState configureDeltaFromState)
         throws IOException, ConfigInvalidException {
       return accountsUpdate.update("Update Test Account", accountId, configureDeltaFromState);
diff --git a/java/com/google/gerrit/acceptance/testsuite/account/TestAccountCreation.java b/java/com/google/gerrit/acceptance/testsuite/account/TestAccountCreation.java
index 042dc9a..5d40517 100644
--- a/java/com/google/gerrit/acceptance/testsuite/account/TestAccountCreation.java
+++ b/java/com/google/gerrit/acceptance/testsuite/account/TestAccountCreation.java
@@ -18,6 +18,7 @@
 
 import com.google.auto.value.AutoValue;
 import com.google.common.collect.ImmutableSet;
+import com.google.errorprone.annotations.CanIgnoreReturnValue;
 import com.google.gerrit.acceptance.testsuite.ThrowingFunction;
 import com.google.gerrit.entities.Account;
 import java.util.Optional;
@@ -41,50 +42,62 @@
 
   abstract ThrowingFunction<TestAccountCreation, Account.Id> accountCreator();
 
-  public static Builder builder(ThrowingFunction<TestAccountCreation, Account.Id> accountCreator) {
-    return new AutoValue_TestAccountCreation.Builder()
-        .accountCreator(accountCreator)
-        .httpPassword("http-pass");
+  public static Builder builder(
+      ThrowingFunction<TestAccountCreation, Account.Id> accountCreator,
+      boolean arePasswordsAllowed) {
+    TestAccountCreation.Builder builder =
+        new AutoValue_TestAccountCreation.Builder().accountCreator(accountCreator);
+    if (arePasswordsAllowed) {
+      builder.httpPassword("http-pass");
+    }
+    return builder;
   }
 
   @AutoValue.Builder
   public abstract static class Builder {
     public abstract Builder fullname(String fullname);
 
+    @CanIgnoreReturnValue
     public Builder clearFullname() {
       return fullname("");
     }
 
     public abstract Builder httpPassword(String httpPassword);
 
+    @CanIgnoreReturnValue
     public Builder clearHttpPassword() {
       return httpPassword("");
     }
 
     public abstract Builder preferredEmail(String preferredEmail);
 
+    @CanIgnoreReturnValue
     public Builder clearPreferredEmail() {
       return preferredEmail("");
     }
 
     public abstract Builder username(String username);
 
+    @CanIgnoreReturnValue
     public Builder clearUsername() {
       return username("");
     }
 
     public abstract Builder status(String status);
 
+    @CanIgnoreReturnValue
     public Builder clearStatus() {
       return status("");
     }
 
     abstract Builder active(boolean active);
 
+    @CanIgnoreReturnValue
     public Builder active() {
       return active(true);
     }
 
+    @CanIgnoreReturnValue
     public Builder inactive() {
       return active(false);
     }
@@ -93,6 +106,7 @@
 
     abstract ImmutableSet.Builder<String> secondaryEmailsBuilder();
 
+    @CanIgnoreReturnValue
     public Builder addSecondaryEmail(String secondaryEmail) {
       secondaryEmailsBuilder().add(secondaryEmail);
       return this;
@@ -103,6 +117,7 @@
 
     abstract TestAccountCreation autoBuild();
 
+    @CanIgnoreReturnValue
     public Account.Id create() {
       TestAccountCreation accountCreation = autoBuild();
       if (accountCreation.preferredEmail().isPresent()) {
diff --git a/java/com/google/gerrit/server/StarredChangesUtil.java b/java/com/google/gerrit/acceptance/testsuite/account/package-info.java
similarity index 64%
copy from java/com/google/gerrit/server/StarredChangesUtil.java
copy to java/com/google/gerrit/acceptance/testsuite/account/package-info.java
index 0709b86..e211ecd 100644
--- a/java/com/google/gerrit/server/StarredChangesUtil.java
+++ b/java/com/google/gerrit/acceptance/testsuite/account/package-info.java
@@ -1,4 +1,4 @@
-// Copyright (C) 2015 The Android Open Source Project
+// Copyright (C) 2023 The Android Open Source Project
 //
 // Licensed under the Apache License, Version 2.0 (the "License");
 // you may not use this file except in compliance with the License.
@@ -12,8 +12,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package com.google.gerrit.server;
+@CheckReturnValue
+package com.google.gerrit.acceptance.testsuite.account;
 
-// TODO - delete this class. It cannot be deleted as part of the original refactoring since it's
-// used by a plugin.
-public interface StarredChangesUtil extends StarredChangesReader, StarredChangesWriter {}
+import com.google.errorprone.annotations.CheckReturnValue;
diff --git a/java/com/google/gerrit/acceptance/testsuite/change/PerPatchsetOperationsImpl.java b/java/com/google/gerrit/acceptance/testsuite/change/PerPatchsetOperationsImpl.java
index 3bd355b..1fd780f 100644
--- a/java/com/google/gerrit/acceptance/testsuite/change/PerPatchsetOperationsImpl.java
+++ b/java/com/google/gerrit/acceptance/testsuite/change/PerPatchsetOperationsImpl.java
@@ -181,7 +181,8 @@
               side,
               message,
               unresolved,
-              parentUuid);
+              parentUuid,
+              null);
       // For draft comments, only the tag set on the HumanComment (and not on the ChangeUpdate)
       // matters.
       commentCreation.tag().ifPresent(tag -> newComment.tag = tag);
diff --git a/java/com/google/gerrit/acceptance/testsuite/change/TestChangeCreation.java b/java/com/google/gerrit/acceptance/testsuite/change/TestChangeCreation.java
index a0746e2..a84e3f04 100644
--- a/java/com/google/gerrit/acceptance/testsuite/change/TestChangeCreation.java
+++ b/java/com/google/gerrit/acceptance/testsuite/change/TestChangeCreation.java
@@ -20,6 +20,7 @@
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableMap;
 import com.google.gerrit.acceptance.testsuite.ThrowingFunction;
+import com.google.gerrit.common.UsedAt;
 import com.google.gerrit.entities.Account;
 import com.google.gerrit.entities.Change;
 import com.google.gerrit.entities.Project;
@@ -32,6 +33,9 @@
 /** Initial attributes of the change. If not provided, arbitrary values will be used. */
 @AutoValue
 public abstract class TestChangeCreation {
+  @UsedAt(UsedAt.Project.GOOGLE)
+  public abstract Optional<String> host();
+
   public abstract Optional<Project.NameKey> project();
 
   public abstract String branch();
@@ -72,6 +76,10 @@
 
   @AutoValue.Builder
   public abstract static class Builder {
+    /** Host name in a multi-tenant deployment. */
+    @UsedAt(UsedAt.Project.GOOGLE)
+    public abstract Builder host(String host);
+
     /** Target project/Repository of the change. Must be an existing project. */
     public abstract Builder project(Project.NameKey project);
 
diff --git a/java/com/google/gerrit/acceptance/testsuite/change/TestCommentCreation.java b/java/com/google/gerrit/acceptance/testsuite/change/TestCommentCreation.java
index 2031bde..9828e6c 100644
--- a/java/com/google/gerrit/acceptance/testsuite/change/TestCommentCreation.java
+++ b/java/com/google/gerrit/acceptance/testsuite/change/TestCommentCreation.java
@@ -15,6 +15,7 @@
 package com.google.gerrit.acceptance.testsuite.change;
 
 import com.google.auto.value.AutoValue;
+import com.google.errorprone.annotations.CanIgnoreReturnValue;
 import com.google.gerrit.acceptance.testsuite.ThrowingFunction;
 import com.google.gerrit.acceptance.testsuite.change.TestRange.Position;
 import com.google.gerrit.common.Nullable;
@@ -68,6 +69,7 @@
   @AutoValue.Builder
   public abstract static class Builder {
 
+    @CanIgnoreReturnValue
     public Builder noMessage() {
       return message("");
     }
@@ -76,11 +78,13 @@
     public abstract Builder message(String message);
 
     /** Indicates a patchset-level comment. */
+    @CanIgnoreReturnValue
     public Builder onPatchsetLevel() {
       return file(Patch.PATCHSET_LEVEL);
     }
 
     /** Indicates a file comment. The comment will be on the specified file. */
+    @CanIgnoreReturnValue
     public Builder onFileLevelOf(String filePath) {
       return file(filePath).line(null).range(null);
     }
@@ -122,6 +126,7 @@
      * <p>On the UI, such comments are shown on the right side of a diff view when a diff against
      * base is selected. See {@link #onParentCommit()} for comments shown on the left side.
      */
+    @CanIgnoreReturnValue
     public Builder onPatchsetCommit() {
       return side(CommentSide.PATCHSET_COMMIT);
     }
@@ -135,11 +140,13 @@
      *
      * <p>For merge commits, this indicates the first parent commit.
      */
+    @CanIgnoreReturnValue
     public Builder onParentCommit() {
       return side(CommentSide.PARENT_COMMIT);
     }
 
     /** Like {@link #onParentCommit()} but for the second parent of a merge commit. */
+    @CanIgnoreReturnValue
     public Builder onSecondParentCommit() {
       return side(CommentSide.SECOND_PARENT_COMMIT);
     }
@@ -148,6 +155,7 @@
      * Like {@link #onParentCommit()} but for the AutoMerge commit created from the parents of a
      * merge commit.
      */
+    @CanIgnoreReturnValue
     public Builder onAutoMergeCommit() {
       return side(CommentSide.AUTO_MERGE_COMMIT);
     }
@@ -155,11 +163,13 @@
     abstract Builder side(CommentSide side);
 
     /** Indicates a resolved comment. */
+    @CanIgnoreReturnValue
     public Builder resolved() {
       return unresolved(false);
     }
 
     /** Indicates an unresolved comment. */
+    @CanIgnoreReturnValue
     public Builder unresolved() {
       return unresolved(true);
     }
@@ -211,6 +221,7 @@
      *
      * @return the UUID of the created comment
      */
+    @CanIgnoreReturnValue
     public String create() {
       TestCommentCreation commentCreation = autoBuild();
       return commentCreation.commentCreator().applyAndThrowSilently(commentCreation);
diff --git a/java/com/google/gerrit/acceptance/testsuite/change/TestPatchsetCreation.java b/java/com/google/gerrit/acceptance/testsuite/change/TestPatchsetCreation.java
index f8ca977..d9ac490 100644
--- a/java/com/google/gerrit/acceptance/testsuite/change/TestPatchsetCreation.java
+++ b/java/com/google/gerrit/acceptance/testsuite/change/TestPatchsetCreation.java
@@ -18,6 +18,7 @@
 
 import com.google.auto.value.AutoValue;
 import com.google.common.collect.ImmutableList;
+import com.google.errorprone.annotations.CanIgnoreReturnValue;
 import com.google.gerrit.acceptance.testsuite.ThrowingFunction;
 import com.google.gerrit.entities.Account;
 import com.google.gerrit.entities.PatchSet;
@@ -181,6 +182,7 @@
      *
      * @return the {@code PatchSet.Id} of the created patchset
      */
+    @CanIgnoreReturnValue
     public PatchSet.Id create() {
       TestPatchsetCreation patchsetCreation = build();
       return patchsetCreation.patchsetCreator().applyAndThrowSilently(patchsetCreation);
diff --git a/java/com/google/gerrit/server/StarredChangesUtil.java b/java/com/google/gerrit/acceptance/testsuite/change/package-info.java
similarity index 64%
copy from java/com/google/gerrit/server/StarredChangesUtil.java
copy to java/com/google/gerrit/acceptance/testsuite/change/package-info.java
index 0709b86..3863f72 100644
--- a/java/com/google/gerrit/server/StarredChangesUtil.java
+++ b/java/com/google/gerrit/acceptance/testsuite/change/package-info.java
@@ -1,4 +1,4 @@
-// Copyright (C) 2015 The Android Open Source Project
+// Copyright (C) 2023 The Android Open Source Project
 //
 // Licensed under the Apache License, Version 2.0 (the "License");
 // you may not use this file except in compliance with the License.
@@ -12,8 +12,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package com.google.gerrit.server;
+@CheckReturnValue
+package com.google.gerrit.acceptance.testsuite.change;
 
-// TODO - delete this class. It cannot be deleted as part of the original refactoring since it's
-// used by a plugin.
-public interface StarredChangesUtil extends StarredChangesReader, StarredChangesWriter {}
+import com.google.errorprone.annotations.CheckReturnValue;
diff --git a/java/com/google/gerrit/acceptance/testsuite/group/BUILD b/java/com/google/gerrit/acceptance/testsuite/group/BUILD
index 2052105..2bc2b62 100644
--- a/java/com/google/gerrit/acceptance/testsuite/group/BUILD
+++ b/java/com/google/gerrit/acceptance/testsuite/group/BUILD
@@ -21,6 +21,7 @@
         "//lib/auto:auto-value",
         "//lib/auto:auto-value-annotations",
         "//lib/commons:lang3",
+        "//lib/errorprone:annotations",
         "//lib/guice",
     ],
 )
diff --git a/java/com/google/gerrit/acceptance/testsuite/group/TestGroupCreation.java b/java/com/google/gerrit/acceptance/testsuite/group/TestGroupCreation.java
index 99899cf..7549c84 100644
--- a/java/com/google/gerrit/acceptance/testsuite/group/TestGroupCreation.java
+++ b/java/com/google/gerrit/acceptance/testsuite/group/TestGroupCreation.java
@@ -19,6 +19,7 @@
 import com.google.auto.value.AutoValue;
 import com.google.common.collect.ImmutableSet;
 import com.google.common.collect.Sets;
+import com.google.errorprone.annotations.CanIgnoreReturnValue;
 import com.google.gerrit.acceptance.testsuite.ThrowingFunction;
 import com.google.gerrit.entities.Account;
 import com.google.gerrit.entities.AccountGroup;
@@ -54,6 +55,7 @@
 
     public abstract Builder description(String description);
 
+    @CanIgnoreReturnValue
     public Builder clearDescription() {
       return description("");
     }
@@ -62,10 +64,12 @@
 
     public abstract Builder visibleToAll(boolean visibleToAll);
 
+    @CanIgnoreReturnValue
     public Builder clearMembers() {
       return members(ImmutableSet.of());
     }
 
+    @CanIgnoreReturnValue
     public Builder members(Account.Id member1, Account.Id... otherMembers) {
       return members(Sets.union(ImmutableSet.of(member1), ImmutableSet.copyOf(otherMembers)));
     }
@@ -74,15 +78,18 @@
 
     abstract ImmutableSet.Builder<Account.Id> membersBuilder();
 
+    @CanIgnoreReturnValue
     public Builder addMember(Account.Id member) {
       membersBuilder().add(member);
       return this;
     }
 
+    @CanIgnoreReturnValue
     public Builder clearSubgroups() {
       return subgroups(ImmutableSet.of());
     }
 
+    @CanIgnoreReturnValue
     public Builder subgroups(AccountGroup.UUID subgroup1, AccountGroup.UUID... otherSubgroups) {
       return subgroups(Sets.union(ImmutableSet.of(subgroup1), ImmutableSet.copyOf(otherSubgroups)));
     }
@@ -91,6 +98,7 @@
 
     abstract ImmutableSet.Builder<AccountGroup.UUID> subgroupsBuilder();
 
+    @CanIgnoreReturnValue
     public Builder addSubgroup(AccountGroup.UUID subgroup) {
       subgroupsBuilder().add(subgroup);
       return this;
@@ -106,6 +114,7 @@
      *
      * @return the UUID of the created group
      */
+    @CanIgnoreReturnValue
     public AccountGroup.UUID create() {
       TestGroupCreation groupCreation = autoBuild();
       return testRefAction(() -> groupCreation.groupCreator().applyAndThrowSilently(groupCreation));
diff --git a/java/com/google/gerrit/acceptance/testsuite/group/TestGroupUpdate.java b/java/com/google/gerrit/acceptance/testsuite/group/TestGroupUpdate.java
index 47c7117..e375760 100644
--- a/java/com/google/gerrit/acceptance/testsuite/group/TestGroupUpdate.java
+++ b/java/com/google/gerrit/acceptance/testsuite/group/TestGroupUpdate.java
@@ -17,6 +17,7 @@
 import com.google.auto.value.AutoValue;
 import com.google.common.collect.ImmutableSet;
 import com.google.common.collect.Sets;
+import com.google.errorprone.annotations.CanIgnoreReturnValue;
 import com.google.gerrit.acceptance.testsuite.ThrowingConsumer;
 import com.google.gerrit.entities.Account;
 import com.google.gerrit.entities.AccountGroup;
@@ -56,6 +57,7 @@
 
     public abstract Builder description(String description);
 
+    @CanIgnoreReturnValue
     public Builder clearDescription() {
       return description("");
     }
@@ -69,10 +71,12 @@
 
     abstract Function<ImmutableSet<Account.Id>, Set<Account.Id>> memberModification();
 
+    @CanIgnoreReturnValue
     public Builder clearMembers() {
       return memberModification(originalMembers -> ImmutableSet.of());
     }
 
+    @CanIgnoreReturnValue
     public Builder addMember(Account.Id member) {
       Function<ImmutableSet<Account.Id>, Set<Account.Id>> previousModification =
           memberModification();
@@ -82,6 +86,7 @@
       return this;
     }
 
+    @CanIgnoreReturnValue
     public Builder removeMember(Account.Id member) {
       Function<ImmutableSet<Account.Id>, Set<Account.Id>> previousModification =
           memberModification();
@@ -98,10 +103,12 @@
     abstract Function<ImmutableSet<AccountGroup.UUID>, Set<AccountGroup.UUID>>
         subgroupModification();
 
+    @CanIgnoreReturnValue
     public Builder clearSubgroups() {
       return subgroupModification(originalMembers -> ImmutableSet.of());
     }
 
+    @CanIgnoreReturnValue
     public Builder addSubgroup(AccountGroup.UUID subgroup) {
       Function<ImmutableSet<AccountGroup.UUID>, Set<AccountGroup.UUID>> previousModification =
           subgroupModification();
@@ -111,6 +118,7 @@
       return this;
     }
 
+    @CanIgnoreReturnValue
     public Builder removeSubgroup(AccountGroup.UUID subgroup) {
       Function<ImmutableSet<AccountGroup.UUID>, Set<AccountGroup.UUID>> previousModification =
           subgroupModification();
diff --git a/java/com/google/gerrit/server/StarredChangesUtil.java b/java/com/google/gerrit/acceptance/testsuite/group/package-info.java
similarity index 64%
copy from java/com/google/gerrit/server/StarredChangesUtil.java
copy to java/com/google/gerrit/acceptance/testsuite/group/package-info.java
index 0709b86..78778e1 100644
--- a/java/com/google/gerrit/server/StarredChangesUtil.java
+++ b/java/com/google/gerrit/acceptance/testsuite/group/package-info.java
@@ -1,4 +1,4 @@
-// Copyright (C) 2015 The Android Open Source Project
+// Copyright (C) 2023 The Android Open Source Project
 //
 // Licensed under the Apache License, Version 2.0 (the "License");
 // you may not use this file except in compliance with the License.
@@ -12,8 +12,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package com.google.gerrit.server;
+@CheckReturnValue
+package com.google.gerrit.acceptance.testsuite.group;
 
-// TODO - delete this class. It cannot be deleted as part of the original refactoring since it's
-// used by a plugin.
-public interface StarredChangesUtil extends StarredChangesReader, StarredChangesWriter {}
+import com.google.errorprone.annotations.CheckReturnValue;
diff --git a/java/com/google/gerrit/server/StarredChangesUtil.java b/java/com/google/gerrit/acceptance/testsuite/package-info.java
similarity index 64%
copy from java/com/google/gerrit/server/StarredChangesUtil.java
copy to java/com/google/gerrit/acceptance/testsuite/package-info.java
index 0709b86..2f43e09 100644
--- a/java/com/google/gerrit/server/StarredChangesUtil.java
+++ b/java/com/google/gerrit/acceptance/testsuite/package-info.java
@@ -1,4 +1,4 @@
-// Copyright (C) 2015 The Android Open Source Project
+// Copyright (C) 2023 The Android Open Source Project
 //
 // Licensed under the Apache License, Version 2.0 (the "License");
 // you may not use this file except in compliance with the License.
@@ -12,8 +12,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package com.google.gerrit.server;
+@CheckReturnValue
+package com.google.gerrit.acceptance.testsuite;
 
-// TODO - delete this class. It cannot be deleted as part of the original refactoring since it's
-// used by a plugin.
-public interface StarredChangesUtil extends StarredChangesReader, StarredChangesWriter {}
+import com.google.errorprone.annotations.CheckReturnValue;
diff --git a/java/com/google/gerrit/acceptance/testsuite/project/BUILD b/java/com/google/gerrit/acceptance/testsuite/project/BUILD
index 4ac2705..be51a64 100644
--- a/java/com/google/gerrit/acceptance/testsuite/project/BUILD
+++ b/java/com/google/gerrit/acceptance/testsuite/project/BUILD
@@ -21,6 +21,7 @@
         "//lib/auto:auto-value",
         "//lib/auto:auto-value-annotations",
         "//lib/commons:lang3",
+        "//lib/errorprone:annotations",
         "//lib/guice",
     ],
 )
diff --git a/java/com/google/gerrit/acceptance/testsuite/project/TestProjectCreation.java b/java/com/google/gerrit/acceptance/testsuite/project/TestProjectCreation.java
index 3337fc3..e8df7ed 100644
--- a/java/com/google/gerrit/acceptance/testsuite/project/TestProjectCreation.java
+++ b/java/com/google/gerrit/acceptance/testsuite/project/TestProjectCreation.java
@@ -19,6 +19,7 @@
 import com.google.auto.value.AutoValue;
 import com.google.common.collect.ImmutableSet;
 import com.google.common.collect.Sets;
+import com.google.errorprone.annotations.CanIgnoreReturnValue;
 import com.google.gerrit.acceptance.testsuite.ThrowingFunction;
 import com.google.gerrit.entities.AccountGroup;
 import com.google.gerrit.entities.Project;
@@ -66,6 +67,7 @@
      * "refs/heads/" prefix of the branch name can be omitted. The specified branches are ignored if
      * {@link #noEmptyCommit()} is used.
      */
+    @CanIgnoreReturnValue
     public TestProjectCreation.Builder branches(String branch1, String... otherBranches) {
       return branches(Sets.union(ImmutableSet.of(branch1), ImmutableSet.copyOf(otherBranches)));
     }
@@ -77,10 +79,12 @@
     public abstract TestProjectCreation.Builder permissionOnly(boolean value);
 
     /** Skips the empty commit on creation. This means that project's branches will not exist. */
+    @CanIgnoreReturnValue
     public TestProjectCreation.Builder noEmptyCommit() {
       return createEmptyCommit(false);
     }
 
+    @CanIgnoreReturnValue
     public TestProjectCreation.Builder addOwner(AccountGroup.UUID owner) {
       ownersBuilder().add(requireNonNull(owner, "owner"));
       return this;
@@ -98,6 +102,7 @@
      *
      * @return the name of the created project
      */
+    @CanIgnoreReturnValue
     public Project.NameKey create() {
       TestProjectCreation creation = autoBuild();
       return creation.projectCreator().applyAndThrowSilently(creation);
diff --git a/java/com/google/gerrit/acceptance/testsuite/project/TestProjectUpdate.java b/java/com/google/gerrit/acceptance/testsuite/project/TestProjectUpdate.java
index 5634c78..cc57ba6 100644
--- a/java/com/google/gerrit/acceptance/testsuite/project/TestProjectUpdate.java
+++ b/java/com/google/gerrit/acceptance/testsuite/project/TestProjectUpdate.java
@@ -21,6 +21,7 @@
 import com.google.auto.value.AutoValue;
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableMap;
+import com.google.errorprone.annotations.CanIgnoreReturnValue;
 import com.google.gerrit.acceptance.testsuite.ThrowingConsumer;
 import com.google.gerrit.common.data.GlobalCapability;
 import com.google.gerrit.entities.AccountGroup;
@@ -75,6 +76,7 @@
       abstract Optional<Integer> max();
 
       /** Sets the minimum and maximum values for the capability. */
+      @CanIgnoreReturnValue
       public Builder range(int min, int max) {
         checkNonInvertedRange(min, max);
         return min(min).max(max);
@@ -353,61 +355,72 @@
      * Removes all access sections. Useful when testing against a specific set of access sections or
      * permissions.
      */
+    @CanIgnoreReturnValue
     public Builder removeAllAccessSections() {
       return removeAllAccessSections(true);
     }
 
     /** Adds a permission to be included in this update. */
+    @CanIgnoreReturnValue
     public Builder add(TestPermission testPermission) {
       addedPermissionsBuilder().add(testPermission);
       return this;
     }
 
     /** Adds a permission to be included in this update. */
+    @CanIgnoreReturnValue
     public Builder add(TestPermission.Builder testPermissionBuilder) {
       return add(testPermissionBuilder.build());
     }
 
     /** Adds a label permission to be included in this update. */
+    @CanIgnoreReturnValue
     public Builder add(TestLabelPermission testLabelPermission) {
       addedLabelPermissionsBuilder().add(testLabelPermission);
       return this;
     }
 
     /** Adds a label permission to be included in this update. */
+    @CanIgnoreReturnValue
     public Builder add(TestLabelPermission.Builder testLabelPermissionBuilder) {
       return add(testLabelPermissionBuilder.build());
     }
 
     /** Adds a capability to be included in this update. */
+    @CanIgnoreReturnValue
     public Builder add(TestCapability testCapability) {
       addedCapabilitiesBuilder().add(testCapability);
       return this;
     }
 
     /** Adds a capability to be included in this update. */
+    @CanIgnoreReturnValue
     public Builder add(TestCapability.Builder testCapabilityBuilder) {
       return add(testCapabilityBuilder.build());
     }
 
     /** Removes a permission, label permission, or capability as part of this update. */
+    @CanIgnoreReturnValue
     public Builder remove(TestPermissionKey testPermissionKey) {
       removedPermissionsBuilder().add(testPermissionKey);
       return this;
     }
 
     /** Removes a permission, label permission, or capability as part of this update. */
+    @CanIgnoreReturnValue
     public Builder remove(TestPermissionKey.Builder testPermissionKeyBuilder) {
       return remove(testPermissionKeyBuilder.build());
     }
 
     /** Sets the exclusive bit bit for the given permission key. */
+    @CanIgnoreReturnValue
     public Builder setExclusiveGroup(
         TestPermissionKey.Builder testPermissionKeyBuilder, boolean exclusive) {
       return setExclusiveGroup(testPermissionKeyBuilder.build(), exclusive);
     }
 
     /** Sets the exclusive bit bit for the given permission key. */
+    @CanIgnoreReturnValue
     public Builder setExclusiveGroup(TestPermissionKey testPermissionKey, boolean exclusive) {
       checkArgument(
           !testPermissionKey.group().isPresent(),
diff --git a/java/com/google/gerrit/server/StarredChangesUtil.java b/java/com/google/gerrit/acceptance/testsuite/project/package-info.java
similarity index 64%
copy from java/com/google/gerrit/server/StarredChangesUtil.java
copy to java/com/google/gerrit/acceptance/testsuite/project/package-info.java
index 0709b86..dfe56d6 100644
--- a/java/com/google/gerrit/server/StarredChangesUtil.java
+++ b/java/com/google/gerrit/acceptance/testsuite/project/package-info.java
@@ -1,4 +1,4 @@
-// Copyright (C) 2015 The Android Open Source Project
+// Copyright (C) 2023 The Android Open Source Project
 //
 // Licensed under the Apache License, Version 2.0 (the "License");
 // you may not use this file except in compliance with the License.
@@ -12,8 +12,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package com.google.gerrit.server;
+@CheckReturnValue
+package com.google.gerrit.acceptance.testsuite.project;
 
-// TODO - delete this class. It cannot be deleted as part of the original refactoring since it's
-// used by a plugin.
-public interface StarredChangesUtil extends StarredChangesReader, StarredChangesWriter {}
+import com.google.errorprone.annotations.CheckReturnValue;
diff --git a/java/com/google/gerrit/acceptance/testsuite/request/RequestScopeOperations.java b/java/com/google/gerrit/acceptance/testsuite/request/RequestScopeOperations.java
index a9914b3..97826dc 100644
--- a/java/com/google/gerrit/acceptance/testsuite/request/RequestScopeOperations.java
+++ b/java/com/google/gerrit/acceptance/testsuite/request/RequestScopeOperations.java
@@ -14,6 +14,7 @@
 
 package com.google.gerrit.acceptance.testsuite.request;
 
+import com.google.errorprone.annotations.CanIgnoreReturnValue;
 import com.google.gerrit.acceptance.AcceptanceTestRequestScope;
 import com.google.gerrit.acceptance.testsuite.account.TestAccount;
 import com.google.gerrit.entities.Account;
@@ -35,6 +36,7 @@
    * @param accountId account ID. Must exist; throws an unchecked exception otherwise.
    * @return the previous request scope.
    */
+  @CanIgnoreReturnValue
   AcceptanceTestRequestScope.Context setApiUser(Account.Id accountId);
 
   /**
@@ -48,6 +50,7 @@
    * @param testAccount test account from {@code AccountOperations}.
    * @return the previous request scope.
    */
+  @CanIgnoreReturnValue
   AcceptanceTestRequestScope.Context setApiUser(TestAccount testAccount);
 
   /**
@@ -60,6 +63,7 @@
    *
    * @return the previous request scope.
    */
+  @CanIgnoreReturnValue
   AcceptanceTestRequestScope.Context resetCurrentApiUser();
 
   /**
@@ -67,6 +71,7 @@
    *
    * @return the previous request scope.
    */
+  @CanIgnoreReturnValue
   AcceptanceTestRequestScope.Context setApiUserAnonymous();
 
   /**
@@ -74,5 +79,6 @@
    *
    * @return the previous request scope.
    */
+  @CanIgnoreReturnValue
   AcceptanceTestRequestScope.Context setApiUserInternal();
 }
diff --git a/java/com/google/gerrit/acceptance/testsuite/request/RequestScopeOperationsImpl.java b/java/com/google/gerrit/acceptance/testsuite/request/RequestScopeOperationsImpl.java
index 895c7a0..a10ece6 100644
--- a/java/com/google/gerrit/acceptance/testsuite/request/RequestScopeOperationsImpl.java
+++ b/java/com/google/gerrit/acceptance/testsuite/request/RequestScopeOperationsImpl.java
@@ -17,6 +17,7 @@
 import static com.google.common.base.Preconditions.checkState;
 import static java.util.Objects.requireNonNull;
 
+import com.google.errorprone.annotations.CanIgnoreReturnValue;
 import com.google.gerrit.acceptance.AcceptanceTestRequestScope;
 import com.google.gerrit.acceptance.GerritServer.TestSshServerAddress;
 import com.google.gerrit.acceptance.testsuite.account.AccountOperations;
@@ -73,11 +74,13 @@
   }
 
   @Override
+  @CanIgnoreReturnValue
   public AcceptanceTestRequestScope.Context setApiUser(Account.Id accountId) {
     return setApiUser(accountOperations.account(accountId).get());
   }
 
   @Override
+  @CanIgnoreReturnValue
   public AcceptanceTestRequestScope.Context setApiUser(TestAccount testAccount) {
     return atrScope.set(
         atrScope.newContext(
@@ -86,6 +89,7 @@
   }
 
   @Override
+  @CanIgnoreReturnValue
   public AcceptanceTestRequestScope.Context resetCurrentApiUser() {
     CurrentUser user = atrScope.get().getUser();
     // More special cases for anonymous users etc. can be added as needed.
@@ -94,11 +98,13 @@
   }
 
   @Override
+  @CanIgnoreReturnValue
   public AcceptanceTestRequestScope.Context setApiUserAnonymous() {
     return atrScope.set(atrScope.newContext(null, anonymousUserProvider.get()));
   }
 
   @Override
+  @CanIgnoreReturnValue
   public AcceptanceTestRequestScope.Context setApiUserInternal() {
     return atrScope.set(atrScope.newContext(null, internalUserFactory.create()));
   }
diff --git a/java/com/google/gerrit/server/StarredChangesUtil.java b/java/com/google/gerrit/acceptance/testsuite/request/package-info.java
similarity index 64%
copy from java/com/google/gerrit/server/StarredChangesUtil.java
copy to java/com/google/gerrit/acceptance/testsuite/request/package-info.java
index 0709b86..943bd21 100644
--- a/java/com/google/gerrit/server/StarredChangesUtil.java
+++ b/java/com/google/gerrit/acceptance/testsuite/request/package-info.java
@@ -1,4 +1,4 @@
-// Copyright (C) 2015 The Android Open Source Project
+// Copyright (C) 2023 The Android Open Source Project
 //
 // Licensed under the Apache License, Version 2.0 (the "License");
 // you may not use this file except in compliance with the License.
@@ -12,8 +12,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package com.google.gerrit.server;
+@CheckReturnValue
+package com.google.gerrit.acceptance.testsuite.request;
 
-// TODO - delete this class. It cannot be deleted as part of the original refactoring since it's
-// used by a plugin.
-public interface StarredChangesUtil extends StarredChangesReader, StarredChangesWriter {}
+import com.google.errorprone.annotations.CheckReturnValue;
diff --git a/java/com/google/gerrit/asciidoctor/AsciiDoctor.java b/java/com/google/gerrit/asciidoctor/AsciiDoctor.java
index 9d0a28e..25ed813 100644
--- a/java/com/google/gerrit/asciidoctor/AsciiDoctor.java
+++ b/java/com/google/gerrit/asciidoctor/AsciiDoctor.java
@@ -22,7 +22,7 @@
 import java.io.IOException;
 import java.io.InputStream;
 import java.nio.file.Files;
-import java.nio.file.Paths;
+import java.nio.file.Path;
 import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.List;
@@ -164,7 +164,7 @@
     if (bazel) {
       renderFiles(inputFiles, null);
     } else {
-      try (ZipOutputStream zip = new ZipOutputStream(Files.newOutputStream(Paths.get(zipFile)))) {
+      try (ZipOutputStream zip = new ZipOutputStream(Files.newOutputStream(Path.of(zipFile)))) {
         renderFiles(inputFiles, zip);
 
         File[] cssFiles = tmpdir.listFiles((dir, name) -> name.endsWith(".css"));
diff --git a/java/com/google/gerrit/asciidoctor/BUILD b/java/com/google/gerrit/asciidoctor/BUILD
index 94ec20d..a132095 100644
--- a/java/com/google/gerrit/asciidoctor/BUILD
+++ b/java/com/google/gerrit/asciidoctor/BUILD
@@ -35,6 +35,6 @@
         "//lib:args4j",
         "//lib:guava",
         "//lib/lucene:lucene-analyzers-common",
-        "//lib/lucene:lucene-core-and-backward-codecs",
+        "//lib/lucene:lucene-core",
     ],
 )
diff --git a/java/com/google/gerrit/asciidoctor/DocIndexer.java b/java/com/google/gerrit/asciidoctor/DocIndexer.java
index acd6aad..fd8161b 100644
--- a/java/com/google/gerrit/asciidoctor/DocIndexer.java
+++ b/java/com/google/gerrit/asciidoctor/DocIndexer.java
@@ -24,7 +24,7 @@
 import java.io.IOException;
 import java.io.UnsupportedEncodingException;
 import java.nio.file.Files;
-import java.nio.file.Paths;
+import java.nio.file.Path;
 import java.util.ArrayList;
 import java.util.List;
 import java.util.jar.JarEntry;
@@ -82,7 +82,7 @@
       return;
     }
 
-    try (JarOutputStream jar = new JarOutputStream(Files.newOutputStream(Paths.get(outFile)))) {
+    try (JarOutputStream jar = new JarOutputStream(Files.newOutputStream(Path.of(outFile)))) {
       byte[] compressedIndex = zip(index());
       JarEntry entry = new JarEntry(String.format("%s/%s", Constants.PACKAGE, Constants.INDEX_ZIP));
       entry.setSize(compressedIndex.length);
diff --git a/java/com/google/gerrit/server/StarredChangesUtil.java b/java/com/google/gerrit/asciidoctor/package-info.java
similarity index 64%
copy from java/com/google/gerrit/server/StarredChangesUtil.java
copy to java/com/google/gerrit/asciidoctor/package-info.java
index 0709b86..504c3f5 100644
--- a/java/com/google/gerrit/server/StarredChangesUtil.java
+++ b/java/com/google/gerrit/asciidoctor/package-info.java
@@ -1,4 +1,4 @@
-// Copyright (C) 2015 The Android Open Source Project
+// Copyright (C) 2023 The Android Open Source Project
 //
 // Licensed under the Apache License, Version 2.0 (the "License");
 // you may not use this file except in compliance with the License.
@@ -12,8 +12,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package com.google.gerrit.server;
+@CheckReturnValue
+package com.google.gerrit.asciidoctor;
 
-// TODO - delete this class. It cannot be deleted as part of the original refactoring since it's
-// used by a plugin.
-public interface StarredChangesUtil extends StarredChangesReader, StarredChangesWriter {}
+import com.google.errorprone.annotations.CheckReturnValue;
diff --git a/java/com/google/gerrit/auth/ldap/LdapRealm.java b/java/com/google/gerrit/auth/ldap/LdapRealm.java
index 7dc2b1b..e33e5cb 100644
--- a/java/com/google/gerrit/auth/ldap/LdapRealm.java
+++ b/java/com/google/gerrit/auth/ldap/LdapRealm.java
@@ -331,7 +331,10 @@
     final DirContext ctx = helper.open();
     try {
       Helper.LdapSchema schema = helper.getSchema(ctx);
-      helper.findAccount(schema, ctx, username, false);
+
+      @SuppressWarnings("unused")
+      var unused = helper.findAccount(schema, ctx, username, false);
+
       return true;
     } catch (NoSuchUserException e) {
       return false;
diff --git a/java/com/google/gerrit/server/StarredChangesUtil.java b/java/com/google/gerrit/auth/ldap/package-info.java
similarity index 64%
copy from java/com/google/gerrit/server/StarredChangesUtil.java
copy to java/com/google/gerrit/auth/ldap/package-info.java
index 0709b86..45e1bc2 100644
--- a/java/com/google/gerrit/server/StarredChangesUtil.java
+++ b/java/com/google/gerrit/auth/ldap/package-info.java
@@ -1,4 +1,4 @@
-// Copyright (C) 2015 The Android Open Source Project
+// Copyright (C) 2023 The Android Open Source Project
 //
 // Licensed under the Apache License, Version 2.0 (the "License");
 // you may not use this file except in compliance with the License.
@@ -12,8 +12,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package com.google.gerrit.server;
+@CheckReturnValue
+package com.google.gerrit.auth.ldap;
 
-// TODO - delete this class. It cannot be deleted as part of the original refactoring since it's
-// used by a plugin.
-public interface StarredChangesUtil extends StarredChangesReader, StarredChangesWriter {}
+import com.google.errorprone.annotations.CheckReturnValue;
diff --git a/java/com/google/gerrit/server/StarredChangesUtil.java b/java/com/google/gerrit/auth/oauth/package-info.java
similarity index 64%
copy from java/com/google/gerrit/server/StarredChangesUtil.java
copy to java/com/google/gerrit/auth/oauth/package-info.java
index 0709b86..53c6829 100644
--- a/java/com/google/gerrit/server/StarredChangesUtil.java
+++ b/java/com/google/gerrit/auth/oauth/package-info.java
@@ -1,4 +1,4 @@
-// Copyright (C) 2015 The Android Open Source Project
+// Copyright (C) 2023 The Android Open Source Project
 //
 // Licensed under the Apache License, Version 2.0 (the "License");
 // you may not use this file except in compliance with the License.
@@ -12,8 +12,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package com.google.gerrit.server;
+@CheckReturnValue
+package com.google.gerrit.auth.oauth;
 
-// TODO - delete this class. It cannot be deleted as part of the original refactoring since it's
-// used by a plugin.
-public interface StarredChangesUtil extends StarredChangesReader, StarredChangesWriter {}
+import com.google.errorprone.annotations.CheckReturnValue;
diff --git a/java/com/google/gerrit/server/StarredChangesUtil.java b/java/com/google/gerrit/auth/openid/package-info.java
similarity index 64%
copy from java/com/google/gerrit/server/StarredChangesUtil.java
copy to java/com/google/gerrit/auth/openid/package-info.java
index 0709b86..2b6f77d 100644
--- a/java/com/google/gerrit/server/StarredChangesUtil.java
+++ b/java/com/google/gerrit/auth/openid/package-info.java
@@ -1,4 +1,4 @@
-// Copyright (C) 2015 The Android Open Source Project
+// Copyright (C) 2023 The Android Open Source Project
 //
 // Licensed under the Apache License, Version 2.0 (the "License");
 // you may not use this file except in compliance with the License.
@@ -12,8 +12,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package com.google.gerrit.server;
+@CheckReturnValue
+package com.google.gerrit.auth.openid;
 
-// TODO - delete this class. It cannot be deleted as part of the original refactoring since it's
-// used by a plugin.
-public interface StarredChangesUtil extends StarredChangesReader, StarredChangesWriter {}
+import com.google.errorprone.annotations.CheckReturnValue;
diff --git a/java/com/google/gerrit/server/StarredChangesUtil.java b/java/com/google/gerrit/auth/package-info.java
similarity index 64%
copy from java/com/google/gerrit/server/StarredChangesUtil.java
copy to java/com/google/gerrit/auth/package-info.java
index 0709b86..6538de4 100644
--- a/java/com/google/gerrit/server/StarredChangesUtil.java
+++ b/java/com/google/gerrit/auth/package-info.java
@@ -1,4 +1,4 @@
-// Copyright (C) 2015 The Android Open Source Project
+// Copyright (C) 2023 The Android Open Source Project
 //
 // Licensed under the Apache License, Version 2.0 (the "License");
 // you may not use this file except in compliance with the License.
@@ -12,8 +12,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package com.google.gerrit.server;
+@CheckReturnValue
+package com.google.gerrit.auth;
 
-// TODO - delete this class. It cannot be deleted as part of the original refactoring since it's
-// used by a plugin.
-public interface StarredChangesUtil extends StarredChangesReader, StarredChangesWriter {}
+import com.google.errorprone.annotations.CheckReturnValue;
diff --git a/java/com/google/gerrit/common/BUILD b/java/com/google/gerrit/common/BUILD
index 8f930bb..8f85311 100644
--- a/java/com/google/gerrit/common/BUILD
+++ b/java/com/google/gerrit/common/BUILD
@@ -30,6 +30,7 @@
         "//lib:servlet-api",
         "//lib/auto:auto-value",
         "//lib/auto:auto-value-annotations",
+        "//lib/errorprone:annotations",
         "//lib/flogger:api",
     ],
 )
diff --git a/java/com/google/gerrit/common/FileUtil.java b/java/com/google/gerrit/common/FileUtil.java
index 5b0925e..35cf848 100644
--- a/java/com/google/gerrit/common/FileUtil.java
+++ b/java/com/google/gerrit/common/FileUtil.java
@@ -14,6 +14,7 @@
 
 package com.google.gerrit.common;
 
+import com.google.errorprone.annotations.CanIgnoreReturnValue;
 import java.io.File;
 import java.io.FileNotFoundException;
 import java.io.IOException;
@@ -84,6 +85,7 @@
     }
   }
 
+  @CanIgnoreReturnValue
   public static Path mkdirsOrDie(Path p, String errMsg) {
     try {
       if (!Files.isDirectory(p)) {
diff --git a/java/com/google/gerrit/server/StarredChangesUtil.java b/java/com/google/gerrit/common/auth/openid/package-info.java
similarity index 64%
copy from java/com/google/gerrit/server/StarredChangesUtil.java
copy to java/com/google/gerrit/common/auth/openid/package-info.java
index 0709b86..931ed56 100644
--- a/java/com/google/gerrit/server/StarredChangesUtil.java
+++ b/java/com/google/gerrit/common/auth/openid/package-info.java
@@ -1,4 +1,4 @@
-// Copyright (C) 2015 The Android Open Source Project
+// Copyright (C) 2023 The Android Open Source Project
 //
 // Licensed under the Apache License, Version 2.0 (the "License");
 // you may not use this file except in compliance with the License.
@@ -12,8 +12,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package com.google.gerrit.server;
+@CheckReturnValue
+package com.google.gerrit.common.auth.openid;
 
-// TODO - delete this class. It cannot be deleted as part of the original refactoring since it's
-// used by a plugin.
-public interface StarredChangesUtil extends StarredChangesReader, StarredChangesWriter {}
+import com.google.errorprone.annotations.CheckReturnValue;
diff --git a/java/com/google/gerrit/server/StarredChangesUtil.java b/java/com/google/gerrit/common/data/package-info.java
similarity index 64%
copy from java/com/google/gerrit/server/StarredChangesUtil.java
copy to java/com/google/gerrit/common/data/package-info.java
index 0709b86..9f3539c 100644
--- a/java/com/google/gerrit/server/StarredChangesUtil.java
+++ b/java/com/google/gerrit/common/data/package-info.java
@@ -1,4 +1,4 @@
-// Copyright (C) 2015 The Android Open Source Project
+// Copyright (C) 2023 The Android Open Source Project
 //
 // Licensed under the Apache License, Version 2.0 (the "License");
 // you may not use this file except in compliance with the License.
@@ -12,8 +12,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package com.google.gerrit.server;
+@CheckReturnValue
+package com.google.gerrit.common.data;
 
-// TODO - delete this class. It cannot be deleted as part of the original refactoring since it's
-// used by a plugin.
-public interface StarredChangesUtil extends StarredChangesReader, StarredChangesWriter {}
+import com.google.errorprone.annotations.CheckReturnValue;
diff --git a/java/com/google/gerrit/common/data/testing/BUILD b/java/com/google/gerrit/common/data/testing/BUILD
index d39d05c..a580c77 100644
--- a/java/com/google/gerrit/common/data/testing/BUILD
+++ b/java/com/google/gerrit/common/data/testing/BUILD
@@ -7,6 +7,7 @@
     visibility = ["//visibility:public"],
     deps = [
         "//java/com/google/gerrit/entities",
+        "//lib/errorprone:annotations",
         "//lib/truth",
     ],
 )
diff --git a/java/com/google/gerrit/server/StarredChangesUtil.java b/java/com/google/gerrit/common/data/testing/package-info.java
similarity index 64%
copy from java/com/google/gerrit/server/StarredChangesUtil.java
copy to java/com/google/gerrit/common/data/testing/package-info.java
index 0709b86..24c3708 100644
--- a/java/com/google/gerrit/server/StarredChangesUtil.java
+++ b/java/com/google/gerrit/common/data/testing/package-info.java
@@ -1,4 +1,4 @@
-// Copyright (C) 2015 The Android Open Source Project
+// Copyright (C) 2023 The Android Open Source Project
 //
 // Licensed under the Apache License, Version 2.0 (the "License");
 // you may not use this file except in compliance with the License.
@@ -12,8 +12,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package com.google.gerrit.server;
+@CheckReturnValue
+package com.google.gerrit.common.data.testing;
 
-// TODO - delete this class. It cannot be deleted as part of the original refactoring since it's
-// used by a plugin.
-public interface StarredChangesUtil extends StarredChangesReader, StarredChangesWriter {}
+import com.google.errorprone.annotations.CheckReturnValue;
diff --git a/java/com/google/gerrit/server/StarredChangesUtil.java b/java/com/google/gerrit/common/package-info.java
similarity index 64%
copy from java/com/google/gerrit/server/StarredChangesUtil.java
copy to java/com/google/gerrit/common/package-info.java
index 0709b86..5f97d56 100644
--- a/java/com/google/gerrit/server/StarredChangesUtil.java
+++ b/java/com/google/gerrit/common/package-info.java
@@ -1,4 +1,4 @@
-// Copyright (C) 2015 The Android Open Source Project
+// Copyright (C) 2023 The Android Open Source Project
 //
 // Licensed under the Apache License, Version 2.0 (the "License");
 // you may not use this file except in compliance with the License.
@@ -12,8 +12,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package com.google.gerrit.server;
+@CheckReturnValue
+package com.google.gerrit.common;
 
-// TODO - delete this class. It cannot be deleted as part of the original refactoring since it's
-// used by a plugin.
-public interface StarredChangesUtil extends StarredChangesReader, StarredChangesWriter {}
+import com.google.errorprone.annotations.CheckReturnValue;
diff --git a/java/com/google/gerrit/entities/AccessSection.java b/java/com/google/gerrit/entities/AccessSection.java
index 8ae0a5d..7dca72e 100644
--- a/java/com/google/gerrit/entities/AccessSection.java
+++ b/java/com/google/gerrit/entities/AccessSection.java
@@ -20,6 +20,7 @@
 import com.google.auto.value.AutoValue;
 import com.google.auto.value.extension.memoized.Memoized;
 import com.google.common.collect.ImmutableList;
+import com.google.errorprone.annotations.CanIgnoreReturnValue;
 import com.google.gerrit.common.Nullable;
 import java.util.ArrayList;
 import java.util.List;
@@ -121,27 +122,32 @@
 
     public abstract String getName();
 
+    @CanIgnoreReturnValue
     public Builder modifyPermissions(Consumer<List<Permission.Builder>> modification) {
       modification.accept(permissionBuilders);
       return this;
     }
 
+    @CanIgnoreReturnValue
     public Builder addPermission(Permission.Builder permission) {
       requireNonNull(permission, "permission must be non-null");
       return modifyPermissions(p -> p.add(permission));
     }
 
+    @CanIgnoreReturnValue
     public Builder remove(Permission.Builder permission) {
       requireNonNull(permission, "permission must be non-null");
       return removePermission(permission.getName());
     }
 
+    @CanIgnoreReturnValue
     public Builder removePermission(String name) {
       requireNonNull(name, "name must be non-null");
       return modifyPermissions(
           p -> p.removeIf(permissionBuilder -> name.equalsIgnoreCase(permissionBuilder.getName())));
     }
 
+    @CanIgnoreReturnValue
     public Permission.Builder upsertPermission(String permissionName) {
       requireNonNull(permissionName, "permissionName must be non-null");
 
diff --git a/java/com/google/gerrit/entities/Account.java b/java/com/google/gerrit/entities/Account.java
index 52ad0a9..767e68e 100644
--- a/java/com/google/gerrit/entities/Account.java
+++ b/java/com/google/gerrit/entities/Account.java
@@ -19,7 +19,9 @@
 import static com.google.gerrit.entities.RefNames.REFS_USERS;
 
 import com.google.auto.value.AutoValue;
+import com.google.common.base.MoreObjects;
 import com.google.common.primitives.Ints;
+import com.google.errorprone.annotations.CanIgnoreReturnValue;
 import com.google.gerrit.common.Nullable;
 import com.google.gerrit.extensions.client.DiffPreferencesInfo;
 import java.time.Instant;
@@ -261,6 +263,7 @@
 
     public abstract Builder setInactive(boolean inactive);
 
+    @CanIgnoreReturnValue
     public Builder setActive(boolean active) {
       return setInactive(!active);
     }
@@ -282,4 +285,17 @@
   public final String toString() {
     return getName();
   }
+
+  public final String debugString() {
+    return MoreObjects.toStringHelper(this)
+        .add("id", id())
+        .add("registeredOn", registeredOn())
+        .add("fullName", fullName())
+        .add("displayName", displayName())
+        .add("preferredEmail", preferredEmail())
+        .add("inactive", inactive())
+        .add("status", status())
+        .add("metaId", metaId())
+        .toString();
+  }
 }
diff --git a/java/com/google/gerrit/entities/AccountGroupByIdAudit.java b/java/com/google/gerrit/entities/AccountGroupByIdAudit.java
index 0ef51e5..5db5af5 100644
--- a/java/com/google/gerrit/entities/AccountGroupByIdAudit.java
+++ b/java/com/google/gerrit/entities/AccountGroupByIdAudit.java
@@ -15,6 +15,7 @@
 package com.google.gerrit.entities;
 
 import com.google.auto.value.AutoValue;
+import com.google.errorprone.annotations.CanIgnoreReturnValue;
 import java.time.Instant;
 import java.util.Optional;
 
@@ -39,6 +40,7 @@
 
     abstract Builder removedOn(Instant removedOn);
 
+    @CanIgnoreReturnValue
     public Builder removed(Account.Id removedBy, Instant removedOn) {
       return removedBy(removedBy).removedOn(removedOn);
     }
diff --git a/java/com/google/gerrit/entities/AccountGroupMemberAudit.java b/java/com/google/gerrit/entities/AccountGroupMemberAudit.java
index 913956e..0cb1c62 100644
--- a/java/com/google/gerrit/entities/AccountGroupMemberAudit.java
+++ b/java/com/google/gerrit/entities/AccountGroupMemberAudit.java
@@ -15,6 +15,7 @@
 package com.google.gerrit.entities;
 
 import com.google.auto.value.AutoValue;
+import com.google.errorprone.annotations.CanIgnoreReturnValue;
 import java.time.Instant;
 import java.util.Optional;
 
@@ -43,10 +44,12 @@
 
     abstract Builder removedOn(Instant removedOn);
 
+    @CanIgnoreReturnValue
     public Builder removed(Account.Id removedBy, Instant removedOn) {
       return removedBy(removedBy).removedOn(removedOn);
     }
 
+    @CanIgnoreReturnValue
     public Builder removedLegacy() {
       return removed(addedBy(), addedOn());
     }
diff --git a/java/com/google/gerrit/entities/CachedProjectConfig.java b/java/com/google/gerrit/entities/CachedProjectConfig.java
index be4a1cf..6dc9e32 100644
--- a/java/com/google/gerrit/entities/CachedProjectConfig.java
+++ b/java/com/google/gerrit/entities/CachedProjectConfig.java
@@ -20,6 +20,7 @@
 import com.google.common.collect.ImmutableSet;
 import com.google.common.collect.ImmutableSortedMap;
 import com.google.common.flogger.FluentLogger;
+import com.google.errorprone.annotations.CanIgnoreReturnValue;
 import java.util.Collection;
 import java.util.List;
 import java.util.Map;
@@ -148,31 +149,37 @@
 
     public abstract Builder setBranchOrderSection(Optional<BranchOrderSection> value);
 
+    @CanIgnoreReturnValue
     public Builder addGroup(GroupReference groupReference) {
       groupsBuilder().put(groupReference.getUUID(), groupReference);
       return this;
     }
 
+    @CanIgnoreReturnValue
     public Builder addAccessSection(AccessSection accessSection) {
       accessSectionsBuilder().put(accessSection.getName(), accessSection);
       return this;
     }
 
+    @CanIgnoreReturnValue
     public Builder addContributorAgreement(ContributorAgreement contributorAgreement) {
       contributorAgreementsBuilder().put(contributorAgreement.getName(), contributorAgreement);
       return this;
     }
 
+    @CanIgnoreReturnValue
     public Builder addNotifySection(NotifyConfig notifyConfig) {
       notifySectionsBuilder().put(notifyConfig.getName(), notifyConfig);
       return this;
     }
 
+    @CanIgnoreReturnValue
     public Builder addLabelSection(LabelType labelType) {
       labelSectionsBuilder().put(labelType.getName(), labelType);
       return this;
     }
 
+    @CanIgnoreReturnValue
     public Builder addSubmitRequirementSection(SubmitRequirement submitRequirement) {
       submitRequirementSectionsBuilder().put(submitRequirement.name(), submitRequirement);
       return this;
@@ -180,11 +187,13 @@
 
     public abstract Builder setMimeTypes(ConfiguredMimeTypes value);
 
+    @CanIgnoreReturnValue
     public Builder addSubscribeSection(SubscribeSection subscribeSection) {
       subscribeSectionsBuilder().put(subscribeSection.project(), subscribeSection);
       return this;
     }
 
+    @CanIgnoreReturnValue
     public Builder addCommentLinkSection(StoredCommentLinkInfo storedCommentLinkInfo) {
       commentLinkSectionsBuilder().put(storedCommentLinkInfo.getName(), storedCommentLinkInfo);
       return this;
@@ -213,6 +222,7 @@
 
     abstract ImmutableMap.Builder<String, String> pluginConfigsBuilder();
 
+    @CanIgnoreReturnValue
     public Builder addPluginConfig(String pluginName, String pluginConfig) {
       pluginConfigsBuilder().put(pluginName, pluginConfig);
       return this;
@@ -222,6 +232,7 @@
 
     abstract ImmutableMap.Builder<String, ImmutableConfig> parsedProjectLevelConfigsBuilder();
 
+    @CanIgnoreReturnValue
     public Builder addProjectLevelConfig(String configFileName, String config) {
       projectLevelConfigsBuilder().put(configFileName, config);
       try {
diff --git a/java/com/google/gerrit/entities/Comment.java b/java/com/google/gerrit/entities/Comment.java
index e1e143c..35a60eb 100644
--- a/java/com/google/gerrit/entities/Comment.java
+++ b/java/com/google/gerrit/entities/Comment.java
@@ -20,6 +20,7 @@
 import java.sql.Timestamp;
 import java.time.Instant;
 import java.util.Comparator;
+import java.util.List;
 import java.util.Objects;
 import org.eclipse.jgit.lib.AnyObjectId;
 import org.eclipse.jgit.lib.ObjectId;
@@ -216,7 +217,7 @@
   public int lineNbr;
 
   public Identity author;
-  protected Identity realAuthor;
+  public Identity realAuthor;
 
   // TODO(issue-15525): Migrate this field from Timestamp to Instant
   public Timestamp writtenOn;
@@ -227,6 +228,8 @@
   public Range range;
   public String tag;
 
+  @Nullable public List<FixSuggestion> fixSuggestions;
+
   /**
    * Hex commit SHA1 of the commit of the patchset to which this comment applies. Other classes call
    * this "commitId", but this class uses the old ReviewDb term "revId", and this field name is
@@ -300,7 +303,14 @@
         + (key != null ? nullableLength(key.filename, key.uuid) : 0);
   }
 
-  public abstract int getApproximateSize();
+  public int getApproximateSize() {
+    int approximateSize = getCommentFieldApproximateSize();
+    approximateSize +=
+        fixSuggestions != null
+            ? fixSuggestions.stream().mapToInt(FixSuggestion::getApproximateSize).sum()
+            : 0;
+    return approximateSize;
+  }
 
   static int nullableLength(String... strings) {
     int length = 0;
@@ -327,7 +337,8 @@
         && Objects.equals(range, c.range)
         && Objects.equals(tag, c.tag)
         && Objects.equals(revId, c.revId)
-        && Objects.equals(serverId, c.serverId);
+        && Objects.equals(serverId, c.serverId)
+        && Objects.equals(fixSuggestions, c.fixSuggestions);
   }
 
   @Override
@@ -344,7 +355,8 @@
         range,
         tag,
         revId,
-        serverId);
+        serverId,
+        fixSuggestions);
   }
 
   @Override
@@ -364,6 +376,7 @@
         .add("parentUuid", Objects.toString(parentUuid, ""))
         .add("range", Objects.toString(range, ""))
         .add("revId", Objects.toString(revId, ""))
-        .add("tag", Objects.toString(tag, ""));
+        .add("tag", Objects.toString(tag, ""))
+        .add("fixSuggestions", Objects.toString(fixSuggestions, ""));
   }
 }
diff --git a/java/com/google/gerrit/entities/FixReplacement.java b/java/com/google/gerrit/entities/FixReplacement.java
index fbbf746..aa15ffc 100644
--- a/java/com/google/gerrit/entities/FixReplacement.java
+++ b/java/com/google/gerrit/entities/FixReplacement.java
@@ -14,6 +14,8 @@
 
 package com.google.gerrit.entities;
 
+import java.util.Objects;
+
 public final class FixReplacement {
   public final String path;
   public final Comment.Range range;
@@ -43,4 +45,20 @@
   int getApproximateSize() {
     return path.length() + replacement.length();
   }
+
+  @Override
+  public boolean equals(Object o) {
+    if (!(o instanceof FixReplacement)) {
+      return false;
+    }
+    FixReplacement f = (FixReplacement) o;
+    return Objects.equals(path, f.path)
+        && Objects.equals(range, f.range)
+        && Objects.equals(replacement, f.replacement);
+  }
+
+  @Override
+  public int hashCode() {
+    return Objects.hash(path, range, replacement);
+  }
 }
diff --git a/java/com/google/gerrit/entities/FixSuggestion.java b/java/com/google/gerrit/entities/FixSuggestion.java
index 892e324..737c23e 100644
--- a/java/com/google/gerrit/entities/FixSuggestion.java
+++ b/java/com/google/gerrit/entities/FixSuggestion.java
@@ -15,6 +15,7 @@
 package com.google.gerrit.entities;
 
 import java.util.List;
+import java.util.Objects;
 
 public final class FixSuggestion {
   public final String fixId;
@@ -47,4 +48,20 @@
         + description.length()
         + replacements.stream().mapToInt(FixReplacement::getApproximateSize).sum();
   }
+
+  @Override
+  public boolean equals(Object o) {
+    if (!(o instanceof FixSuggestion)) {
+      return false;
+    }
+    FixSuggestion fs = (FixSuggestion) o;
+    return Objects.equals(fixId, fs.fixId)
+        && Objects.equals(description, fs.description)
+        && Objects.equals(replacements, fs.replacements);
+  }
+
+  @Override
+  public int hashCode() {
+    return Objects.hash(fixId, description, replacements);
+  }
 }
diff --git a/java/com/google/gerrit/entities/HumanComment.java b/java/com/google/gerrit/entities/HumanComment.java
index d287fa0..1e48f11 100644
--- a/java/com/google/gerrit/entities/HumanComment.java
+++ b/java/com/google/gerrit/entities/HumanComment.java
@@ -47,11 +47,6 @@
   }
 
   @Override
-  public int getApproximateSize() {
-    return super.getCommentFieldApproximateSize();
-  }
-
-  @Override
   public String toString() {
     return toStringHelper().add("unresolved", unresolved).toString();
   }
diff --git a/java/com/google/gerrit/entities/LabelType.java b/java/com/google/gerrit/entities/LabelType.java
index 7a3266ecc..ff4b8f9 100644
--- a/java/com/google/gerrit/entities/LabelType.java
+++ b/java/com/google/gerrit/entities/LabelType.java
@@ -20,6 +20,7 @@
 import com.google.auto.value.AutoValue;
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableMap;
+import com.google.errorprone.annotations.CanIgnoreReturnValue;
 import com.google.gerrit.common.Nullable;
 import java.util.ArrayList;
 import java.util.List;
@@ -39,6 +40,7 @@
     return create(name, values);
   }
 
+  @CanIgnoreReturnValue
   public static String checkName(String name) throws IllegalArgumentException {
     checkNameInternal(name);
     if ("SUBM".equals(name)) {
@@ -47,6 +49,7 @@
     return name;
   }
 
+  @CanIgnoreReturnValue
   public static String checkNameInternal(String name) throws IllegalArgumentException {
     if (name == null || name.isEmpty()) {
       throw new IllegalArgumentException("Empty label name");
@@ -246,7 +249,7 @@
         setRefPatterns(null);
       }
 
-      List<LabelValue> valueList = sortValues(getValues());
+      ImmutableList<LabelValue> valueList = sortValues(getValues());
       setValues(valueList);
       if (!valueList.isEmpty()) {
         if (valueList.get(0).getValue() < 0) {
diff --git a/java/com/google/gerrit/entities/NotifyConfig.java b/java/com/google/gerrit/entities/NotifyConfig.java
index 5c0a3db..d3123c4 100644
--- a/java/com/google/gerrit/entities/NotifyConfig.java
+++ b/java/com/google/gerrit/entities/NotifyConfig.java
@@ -18,6 +18,7 @@
 import com.google.auto.value.extension.memoized.Memoized;
 import com.google.common.base.Strings;
 import com.google.common.collect.ImmutableSet;
+import com.google.errorprone.annotations.CanIgnoreReturnValue;
 import com.google.gerrit.common.Nullable;
 import java.util.EnumSet;
 import java.util.Set;
@@ -74,11 +75,13 @@
 
     public abstract Builder setHeader(Header hdr);
 
+    @CanIgnoreReturnValue
     public Builder addGroup(GroupReference group) {
       groupsBuilder().add(group);
       return this;
     }
 
+    @CanIgnoreReturnValue
     public Builder addAddress(Address address) {
       addressesBuilder().add(address);
       return this;
diff --git a/java/com/google/gerrit/entities/Permission.java b/java/com/google/gerrit/entities/Permission.java
index 0e959e7..1f2f151 100644
--- a/java/com/google/gerrit/entities/Permission.java
+++ b/java/com/google/gerrit/entities/Permission.java
@@ -18,6 +18,7 @@
 
 import com.google.auto.value.AutoValue;
 import com.google.common.collect.ImmutableList;
+import com.google.errorprone.annotations.CanIgnoreReturnValue;
 import com.google.gerrit.common.Nullable;
 import java.util.ArrayList;
 import java.util.Iterator;
@@ -274,15 +275,18 @@
 
     public abstract Builder setExclusiveGroup(boolean value);
 
+    @CanIgnoreReturnValue
     public Builder modifyRules(Consumer<List<PermissionRule.Builder>> modification) {
       modification.accept(rulesBuilders);
       return this;
     }
 
+    @CanIgnoreReturnValue
     public Builder add(PermissionRule.Builder rule) {
       return modifyRules(r -> r.add(rule));
     }
 
+    @CanIgnoreReturnValue
     public Builder remove(PermissionRule rule) {
       if (rule != null) {
         return removeRule(rule.getGroup());
@@ -290,10 +294,12 @@
       return this;
     }
 
+    @CanIgnoreReturnValue
     public Builder removeRule(GroupReference group) {
       return modifyRules(rules -> rules.removeIf(rule -> sameGroup(rule.build(), group)));
     }
 
+    @CanIgnoreReturnValue
     public Builder clearRules() {
       return modifyRules(r -> r.clear());
     }
diff --git a/java/com/google/gerrit/entities/PermissionRule.java b/java/com/google/gerrit/entities/PermissionRule.java
index 1665c1c..706091f 100644
--- a/java/com/google/gerrit/entities/PermissionRule.java
+++ b/java/com/google/gerrit/entities/PermissionRule.java
@@ -15,6 +15,7 @@
 package com.google.gerrit.entities;
 
 import com.google.auto.value.AutoValue;
+import com.google.errorprone.annotations.CanIgnoreReturnValue;
 
 @AutoValue
 public abstract class PermissionRule implements Comparable<PermissionRule> {
@@ -239,14 +240,17 @@
 
   @AutoValue.Builder
   public abstract static class Builder {
+    @CanIgnoreReturnValue
     public Builder setDeny() {
       return setAction(Action.DENY);
     }
 
+    @CanIgnoreReturnValue
     public Builder setBlock() {
       return setAction(Action.BLOCK);
     }
 
+    @CanIgnoreReturnValue
     public Builder setRange(int newMin, int newMax) {
       if (newMax < newMin) {
         setMin(newMax);
diff --git a/java/com/google/gerrit/entities/Project.java b/java/com/google/gerrit/entities/Project.java
index 72ca6a9..9c2866c 100644
--- a/java/com/google/gerrit/entities/Project.java
+++ b/java/com/google/gerrit/entities/Project.java
@@ -18,6 +18,7 @@
 
 import com.google.auto.value.AutoValue;
 import com.google.common.collect.ImmutableMap;
+import com.google.errorprone.annotations.CanIgnoreReturnValue;
 import com.google.errorprone.annotations.Immutable;
 import com.google.gerrit.common.Nullable;
 import com.google.gerrit.extensions.client.InheritableBoolean;
@@ -195,6 +196,7 @@
   public abstract static class Builder {
     public abstract Builder setDescription(String description);
 
+    @CanIgnoreReturnValue
     public Builder setBooleanConfig(BooleanProjectConfig config, InheritableBoolean val) {
       Map<BooleanProjectConfig, InheritableBoolean> map = new HashMap<>(getBooleanConfigs());
       map.replace(config, val);
@@ -214,6 +216,7 @@
 
     public abstract Builder setParent(NameKey n);
 
+    @CanIgnoreReturnValue
     public Builder setParent(String n) {
       return setParent(n != null ? nameKey(n) : null);
     }
diff --git a/java/com/google/gerrit/entities/RobotComment.java b/java/com/google/gerrit/entities/RobotComment.java
index 1d46d3b..a4288ca 100644
--- a/java/com/google/gerrit/entities/RobotComment.java
+++ b/java/com/google/gerrit/entities/RobotComment.java
@@ -15,16 +15,15 @@
 package com.google.gerrit.entities;
 
 import java.time.Instant;
-import java.util.List;
 import java.util.Map;
 import java.util.Objects;
 
+@Deprecated
 public final class RobotComment extends Comment {
   public String robotId;
   public String robotRunId;
   public String url;
   public Map<String, String> properties;
-  public List<FixSuggestion> fixSuggestions;
 
   public RobotComment(
       Key key,
@@ -64,7 +63,6 @@
         .add("robotRunId", robotRunId)
         .add("url", url)
         .add("properties", Objects.toString(properties, ""))
-        .add("fixSuggestions", Objects.toString(fixSuggestions, ""))
         .toString();
   }
 
@@ -78,12 +76,11 @@
         && Objects.equals(robotId, c.robotId)
         && Objects.equals(robotRunId, c.robotRunId)
         && Objects.equals(url, c.url)
-        && Objects.equals(properties, c.properties)
-        && Objects.equals(fixSuggestions, c.fixSuggestions);
+        && Objects.equals(properties, c.properties);
   }
 
   @Override
   public int hashCode() {
-    return Objects.hash(super.hashCode(), robotId, robotRunId, url, properties, fixSuggestions);
+    return Objects.hash(super.hashCode(), robotId, robotRunId, url, properties);
   }
 }
diff --git a/java/com/google/gerrit/entities/SubmitRequirementExpressionResult.java b/java/com/google/gerrit/entities/SubmitRequirementExpressionResult.java
index fbb2fd7..f9a5aeb 100644
--- a/java/com/google/gerrit/entities/SubmitRequirementExpressionResult.java
+++ b/java/com/google/gerrit/entities/SubmitRequirementExpressionResult.java
@@ -16,6 +16,7 @@
 
 import com.google.auto.value.AutoValue;
 import com.google.common.collect.ImmutableList;
+import com.google.errorprone.annotations.CanIgnoreReturnValue;
 import com.google.gson.Gson;
 import com.google.gson.TypeAdapter;
 import java.util.Optional;
@@ -203,6 +204,7 @@
 
       public abstract Builder status(boolean value);
 
+      @CanIgnoreReturnValue
       public Builder addChildPredicateResult(PredicateResult result) {
         childPredicateResultsBuilder().add(result);
         return this;
diff --git a/java/com/google/gerrit/entities/SubscribeSection.java b/java/com/google/gerrit/entities/SubscribeSection.java
index 574cae8..7751907 100644
--- a/java/com/google/gerrit/entities/SubscribeSection.java
+++ b/java/com/google/gerrit/entities/SubscribeSection.java
@@ -20,6 +20,7 @@
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableSet;
 import com.google.common.flogger.FluentLogger;
+import com.google.errorprone.annotations.CanIgnoreReturnValue;
 import java.util.Collection;
 import java.util.HashSet;
 import java.util.Set;
@@ -51,12 +52,14 @@
 
     abstract ImmutableList.Builder<RefSpec> multiMatchRefSpecsBuilder();
 
+    @CanIgnoreReturnValue
     public Builder addMatchingRefSpec(String matchingRefSpec) {
       matchingRefSpecsBuilder()
           .add(new RefSpec(matchingRefSpec, RefSpec.WildcardMode.REQUIRE_MATCH));
       return this;
     }
 
+    @CanIgnoreReturnValue
     public Builder addMultiMatchRefSpec(String multiMatchRefSpec) {
       multiMatchRefSpecsBuilder()
           .add(new RefSpec(multiMatchRefSpec, RefSpec.WildcardMode.ALLOW_MISMATCH));
diff --git a/java/com/google/gerrit/entities/converter/HumanCommentProtoConverter.java b/java/com/google/gerrit/entities/converter/HumanCommentProtoConverter.java
new file mode 100644
index 0000000..6e8c907
--- /dev/null
+++ b/java/com/google/gerrit/entities/converter/HumanCommentProtoConverter.java
@@ -0,0 +1,145 @@
+// Copyright (C) 2023 The Android Open Source Project
+//
+// Licensed 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 com.google.gerrit.entities.converter;
+
+import static com.google.gerrit.entities.Patch.PATCHSET_LEVEL;
+
+import com.google.errorprone.annotations.Immutable;
+import com.google.gerrit.entities.Account;
+import com.google.gerrit.entities.Comment;
+import com.google.gerrit.entities.Comment.Range;
+import com.google.gerrit.entities.HumanComment;
+import com.google.gerrit.proto.Entities;
+import com.google.gerrit.proto.Entities.HumanComment.InFilePosition;
+import com.google.gerrit.proto.Entities.HumanComment.InFilePosition.Side;
+import com.google.protobuf.Parser;
+import java.time.Instant;
+import java.util.Optional;
+import org.eclipse.jgit.lib.ObjectId;
+
+/**
+ * Proto converter between {@link HumanComment} and {@link
+ * com.google.gerrit.proto.Entities.HumanComment}.
+ */
+@Immutable
+public enum HumanCommentProtoConverter
+    implements ProtoConverter<Entities.HumanComment, HumanComment> {
+  INSTANCE;
+
+  private final ProtoConverter<Entities.Account_Id, Account.Id> accountIdConverter =
+      AccountIdProtoConverter.INSTANCE;
+  private final ProtoConverter<Entities.ObjectId, ObjectId> objectIdConverter =
+      ObjectIdProtoConverter.INSTANCE;
+
+  @Override
+  public Entities.HumanComment toProto(HumanComment val) {
+
+    Entities.HumanComment.Builder res =
+        Entities.HumanComment.newBuilder()
+            .setPatchsetId(val.key.patchSetId)
+            .setAccountId(accountIdConverter.toProto(val.author.getId()))
+            .setCommentUuid(val.key.uuid)
+            .setCommentText(val.message)
+            .setUnresolved(val.unresolved)
+            .setWrittenOnMillis(val.writtenOn.toInstant().toEpochMilli())
+            .setServerId(val.serverId);
+    if (!val.key.filename.equals(PATCHSET_LEVEL)) {
+      InFilePosition.Builder inFilePos =
+          InFilePosition.newBuilder()
+              .setFilePath(val.key.filename)
+              .setSide(val.side <= 0 ? Side.PARENT : Side.REVISION);
+      if (val.range != null) {
+        inFilePos.setPositionRange(
+            InFilePosition.Range.newBuilder()
+                .setStartLine(val.range.startLine)
+                .setStartChar(val.range.startChar)
+                .setEndLine(val.range.endLine)
+                .setEndChar(val.range.endChar));
+      }
+      if (val.lineNbr != 0) {
+        inFilePos.setLineNumber(val.lineNbr);
+      }
+      res.setInFilePosition(inFilePos);
+    }
+
+    if (val.parentUuid != null) {
+      res.setParentCommentUuid(val.parentUuid);
+    }
+    if (val.tag != null) {
+      res.setTag(val.tag);
+    }
+    if (val.realAuthor != null) {
+      res.setRealAuthor(accountIdConverter.toProto(val.realAuthor.getId()));
+    }
+    if (val.getCommitId() != null) {
+      res.setDestCommitId(objectIdConverter.toProto(val.getCommitId()));
+    }
+
+    return res.build();
+  }
+
+  @Override
+  public HumanComment fromProto(Entities.HumanComment proto) {
+    Optional<InFilePosition> optInFilePosition =
+        proto.hasInFilePosition() ? Optional.of(proto.getInFilePosition()) : Optional.empty();
+    Comment.Key key =
+        new Comment.Key(
+            proto.getCommentUuid(),
+            optInFilePosition.isPresent() ? optInFilePosition.get().getFilePath() : PATCHSET_LEVEL,
+            proto.getPatchsetId());
+    HumanComment res =
+        new HumanComment(
+            key,
+            accountIdConverter.fromProto(proto.getAccountId()),
+            Instant.ofEpochMilli(proto.getWrittenOnMillis()),
+            optInFilePosition.isPresent()
+                ? (short) optInFilePosition.get().getSide().getNumber()
+                : Side.REVISION_VALUE,
+            proto.getCommentText(),
+            proto.getServerId(),
+            proto.getUnresolved());
+
+    res.parentUuid = proto.hasParentCommentUuid() ? proto.getParentCommentUuid() : null;
+    res.tag = proto.hasTag() ? proto.getTag() : null;
+    if (proto.hasRealAuthor()) {
+      res.realAuthor = new Comment.Identity(accountIdConverter.fromProto(proto.getRealAuthor()));
+    }
+    if (proto.hasDestCommitId()) {
+      res.setCommitId(objectIdConverter.fromProto(proto.getDestCommitId()));
+    }
+
+    optInFilePosition.ifPresent(
+        inFilePosition -> {
+          if (inFilePosition.hasPositionRange()) {
+            var range = inFilePosition.getPositionRange();
+            res.range =
+                new Range(
+                    range.getStartLine(),
+                    range.getStartChar(),
+                    range.getEndLine(),
+                    range.getEndChar());
+          }
+          if (inFilePosition.hasLineNumber()) {
+            res.lineNbr = inFilePosition.getLineNumber();
+          }
+        });
+    return res;
+  }
+
+  @Override
+  public Parser<Entities.HumanComment> getParser() {
+    return Entities.HumanComment.parser();
+  }
+}
diff --git a/java/com/google/gerrit/entities/converter/PatchSetProtoConverter.java b/java/com/google/gerrit/entities/converter/PatchSetProtoConverter.java
index a3b4abf..196deca 100644
--- a/java/com/google/gerrit/entities/converter/PatchSetProtoConverter.java
+++ b/java/com/google/gerrit/entities/converter/PatchSetProtoConverter.java
@@ -21,7 +21,6 @@
 import com.google.gerrit.proto.Entities;
 import com.google.protobuf.Parser;
 import java.time.Instant;
-import java.util.List;
 import org.eclipse.jgit.lib.ObjectId;
 
 @Immutable
@@ -45,7 +44,7 @@
             .setRealUploaderAccountId(accountIdConverter.toProto(patchSet.realUploader()))
             .setCreatedOn(patchSet.createdOn().toEpochMilli());
     patchSet.branch().ifPresent(builder::setBranch);
-    List<String> groups = patchSet.groups();
+    ImmutableList<String> groups = patchSet.groups();
     if (!groups.isEmpty()) {
       builder.setGroups(PatchSet.joinGroups(groups));
     }
diff --git a/java/com/google/gerrit/server/StarredChangesUtil.java b/java/com/google/gerrit/entities/converter/package-info.java
similarity index 64%
copy from java/com/google/gerrit/server/StarredChangesUtil.java
copy to java/com/google/gerrit/entities/converter/package-info.java
index 0709b86..51ea401 100644
--- a/java/com/google/gerrit/server/StarredChangesUtil.java
+++ b/java/com/google/gerrit/entities/converter/package-info.java
@@ -1,4 +1,4 @@
-// Copyright (C) 2015 The Android Open Source Project
+// Copyright (C) 2023 The Android Open Source Project
 //
 // Licensed under the Apache License, Version 2.0 (the "License");
 // you may not use this file except in compliance with the License.
@@ -12,8 +12,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package com.google.gerrit.server;
+@CheckReturnValue
+package com.google.gerrit.entities.converter;
 
-// TODO - delete this class. It cannot be deleted as part of the original refactoring since it's
-// used by a plugin.
-public interface StarredChangesUtil extends StarredChangesReader, StarredChangesWriter {}
+import com.google.errorprone.annotations.CheckReturnValue;
diff --git a/java/com/google/gerrit/server/StarredChangesUtil.java b/java/com/google/gerrit/entities/package-info.java
similarity index 64%
copy from java/com/google/gerrit/server/StarredChangesUtil.java
copy to java/com/google/gerrit/entities/package-info.java
index 0709b86..f1cde1b 100644
--- a/java/com/google/gerrit/server/StarredChangesUtil.java
+++ b/java/com/google/gerrit/entities/package-info.java
@@ -1,4 +1,4 @@
-// Copyright (C) 2015 The Android Open Source Project
+// Copyright (C) 2023 The Android Open Source Project
 //
 // Licensed under the Apache License, Version 2.0 (the "License");
 // you may not use this file except in compliance with the License.
@@ -12,8 +12,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package com.google.gerrit.server;
+@CheckReturnValue
+package com.google.gerrit.entities;
 
-// TODO - delete this class. It cannot be deleted as part of the original refactoring since it's
-// used by a plugin.
-public interface StarredChangesUtil extends StarredChangesReader, StarredChangesWriter {}
+import com.google.errorprone.annotations.CheckReturnValue;
diff --git a/java/com/google/gerrit/exceptions/BUILD b/java/com/google/gerrit/exceptions/BUILD
index 873b659..b31d12e 100644
--- a/java/com/google/gerrit/exceptions/BUILD
+++ b/java/com/google/gerrit/exceptions/BUILD
@@ -7,5 +7,6 @@
     deps = [
         "//java/com/google/gerrit/entities",
         "//lib:jgit",
+        "//lib/errorprone:annotations",
     ],
 )
diff --git a/java/com/google/gerrit/server/StarredChangesUtil.java b/java/com/google/gerrit/exceptions/package-info.java
similarity index 64%
copy from java/com/google/gerrit/server/StarredChangesUtil.java
copy to java/com/google/gerrit/exceptions/package-info.java
index 0709b86..ce52b4a 100644
--- a/java/com/google/gerrit/server/StarredChangesUtil.java
+++ b/java/com/google/gerrit/exceptions/package-info.java
@@ -1,4 +1,4 @@
-// Copyright (C) 2015 The Android Open Source Project
+// Copyright (C) 2023 The Android Open Source Project
 //
 // Licensed under the Apache License, Version 2.0 (the "License");
 // you may not use this file except in compliance with the License.
@@ -12,8 +12,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package com.google.gerrit.server;
+@CheckReturnValue
+package com.google.gerrit.exceptions;
 
-// TODO - delete this class. It cannot be deleted as part of the original refactoring since it's
-// used by a plugin.
-public interface StarredChangesUtil extends StarredChangesReader, StarredChangesWriter {}
+import com.google.errorprone.annotations.CheckReturnValue;
diff --git a/java/com/google/gerrit/server/StarredChangesUtil.java b/java/com/google/gerrit/extensions/annotations/package-info.java
similarity index 64%
copy from java/com/google/gerrit/server/StarredChangesUtil.java
copy to java/com/google/gerrit/extensions/annotations/package-info.java
index 0709b86..f338b5b 100644
--- a/java/com/google/gerrit/server/StarredChangesUtil.java
+++ b/java/com/google/gerrit/extensions/annotations/package-info.java
@@ -1,4 +1,4 @@
-// Copyright (C) 2015 The Android Open Source Project
+// Copyright (C) 2023 The Android Open Source Project
 //
 // Licensed under the Apache License, Version 2.0 (the "License");
 // you may not use this file except in compliance with the License.
@@ -12,8 +12,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package com.google.gerrit.server;
+@CheckReturnValue
+package com.google.gerrit.extensions.annotations;
 
-// TODO - delete this class. It cannot be deleted as part of the original refactoring since it's
-// used by a plugin.
-public interface StarredChangesUtil extends StarredChangesReader, StarredChangesWriter {}
+import com.google.errorprone.annotations.CheckReturnValue;
diff --git a/java/com/google/gerrit/server/StarredChangesUtil.java b/java/com/google/gerrit/extensions/api/access/package-info.java
similarity index 64%
copy from java/com/google/gerrit/server/StarredChangesUtil.java
copy to java/com/google/gerrit/extensions/api/access/package-info.java
index 0709b86..65e2d75 100644
--- a/java/com/google/gerrit/server/StarredChangesUtil.java
+++ b/java/com/google/gerrit/extensions/api/access/package-info.java
@@ -1,4 +1,4 @@
-// Copyright (C) 2015 The Android Open Source Project
+// Copyright (C) 2023 The Android Open Source Project
 //
 // Licensed under the Apache License, Version 2.0 (the "License");
 // you may not use this file except in compliance with the License.
@@ -12,8 +12,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package com.google.gerrit.server;
+@CheckReturnValue
+package com.google.gerrit.extensions.api.access;
 
-// TODO - delete this class. It cannot be deleted as part of the original refactoring since it's
-// used by a plugin.
-public interface StarredChangesUtil extends StarredChangesReader, StarredChangesWriter {}
+import com.google.errorprone.annotations.CheckReturnValue;
diff --git a/java/com/google/gerrit/extensions/api/accounts/AccountApi.java b/java/com/google/gerrit/extensions/api/accounts/AccountApi.java
index 0d019aa..e40f82e 100644
--- a/java/com/google/gerrit/extensions/api/accounts/AccountApi.java
+++ b/java/com/google/gerrit/extensions/api/accounts/AccountApi.java
@@ -14,6 +14,7 @@
 
 package com.google.gerrit.extensions.api.accounts;
 
+import com.google.errorprone.annotations.CanIgnoreReturnValue;
 import com.google.gerrit.extensions.client.DiffPreferencesInfo;
 import com.google.gerrit.extensions.client.EditPreferencesInfo;
 import com.google.gerrit.extensions.client.GeneralPreferencesInfo;
@@ -44,18 +45,22 @@
 
   GeneralPreferencesInfo getPreferences() throws RestApiException;
 
+  @CanIgnoreReturnValue
   GeneralPreferencesInfo setPreferences(GeneralPreferencesInfo in) throws RestApiException;
 
   DiffPreferencesInfo getDiffPreferences() throws RestApiException;
 
+  @CanIgnoreReturnValue
   DiffPreferencesInfo setDiffPreferences(DiffPreferencesInfo in) throws RestApiException;
 
   EditPreferencesInfo getEditPreferences() throws RestApiException;
 
+  @CanIgnoreReturnValue
   EditPreferencesInfo setEditPreferences(EditPreferencesInfo in) throws RestApiException;
 
   List<ProjectWatchInfo> getWatchedProjects() throws RestApiException;
 
+  @CanIgnoreReturnValue
   List<ProjectWatchInfo> setWatchedProjects(List<ProjectWatchInfo> in) throws RestApiException;
 
   void deleteWatchedProjects(List<ProjectWatchInfo> in) throws RestApiException;
@@ -72,6 +77,7 @@
 
   void deleteEmail(String email) throws RestApiException;
 
+  @CanIgnoreReturnValue
   EmailApi createEmail(EmailInput emailInput) throws RestApiException;
 
   EmailApi email(String email) throws RestApiException;
@@ -82,12 +88,14 @@
 
   List<SshKeyInfo> listSshKeys() throws RestApiException;
 
+  @CanIgnoreReturnValue
   SshKeyInfo addSshKey(String key) throws RestApiException;
 
   void deleteSshKey(int seq) throws RestApiException;
 
   Map<String, GpgKeyInfo> listGpgKeys() throws RestApiException;
 
+  @CanIgnoreReturnValue
   Map<String, GpgKeyInfo> putGpgKeys(List<String> add, List<String> remove) throws RestApiException;
 
   GpgKeyApi gpgKey(String id) throws RestApiException;
@@ -102,6 +110,7 @@
 
   void deleteExternalIds(List<String> externalIds) throws RestApiException;
 
+  @CanIgnoreReturnValue
   List<DeletedDraftCommentInfo> deleteDraftComments(DeleteDraftCommentsInput input)
       throws RestApiException;
 
@@ -122,6 +131,7 @@
    * @param httpPassword the new password, {@code null} to remove the password.
    * @return the new password, {@code null} if the password was removed.
    */
+  @CanIgnoreReturnValue
   String setHttpPassword(String httpPassword) throws RestApiException;
 
   void delete() throws RestApiException;
diff --git a/java/com/google/gerrit/extensions/api/accounts/Accounts.java b/java/com/google/gerrit/extensions/api/accounts/Accounts.java
index 285b385..ad0d385 100644
--- a/java/com/google/gerrit/extensions/api/accounts/Accounts.java
+++ b/java/com/google/gerrit/extensions/api/accounts/Accounts.java
@@ -14,6 +14,7 @@
 
 package com.google.gerrit.extensions.api.accounts;
 
+import com.google.errorprone.annotations.CanIgnoreReturnValue;
 import com.google.gerrit.extensions.client.ListAccountsOption;
 import com.google.gerrit.extensions.common.AccountInfo;
 import com.google.gerrit.extensions.restapi.NotImplementedException;
@@ -53,9 +54,11 @@
   AccountApi self() throws RestApiException;
 
   /** Create a new account with the given username and default options. */
+  @CanIgnoreReturnValue
   AccountApi create(String username) throws RestApiException;
 
   /** Create a new account. */
+  @CanIgnoreReturnValue
   AccountApi create(AccountInput input) throws RestApiException;
 
   /**
diff --git a/java/com/google/gerrit/server/StarredChangesUtil.java b/java/com/google/gerrit/extensions/api/accounts/package-info.java
similarity index 64%
copy from java/com/google/gerrit/server/StarredChangesUtil.java
copy to java/com/google/gerrit/extensions/api/accounts/package-info.java
index 0709b86..5bd477a 100644
--- a/java/com/google/gerrit/server/StarredChangesUtil.java
+++ b/java/com/google/gerrit/extensions/api/accounts/package-info.java
@@ -1,4 +1,4 @@
-// Copyright (C) 2015 The Android Open Source Project
+// Copyright (C) 2023 The Android Open Source Project
 //
 // Licensed under the Apache License, Version 2.0 (the "License");
 // you may not use this file except in compliance with the License.
@@ -12,8 +12,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package com.google.gerrit.server;
+@CheckReturnValue
+package com.google.gerrit.extensions.api.accounts;
 
-// TODO - delete this class. It cannot be deleted as part of the original refactoring since it's
-// used by a plugin.
-public interface StarredChangesUtil extends StarredChangesReader, StarredChangesWriter {}
+import com.google.errorprone.annotations.CheckReturnValue;
diff --git a/java/com/google/gerrit/extensions/api/changes/ChangeApi.java b/java/com/google/gerrit/extensions/api/changes/ChangeApi.java
index d8fd727..a663e25 100644
--- a/java/com/google/gerrit/extensions/api/changes/ChangeApi.java
+++ b/java/com/google/gerrit/extensions/api/changes/ChangeApi.java
@@ -17,6 +17,7 @@
 import com.google.common.collect.ImmutableListMultimap;
 import com.google.common.collect.ImmutableMap;
 import com.google.common.collect.Sets;
+import com.google.errorprone.annotations.CanIgnoreReturnValue;
 import com.google.gerrit.common.Nullable;
 import com.google.gerrit.extensions.client.ListChangesOption;
 import com.google.gerrit.extensions.client.ReviewerState;
@@ -130,31 +131,29 @@
     setReadyForReview(null);
   }
 
-  /**
-   * Create a new change that reverts this change.
-   *
-   * @see Changes#id(int)
-   */
+  /** Create a new change that reverts this change. */
+  @CanIgnoreReturnValue
   default ChangeApi revert() throws RestApiException {
     return revert(new RevertInput());
   }
 
-  /**
-   * Create a new change that reverts this change.
-   *
-   * @see Changes#id(int)
-   */
+  /** Create a new change that reverts this change. */
+  @CanIgnoreReturnValue
   ChangeApi revert(RevertInput in) throws RestApiException;
 
+  @CanIgnoreReturnValue
   default RevertSubmissionInfo revertSubmission() throws RestApiException {
     return revertSubmission(new RevertInput());
   }
 
+  @CanIgnoreReturnValue
   RevertSubmissionInfo revertSubmission(RevertInput in) throws RestApiException;
 
   /** Create a merge patch set for the change. */
+  @CanIgnoreReturnValue
   ChangeInfo createMergePatchSet(MergePatchSetInput in) throws RestApiException;
 
+  @CanIgnoreReturnValue
   ChangeInfo applyPatch(ApplyPatchPatchSetInput in) throws RestApiException;
 
   default List<ChangeInfo> submittedTogether() throws RestApiException {
@@ -187,6 +186,7 @@
    * @return a {@code RebaseChainInfo} contains the {@code ChangeInfo} data for the rebased the
    *     chain
    */
+  @CanIgnoreReturnValue
   default Response<RebaseChainInfo> rebaseChain() throws RestApiException {
     return rebaseChain(new RebaseInput());
   }
@@ -197,6 +197,7 @@
    * @return a {@code RebaseChainInfo} contains the {@code ChangeInfo} data for the rebased the
    *     chain
    */
+  @CanIgnoreReturnValue
   Response<RebaseChainInfo> rebaseChain(RebaseInput in) throws RestApiException;
 
   /** Deletes a change. */
@@ -208,12 +209,14 @@
 
   IncludedInInfo includedIn() throws RestApiException;
 
+  @CanIgnoreReturnValue
   default ReviewerResult addReviewer(String reviewer) throws RestApiException {
     ReviewerInput in = new ReviewerInput();
     in.reviewer = reviewer;
     return addReviewer(in);
   }
 
+  @CanIgnoreReturnValue
   ReviewerResult addReviewer(ReviewerInput in) throws RestApiException;
 
   SuggestedReviewersRequest suggestReviewers() throws RestApiException;
@@ -356,6 +359,7 @@
   AttentionSetApi attention(String id) throws RestApiException;
 
   /** Adds a user to the attention set. */
+  @CanIgnoreReturnValue
   AccountInfo addToAttentionSet(AttentionSetInput input) throws RestApiException;
 
   /**
@@ -470,9 +474,11 @@
   void index() throws RestApiException;
 
   /** Check if this change is a pure revert of the change stored in revertOf. */
+  @CanIgnoreReturnValue
   PureRevertInfo pureRevert() throws RestApiException;
 
   /** Check if this change is a pure revert of claimedOriginal (SHA1 in 40 digit hex). */
+  @CanIgnoreReturnValue
   PureRevertInfo pureRevert(String claimedOriginal) throws RestApiException;
 
   /**
diff --git a/java/com/google/gerrit/extensions/api/changes/ChangeEditApi.java b/java/com/google/gerrit/extensions/api/changes/ChangeEditApi.java
index a26068a..2fd8a07 100644
--- a/java/com/google/gerrit/extensions/api/changes/ChangeEditApi.java
+++ b/java/com/google/gerrit/extensions/api/changes/ChangeEditApi.java
@@ -196,6 +196,18 @@
   void modifyCommitMessage(String newCommitMessage) throws RestApiException;
 
   /**
+   * Updates the author/committer of the change edit. If the change edit doesn't exist, it will be
+   * created based on the current patch set of the change.
+   *
+   * @param name the name of the author/committer
+   * @param email the email of the author/committer
+   * @param type the type of the identity being edited
+   * @throws RestApiException if the author/committer identity couldn't be updated
+   */
+  void modifyIdentity(String name, String email, ChangeEditIdentityType type)
+      throws RestApiException;
+
+  /**
    * A default implementation which allows source compatibility when adding new methods to the
    * interface.
    */
@@ -269,5 +281,11 @@
     public void modifyCommitMessage(String newCommitMessage) throws RestApiException {
       throw new NotImplementedException();
     }
+
+    @Override
+    public void modifyIdentity(String name, String email, ChangeEditIdentityType type)
+        throws RestApiException {
+      throw new NotImplementedException();
+    }
   }
 }
diff --git a/java/com/google/gerrit/server/StarredChangesUtil.java b/java/com/google/gerrit/extensions/api/changes/ChangeEditIdentityType.java
similarity index 64%
copy from java/com/google/gerrit/server/StarredChangesUtil.java
copy to java/com/google/gerrit/extensions/api/changes/ChangeEditIdentityType.java
index 0709b86..77d3f2e 100644
--- a/java/com/google/gerrit/server/StarredChangesUtil.java
+++ b/java/com/google/gerrit/extensions/api/changes/ChangeEditIdentityType.java
@@ -1,4 +1,4 @@
-// Copyright (C) 2015 The Android Open Source Project
+// Copyright (C) 2023 The Android Open Source Project
 //
 // Licensed under the Apache License, Version 2.0 (the "License");
 // you may not use this file except in compliance with the License.
@@ -12,8 +12,9 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package com.google.gerrit.server;
+package com.google.gerrit.extensions.api.changes;
 
-// TODO - delete this class. It cannot be deleted as part of the original refactoring since it's
-// used by a plugin.
-public interface StarredChangesUtil extends StarredChangesReader, StarredChangesWriter {}
+public enum ChangeEditIdentityType {
+  AUTHOR,
+  COMMITTER
+}
diff --git a/java/com/google/gerrit/extensions/api/changes/Changes.java b/java/com/google/gerrit/extensions/api/changes/Changes.java
index ea2a158..5e3d08c 100644
--- a/java/com/google/gerrit/extensions/api/changes/Changes.java
+++ b/java/com/google/gerrit/extensions/api/changes/Changes.java
@@ -16,6 +16,7 @@
 
 import com.google.common.collect.ArrayListMultimap;
 import com.google.common.collect.ListMultimap;
+import com.google.errorprone.annotations.CanIgnoreReturnValue;
 import com.google.gerrit.common.UsedAt;
 import com.google.gerrit.extensions.client.ListChangesOption;
 import com.google.gerrit.extensions.common.ChangeInfo;
@@ -73,6 +74,7 @@
    */
   ChangeApi id(String project, int id) throws RestApiException;
 
+  @CanIgnoreReturnValue
   ChangeApi create(ChangeInput in) throws RestApiException;
 
   ChangeInfo createAsInfo(ChangeInput in) throws RestApiException;
diff --git a/java/com/google/gerrit/extensions/api/changes/CommentApi.java b/java/com/google/gerrit/extensions/api/changes/CommentApi.java
index 889175e..9b5e1da 100644
--- a/java/com/google/gerrit/extensions/api/changes/CommentApi.java
+++ b/java/com/google/gerrit/extensions/api/changes/CommentApi.java
@@ -14,6 +14,7 @@
 
 package com.google.gerrit.extensions.api.changes;
 
+import com.google.errorprone.annotations.CanIgnoreReturnValue;
 import com.google.gerrit.extensions.common.CommentInfo;
 import com.google.gerrit.extensions.restapi.NotImplementedException;
 import com.google.gerrit.extensions.restapi.RestApiException;
@@ -30,6 +31,7 @@
    *
    * @return the comment with its message updated.
    */
+  @CanIgnoreReturnValue
   CommentInfo delete(DeleteCommentInput input) throws RestApiException;
 
   /**
diff --git a/java/com/google/gerrit/extensions/api/changes/DraftApi.java b/java/com/google/gerrit/extensions/api/changes/DraftApi.java
index fa663a5..50816b7 100644
--- a/java/com/google/gerrit/extensions/api/changes/DraftApi.java
+++ b/java/com/google/gerrit/extensions/api/changes/DraftApi.java
@@ -14,11 +14,13 @@
 
 package com.google.gerrit.extensions.api.changes;
 
+import com.google.errorprone.annotations.CanIgnoreReturnValue;
 import com.google.gerrit.extensions.common.CommentInfo;
 import com.google.gerrit.extensions.restapi.NotImplementedException;
 import com.google.gerrit.extensions.restapi.RestApiException;
 
 public interface DraftApi extends CommentApi {
+  @CanIgnoreReturnValue
   CommentInfo update(DraftInput in) throws RestApiException;
 
   void delete() throws RestApiException;
diff --git a/java/com/google/gerrit/extensions/api/changes/ReviewInput.java b/java/com/google/gerrit/extensions/api/changes/ReviewInput.java
index 98807cb..2584448 100644
--- a/java/com/google/gerrit/extensions/api/changes/ReviewInput.java
+++ b/java/com/google/gerrit/extensions/api/changes/ReviewInput.java
@@ -22,7 +22,6 @@
 import com.google.gerrit.extensions.client.Comment;
 import com.google.gerrit.extensions.client.ListChangesOption;
 import com.google.gerrit.extensions.client.ReviewerState;
-import com.google.gerrit.extensions.common.FixSuggestionInfo;
 import com.google.gerrit.extensions.restapi.DefaultInput;
 import java.util.ArrayList;
 import java.util.Collections;
@@ -39,7 +38,7 @@
 
   public Map<String, Short> labels;
   public Map<String, List<CommentInput>> comments;
-  public Map<String, List<RobotCommentInput>> robotComments;
+  @Deprecated public Map<String, List<RobotCommentInput>> robotComments;
 
   /**
    * How to process draft comments already in the database that were not also described in this
@@ -115,12 +114,12 @@
     public Boolean unresolved;
   }
 
+  @Deprecated
   public static class RobotCommentInput extends Comment {
     public String robotId;
     public String robotRunId;
     public String url;
     public Map<String, String> properties;
-    public List<FixSuggestionInfo> fixSuggestions;
   }
 
   @CanIgnoreReturnValue
diff --git a/java/com/google/gerrit/extensions/api/changes/RevisionApi.java b/java/com/google/gerrit/extensions/api/changes/RevisionApi.java
index 73fc170..69cf25d 100644
--- a/java/com/google/gerrit/extensions/api/changes/RevisionApi.java
+++ b/java/com/google/gerrit/extensions/api/changes/RevisionApi.java
@@ -15,6 +15,7 @@
 package com.google.gerrit.extensions.api.changes;
 
 import com.google.common.collect.ListMultimap;
+import com.google.errorprone.annotations.CanIgnoreReturnValue;
 import com.google.gerrit.common.Nullable;
 import com.google.gerrit.extensions.client.ArchiveFormat;
 import com.google.gerrit.extensions.client.SubmitType;
@@ -44,24 +45,30 @@
 
   void description(String description) throws RestApiException;
 
+  @CanIgnoreReturnValue
   ReviewResult review(ReviewInput in) throws RestApiException;
 
+  @CanIgnoreReturnValue
   default ChangeInfo submit() throws RestApiException {
     SubmitInput in = new SubmitInput();
     return submit(in);
   }
 
+  @CanIgnoreReturnValue
   ChangeInfo submit(SubmitInput in) throws RestApiException;
 
+  @CanIgnoreReturnValue
   ChangeApi cherryPick(CherryPickInput in) throws RestApiException;
 
   ChangeInfo cherryPickAsInfo(CherryPickInput in) throws RestApiException;
 
+  @CanIgnoreReturnValue
   default ChangeApi rebase() throws RestApiException {
     RebaseInput in = new RebaseInput();
     return rebase(in);
   }
 
+  @CanIgnoreReturnValue
   ChangeApi rebase(RebaseInput in) throws RestApiException;
 
   ChangeInfo rebaseAsInfo(RebaseInput in) throws RestApiException;
@@ -117,6 +124,7 @@
    * @param fixId the ID of the fix which should be applied
    * @throws RestApiException if the fix couldn't be applied
    */
+  @CanIgnoreReturnValue
   EditInfo applyFix(String fixId) throws RestApiException;
 
   /**
@@ -126,6 +134,7 @@
    * @param applyProvidedFixInput The fix(es) to apply to a new change edit.
    * @throws RestApiException if the fix couldn't be applied.
    */
+  @CanIgnoreReturnValue
   EditInfo applyFix(ApplyProvidedFixInput applyProvidedFixInput) throws RestApiException;
 
   Map<String, DiffInfo> getFixPreview(String fixId) throws RestApiException;
@@ -133,6 +142,7 @@
   Map<String, DiffInfo> getFixPreview(ApplyProvidedFixInput applyProvidedFixInput)
       throws RestApiException;
 
+  @CanIgnoreReturnValue
   DraftApi createDraft(DraftInput in) throws RestApiException;
 
   DraftApi draft(String id) throws RestApiException;
diff --git a/java/com/google/gerrit/server/StarredChangesUtil.java b/java/com/google/gerrit/extensions/api/changes/package-info.java
similarity index 64%
copy from java/com/google/gerrit/server/StarredChangesUtil.java
copy to java/com/google/gerrit/extensions/api/changes/package-info.java
index 0709b86..f548212 100644
--- a/java/com/google/gerrit/server/StarredChangesUtil.java
+++ b/java/com/google/gerrit/extensions/api/changes/package-info.java
@@ -1,4 +1,4 @@
-// Copyright (C) 2015 The Android Open Source Project
+// Copyright (C) 2023 The Android Open Source Project
 //
 // Licensed under the Apache License, Version 2.0 (the "License");
 // you may not use this file except in compliance with the License.
@@ -12,8 +12,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package com.google.gerrit.server;
+@CheckReturnValue
+package com.google.gerrit.extensions.api.changes;
 
-// TODO - delete this class. It cannot be deleted as part of the original refactoring since it's
-// used by a plugin.
-public interface StarredChangesUtil extends StarredChangesReader, StarredChangesWriter {}
+import com.google.errorprone.annotations.CheckReturnValue;
diff --git a/java/com/google/gerrit/extensions/api/config/Server.java b/java/com/google/gerrit/extensions/api/config/Server.java
index 8b69ded..0fe09c6 100644
--- a/java/com/google/gerrit/extensions/api/config/Server.java
+++ b/java/com/google/gerrit/extensions/api/config/Server.java
@@ -14,6 +14,7 @@
 
 package com.google.gerrit.extensions.api.config;
 
+import com.google.errorprone.annotations.CanIgnoreReturnValue;
 import com.google.gerrit.extensions.client.DiffPreferencesInfo;
 import com.google.gerrit.extensions.client.EditPreferencesInfo;
 import com.google.gerrit.extensions.client.GeneralPreferencesInfo;
@@ -31,14 +32,17 @@
 
   GeneralPreferencesInfo getDefaultPreferences() throws RestApiException;
 
+  @CanIgnoreReturnValue
   GeneralPreferencesInfo setDefaultPreferences(GeneralPreferencesInfo in) throws RestApiException;
 
   DiffPreferencesInfo getDefaultDiffPreferences() throws RestApiException;
 
+  @CanIgnoreReturnValue
   DiffPreferencesInfo setDefaultDiffPreferences(DiffPreferencesInfo in) throws RestApiException;
 
   EditPreferencesInfo getDefaultEditPreferences() throws RestApiException;
 
+  @CanIgnoreReturnValue
   EditPreferencesInfo setDefaultEditPreferences(EditPreferencesInfo in) throws RestApiException;
 
   ConsistencyCheckInfo checkConsistency(ConsistencyCheckInput in) throws RestApiException;
diff --git a/java/com/google/gerrit/server/StarredChangesUtil.java b/java/com/google/gerrit/extensions/api/config/package-info.java
similarity index 64%
copy from java/com/google/gerrit/server/StarredChangesUtil.java
copy to java/com/google/gerrit/extensions/api/config/package-info.java
index 0709b86..2ee371c 100644
--- a/java/com/google/gerrit/server/StarredChangesUtil.java
+++ b/java/com/google/gerrit/extensions/api/config/package-info.java
@@ -1,4 +1,4 @@
-// Copyright (C) 2015 The Android Open Source Project
+// Copyright (C) 2023 The Android Open Source Project
 //
 // Licensed under the Apache License, Version 2.0 (the "License");
 // you may not use this file except in compliance with the License.
@@ -12,8 +12,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package com.google.gerrit.server;
+@CheckReturnValue
+package com.google.gerrit.extensions.api.config;
 
-// TODO - delete this class. It cannot be deleted as part of the original refactoring since it's
-// used by a plugin.
-public interface StarredChangesUtil extends StarredChangesReader, StarredChangesWriter {}
+import com.google.errorprone.annotations.CheckReturnValue;
diff --git a/java/com/google/gerrit/extensions/api/groups/Groups.java b/java/com/google/gerrit/extensions/api/groups/Groups.java
index 1a46930..8d53af0 100644
--- a/java/com/google/gerrit/extensions/api/groups/Groups.java
+++ b/java/com/google/gerrit/extensions/api/groups/Groups.java
@@ -14,6 +14,7 @@
 
 package com.google.gerrit.extensions.api.groups;
 
+import com.google.errorprone.annotations.CanIgnoreReturnValue;
 import com.google.gerrit.extensions.client.ListGroupsOption;
 import com.google.gerrit.extensions.common.GroupInfo;
 import com.google.gerrit.extensions.restapi.NotImplementedException;
@@ -42,9 +43,11 @@
   GroupApi id(String id) throws RestApiException;
 
   /** Create a new group with the given name and default options. */
+  @CanIgnoreReturnValue
   GroupApi create(String name) throws RestApiException;
 
   /** Create a new group. */
+  @CanIgnoreReturnValue
   GroupApi create(GroupInput input) throws RestApiException;
 
   /** Returns new request for listing groups. */
diff --git a/java/com/google/gerrit/server/StarredChangesUtil.java b/java/com/google/gerrit/extensions/api/groups/package-info.java
similarity index 64%
copy from java/com/google/gerrit/server/StarredChangesUtil.java
copy to java/com/google/gerrit/extensions/api/groups/package-info.java
index 0709b86..3322f5c 100644
--- a/java/com/google/gerrit/server/StarredChangesUtil.java
+++ b/java/com/google/gerrit/extensions/api/groups/package-info.java
@@ -1,4 +1,4 @@
-// Copyright (C) 2015 The Android Open Source Project
+// Copyright (C) 2023 The Android Open Source Project
 //
 // Licensed under the Apache License, Version 2.0 (the "License");
 // you may not use this file except in compliance with the License.
@@ -12,8 +12,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package com.google.gerrit.server;
+@CheckReturnValue
+package com.google.gerrit.extensions.api.groups;
 
-// TODO - delete this class. It cannot be deleted as part of the original refactoring since it's
-// used by a plugin.
-public interface StarredChangesUtil extends StarredChangesReader, StarredChangesWriter {}
+import com.google.errorprone.annotations.CheckReturnValue;
diff --git a/java/com/google/gerrit/server/StarredChangesUtil.java b/java/com/google/gerrit/extensions/api/lfs/package-info.java
similarity index 64%
copy from java/com/google/gerrit/server/StarredChangesUtil.java
copy to java/com/google/gerrit/extensions/api/lfs/package-info.java
index 0709b86..2246f8d 100644
--- a/java/com/google/gerrit/server/StarredChangesUtil.java
+++ b/java/com/google/gerrit/extensions/api/lfs/package-info.java
@@ -1,4 +1,4 @@
-// Copyright (C) 2015 The Android Open Source Project
+// Copyright (C) 2023 The Android Open Source Project
 //
 // Licensed under the Apache License, Version 2.0 (the "License");
 // you may not use this file except in compliance with the License.
@@ -12,8 +12,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package com.google.gerrit.server;
+@CheckReturnValue
+package com.google.gerrit.extensions.api.lfs;
 
-// TODO - delete this class. It cannot be deleted as part of the original refactoring since it's
-// used by a plugin.
-public interface StarredChangesUtil extends StarredChangesReader, StarredChangesWriter {}
+import com.google.errorprone.annotations.CheckReturnValue;
diff --git a/java/com/google/gerrit/server/StarredChangesUtil.java b/java/com/google/gerrit/extensions/api/package-info.java
similarity index 64%
copy from java/com/google/gerrit/server/StarredChangesUtil.java
copy to java/com/google/gerrit/extensions/api/package-info.java
index 0709b86..b7ad7da 100644
--- a/java/com/google/gerrit/server/StarredChangesUtil.java
+++ b/java/com/google/gerrit/extensions/api/package-info.java
@@ -1,4 +1,4 @@
-// Copyright (C) 2015 The Android Open Source Project
+// Copyright (C) 2023 The Android Open Source Project
 //
 // Licensed under the Apache License, Version 2.0 (the "License");
 // you may not use this file except in compliance with the License.
@@ -12,8 +12,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package com.google.gerrit.server;
+@CheckReturnValue
+package com.google.gerrit.extensions.api;
 
-// TODO - delete this class. It cannot be deleted as part of the original refactoring since it's
-// used by a plugin.
-public interface StarredChangesUtil extends StarredChangesReader, StarredChangesWriter {}
+import com.google.errorprone.annotations.CheckReturnValue;
diff --git a/java/com/google/gerrit/extensions/api/plugins/Plugins.java b/java/com/google/gerrit/extensions/api/plugins/Plugins.java
index 6c2d6db..fed8507 100644
--- a/java/com/google/gerrit/extensions/api/plugins/Plugins.java
+++ b/java/com/google/gerrit/extensions/api/plugins/Plugins.java
@@ -14,6 +14,7 @@
 
 package com.google.gerrit.extensions.api.plugins;
 
+import com.google.errorprone.annotations.CanIgnoreReturnValue;
 import com.google.gerrit.extensions.common.PluginInfo;
 import com.google.gerrit.extensions.restapi.NotImplementedException;
 import com.google.gerrit.extensions.restapi.RestApiException;
@@ -29,9 +30,11 @@
   PluginApi name(String name) throws RestApiException;
 
   @Deprecated
+  @CanIgnoreReturnValue
   PluginApi install(String name, com.google.gerrit.extensions.common.InstallPluginInput input)
       throws RestApiException;
 
+  @CanIgnoreReturnValue
   PluginApi install(String name, InstallPluginInput input) throws RestApiException;
 
   abstract class ListRequest {
diff --git a/java/com/google/gerrit/server/StarredChangesUtil.java b/java/com/google/gerrit/extensions/api/plugins/package-info.java
similarity index 64%
copy from java/com/google/gerrit/server/StarredChangesUtil.java
copy to java/com/google/gerrit/extensions/api/plugins/package-info.java
index 0709b86..c5324d6 100644
--- a/java/com/google/gerrit/server/StarredChangesUtil.java
+++ b/java/com/google/gerrit/extensions/api/plugins/package-info.java
@@ -1,4 +1,4 @@
-// Copyright (C) 2015 The Android Open Source Project
+// Copyright (C) 2023 The Android Open Source Project
 //
 // Licensed under the Apache License, Version 2.0 (the "License");
 // you may not use this file except in compliance with the License.
@@ -12,8 +12,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package com.google.gerrit.server;
+@CheckReturnValue
+package com.google.gerrit.extensions.api.plugins;
 
-// TODO - delete this class. It cannot be deleted as part of the original refactoring since it's
-// used by a plugin.
-public interface StarredChangesUtil extends StarredChangesReader, StarredChangesWriter {}
+import com.google.errorprone.annotations.CheckReturnValue;
diff --git a/java/com/google/gerrit/extensions/api/projects/BranchApi.java b/java/com/google/gerrit/extensions/api/projects/BranchApi.java
index 90f1f3f..a410205 100644
--- a/java/com/google/gerrit/extensions/api/projects/BranchApi.java
+++ b/java/com/google/gerrit/extensions/api/projects/BranchApi.java
@@ -14,6 +14,7 @@
 
 package com.google.gerrit.extensions.api.projects;
 
+import com.google.errorprone.annotations.CanIgnoreReturnValue;
 import com.google.gerrit.extensions.api.changes.ChangeApi.SuggestedReviewersRequest;
 import com.google.gerrit.extensions.restapi.BinaryResult;
 import com.google.gerrit.extensions.restapi.NotImplementedException;
@@ -21,6 +22,7 @@
 import java.util.List;
 
 public interface BranchApi {
+  @CanIgnoreReturnValue
   BranchApi create(BranchInput in) throws RestApiException;
 
   BranchInfo get() throws RestApiException;
diff --git a/java/com/google/gerrit/extensions/api/projects/ConfigValue.java b/java/com/google/gerrit/extensions/api/projects/ConfigValue.java
index 5d6d2b0..2f60628 100644
--- a/java/com/google/gerrit/extensions/api/projects/ConfigValue.java
+++ b/java/com/google/gerrit/extensions/api/projects/ConfigValue.java
@@ -17,6 +17,13 @@
 import java.util.List;
 
 public class ConfigValue {
+
+  public ConfigValue() {}
+
+  public ConfigValue(String value) {
+    this.value = value;
+  }
+
   public String value;
   public List<String> values;
 }
diff --git a/java/com/google/gerrit/extensions/api/projects/LabelApi.java b/java/com/google/gerrit/extensions/api/projects/LabelApi.java
index 975a57e..a5e23f2 100644
--- a/java/com/google/gerrit/extensions/api/projects/LabelApi.java
+++ b/java/com/google/gerrit/extensions/api/projects/LabelApi.java
@@ -14,6 +14,7 @@
 
 package com.google.gerrit.extensions.api.projects;
 
+import com.google.errorprone.annotations.CanIgnoreReturnValue;
 import com.google.gerrit.common.Nullable;
 import com.google.gerrit.extensions.common.LabelDefinitionInfo;
 import com.google.gerrit.extensions.common.LabelDefinitionInput;
@@ -21,10 +22,12 @@
 import com.google.gerrit.extensions.restapi.RestApiException;
 
 public interface LabelApi {
+  @CanIgnoreReturnValue
   LabelApi create(LabelDefinitionInput input) throws RestApiException;
 
   LabelDefinitionInfo get() throws RestApiException;
 
+  @CanIgnoreReturnValue
   LabelDefinitionInfo update(LabelDefinitionInput input) throws RestApiException;
 
   default void delete() throws RestApiException {
diff --git a/java/com/google/gerrit/extensions/api/projects/ProjectApi.java b/java/com/google/gerrit/extensions/api/projects/ProjectApi.java
index 370068e..1169364 100644
--- a/java/com/google/gerrit/extensions/api/projects/ProjectApi.java
+++ b/java/com/google/gerrit/extensions/api/projects/ProjectApi.java
@@ -14,6 +14,7 @@
 
 package com.google.gerrit.extensions.api.projects;
 
+import com.google.errorprone.annotations.CanIgnoreReturnValue;
 import com.google.gerrit.extensions.api.access.ProjectAccessInfo;
 import com.google.gerrit.extensions.api.access.ProjectAccessInput;
 import com.google.gerrit.extensions.api.config.AccessCheckInfo;
@@ -43,6 +44,7 @@
 
   ProjectAccessInfo access() throws RestApiException;
 
+  @CanIgnoreReturnValue
   ProjectAccessInfo access(ProjectAccessInput p) throws RestApiException;
 
   ChangeInfo accessChange(ProjectAccessInput p) throws RestApiException;
@@ -53,6 +55,7 @@
 
   ConfigInfo config() throws RestApiException;
 
+  @CanIgnoreReturnValue
   ConfigInfo config(ConfigInput in) throws RestApiException;
 
   Map<String, Set<String>> commitsIn(Collection<String> commits, Collection<String> refs)
diff --git a/java/com/google/gerrit/extensions/api/projects/Projects.java b/java/com/google/gerrit/extensions/api/projects/Projects.java
index 34ca7d4..7c8ecca 100644
--- a/java/com/google/gerrit/extensions/api/projects/Projects.java
+++ b/java/com/google/gerrit/extensions/api/projects/Projects.java
@@ -14,6 +14,7 @@
 
 package com.google.gerrit.extensions.api.projects;
 
+import com.google.errorprone.annotations.CanIgnoreReturnValue;
 import com.google.gerrit.extensions.client.ProjectState;
 import com.google.gerrit.extensions.common.ProjectInfo;
 import com.google.gerrit.extensions.restapi.NotImplementedException;
@@ -46,6 +47,7 @@
    * @return API for accessing the newly-created project.
    * @throws RestApiException if an error occurred.
    */
+  @CanIgnoreReturnValue
   ProjectApi create(String name) throws RestApiException;
 
   /**
@@ -55,6 +57,7 @@
    * @return API for accessing the newly-created project.
    * @throws RestApiException if an error occurred.
    */
+  @CanIgnoreReturnValue
   ProjectApi create(ProjectInput in) throws RestApiException;
 
   ListRequest list();
diff --git a/java/com/google/gerrit/extensions/api/projects/SubmitRequirementApi.java b/java/com/google/gerrit/extensions/api/projects/SubmitRequirementApi.java
index a6e79db..29765c0 100644
--- a/java/com/google/gerrit/extensions/api/projects/SubmitRequirementApi.java
+++ b/java/com/google/gerrit/extensions/api/projects/SubmitRequirementApi.java
@@ -14,6 +14,7 @@
 
 package com.google.gerrit.extensions.api.projects;
 
+import com.google.errorprone.annotations.CanIgnoreReturnValue;
 import com.google.gerrit.extensions.common.SubmitRequirementInfo;
 import com.google.gerrit.extensions.common.SubmitRequirementInput;
 import com.google.gerrit.extensions.restapi.NotImplementedException;
@@ -21,12 +22,14 @@
 
 public interface SubmitRequirementApi {
   /** Create a new submit requirement. */
+  @CanIgnoreReturnValue
   SubmitRequirementApi create(SubmitRequirementInput input) throws RestApiException;
 
   /** Get existing submit requirement. */
   SubmitRequirementInfo get() throws RestApiException;
 
   /** Update existing submit requirement. */
+  @CanIgnoreReturnValue
   SubmitRequirementInfo update(SubmitRequirementInput input) throws RestApiException;
 
   /** Delete existing submit requirement. */
diff --git a/java/com/google/gerrit/extensions/api/projects/TagApi.java b/java/com/google/gerrit/extensions/api/projects/TagApi.java
index 39efeac..69c29df 100644
--- a/java/com/google/gerrit/extensions/api/projects/TagApi.java
+++ b/java/com/google/gerrit/extensions/api/projects/TagApi.java
@@ -14,10 +14,12 @@
 
 package com.google.gerrit.extensions.api.projects;
 
+import com.google.errorprone.annotations.CanIgnoreReturnValue;
 import com.google.gerrit.extensions.restapi.NotImplementedException;
 import com.google.gerrit.extensions.restapi.RestApiException;
 
 public interface TagApi {
+  @CanIgnoreReturnValue
   TagApi create(TagInput input) throws RestApiException;
 
   TagInfo get() throws RestApiException;
diff --git a/java/com/google/gerrit/server/StarredChangesUtil.java b/java/com/google/gerrit/extensions/api/projects/package-info.java
similarity index 64%
copy from java/com/google/gerrit/server/StarredChangesUtil.java
copy to java/com/google/gerrit/extensions/api/projects/package-info.java
index 0709b86..4fcfc28 100644
--- a/java/com/google/gerrit/server/StarredChangesUtil.java
+++ b/java/com/google/gerrit/extensions/api/projects/package-info.java
@@ -1,4 +1,4 @@
-// Copyright (C) 2015 The Android Open Source Project
+// Copyright (C) 2023 The Android Open Source Project
 //
 // Licensed under the Apache License, Version 2.0 (the "License");
 // you may not use this file except in compliance with the License.
@@ -12,8 +12,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package com.google.gerrit.server;
+@CheckReturnValue
+package com.google.gerrit.extensions.api.projects;
 
-// TODO - delete this class. It cannot be deleted as part of the original refactoring since it's
-// used by a plugin.
-public interface StarredChangesUtil extends StarredChangesReader, StarredChangesWriter {}
+import com.google.errorprone.annotations.CheckReturnValue;
diff --git a/java/com/google/gerrit/server/StarredChangesUtil.java b/java/com/google/gerrit/extensions/auth/oauth/package-info.java
similarity index 64%
copy from java/com/google/gerrit/server/StarredChangesUtil.java
copy to java/com/google/gerrit/extensions/auth/oauth/package-info.java
index 0709b86..591ea67 100644
--- a/java/com/google/gerrit/server/StarredChangesUtil.java
+++ b/java/com/google/gerrit/extensions/auth/oauth/package-info.java
@@ -1,4 +1,4 @@
-// Copyright (C) 2015 The Android Open Source Project
+// Copyright (C) 2023 The Android Open Source Project
 //
 // Licensed under the Apache License, Version 2.0 (the "License");
 // you may not use this file except in compliance with the License.
@@ -12,8 +12,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package com.google.gerrit.server;
+@CheckReturnValue
+package com.google.gerrit.extensions.auth.oauth;
 
-// TODO - delete this class. It cannot be deleted as part of the original refactoring since it's
-// used by a plugin.
-public interface StarredChangesUtil extends StarredChangesReader, StarredChangesWriter {}
+import com.google.errorprone.annotations.CheckReturnValue;
diff --git a/java/com/google/gerrit/extensions/client/Comment.java b/java/com/google/gerrit/extensions/client/Comment.java
index b8843d3..8a68236 100644
--- a/java/com/google/gerrit/extensions/client/Comment.java
+++ b/java/com/google/gerrit/extensions/client/Comment.java
@@ -14,9 +14,11 @@
 
 package com.google.gerrit.extensions.client;
 
+import com.google.gerrit.extensions.common.FixSuggestionInfo;
 import java.sql.Timestamp;
 import java.time.Instant;
 import java.util.Comparator;
+import java.util.List;
 import java.util.Objects;
 
 public abstract class Comment {
@@ -49,6 +51,8 @@
    */
   public String commitId;
 
+  public List<FixSuggestionInfo> fixSuggestions;
+
   // TODO(issue-15508): Migrate timestamp fields in *Info/*Input classes from type Timestamp to
   // Instant
   @SuppressWarnings("JdkObsolete")
@@ -151,13 +155,15 @@
           && Objects.equals(inReplyTo, c.inReplyTo)
           && Objects.equals(updated, c.updated)
           && Objects.equals(message, c.message)
-          && Objects.equals(commitId, c.commitId);
+          && Objects.equals(commitId, c.commitId)
+          && Objects.equals(fixSuggestions, c.fixSuggestions);
     }
     return false;
   }
 
   @Override
   public int hashCode() {
-    return Objects.hash(patchSet, id, path, side, parent, line, range, inReplyTo, updated, message);
+    return Objects.hash(
+        patchSet, id, path, side, parent, line, range, inReplyTo, updated, message, fixSuggestions);
   }
 }
diff --git a/java/com/google/gerrit/extensions/client/GeneralPreferencesInfo.java b/java/com/google/gerrit/extensions/client/GeneralPreferencesInfo.java
index 5c48aaf..1ed9793 100644
--- a/java/com/google/gerrit/extensions/client/GeneralPreferencesInfo.java
+++ b/java/com/google/gerrit/extensions/client/GeneralPreferencesInfo.java
@@ -142,6 +142,7 @@
   public List<MenuItem> my;
   public List<String> changeTable;
   public Boolean allowBrowserNotifications;
+  public Boolean allowSuggestCodeWhileCommenting;
   /**
    * The sidebar section that the user prefers to have open on the diff page, or "NONE" if all
    * sidebars should be closed.
@@ -296,6 +297,7 @@
     p.workInProgressByDefault = false;
     p.allowBrowserNotifications = true;
     p.diffPageSidebar = "NONE";
+    p.allowSuggestCodeWhileCommenting = true;
     return p;
   }
 }
diff --git a/java/com/google/gerrit/server/StarredChangesUtil.java b/java/com/google/gerrit/extensions/client/package-info.java
similarity index 64%
copy from java/com/google/gerrit/server/StarredChangesUtil.java
copy to java/com/google/gerrit/extensions/client/package-info.java
index 0709b86..75bbfda 100644
--- a/java/com/google/gerrit/server/StarredChangesUtil.java
+++ b/java/com/google/gerrit/extensions/client/package-info.java
@@ -1,4 +1,4 @@
-// Copyright (C) 2015 The Android Open Source Project
+// Copyright (C) 2023 The Android Open Source Project
 //
 // Licensed under the Apache License, Version 2.0 (the "License");
 // you may not use this file except in compliance with the License.
@@ -12,8 +12,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package com.google.gerrit.server;
+@CheckReturnValue
+package com.google.gerrit.extensions.client;
 
-// TODO - delete this class. It cannot be deleted as part of the original refactoring since it's
-// used by a plugin.
-public interface StarredChangesUtil extends StarredChangesReader, StarredChangesWriter {}
+import com.google.errorprone.annotations.CheckReturnValue;
diff --git a/java/com/google/gerrit/extensions/common/ChangeInfoDiffer.java b/java/com/google/gerrit/extensions/common/ChangeInfoDiffer.java
index 51c35dc..308daf0 100644
--- a/java/com/google/gerrit/extensions/common/ChangeInfoDiffer.java
+++ b/java/com/google/gerrit/extensions/common/ChangeInfoDiffer.java
@@ -75,12 +75,13 @@
   @SuppressWarnings("unchecked") // reflection is used to construct instances of T
   private static <T> T getAdded(T oldValue, T newValue) {
     if (newValue instanceof Collection) {
-      List<?> result = getAddedForCollection((Collection<?>) oldValue, (Collection<?>) newValue);
+      ImmutableList<?> result =
+          getAddedForCollection((Collection<?>) oldValue, (Collection<?>) newValue);
       return (T) result;
     }
 
     if (newValue instanceof Map) {
-      Map<?, ?> result = getAddedForMap((Map<?, ?>) oldValue, (Map<?, ?>) newValue);
+      ImmutableMap<?, ?> result = getAddedForMap((Map<?, ?>) oldValue, (Map<?, ?>) newValue);
       return (T) result;
     }
 
diff --git a/java/com/google/gerrit/extensions/common/CommentInfo.java b/java/com/google/gerrit/extensions/common/CommentInfo.java
index 35587a0..aee2552 100644
--- a/java/com/google/gerrit/extensions/common/CommentInfo.java
+++ b/java/com/google/gerrit/extensions/common/CommentInfo.java
@@ -39,13 +39,14 @@
       CommentInfo ci = (CommentInfo) o;
       return Objects.equals(author, ci.author)
           && Objects.equals(tag, ci.tag)
-          && Objects.equals(unresolved, ci.unresolved);
+          && Objects.equals(unresolved, ci.unresolved)
+          && Objects.equals(fixSuggestions, ci.fixSuggestions);
     }
     return false;
   }
 
   @Override
   public int hashCode() {
-    return Objects.hash(super.hashCode(), author, tag, unresolved);
+    return Objects.hash(super.hashCode(), author, tag, unresolved, fixSuggestions);
   }
 }
diff --git a/java/com/google/gerrit/extensions/common/CommitMessageInput.java b/java/com/google/gerrit/extensions/common/CommitMessageInput.java
index 1e23cb4..0b27c6b 100644
--- a/java/com/google/gerrit/extensions/common/CommitMessageInput.java
+++ b/java/com/google/gerrit/extensions/common/CommitMessageInput.java
@@ -27,4 +27,6 @@
   @Nullable public NotifyHandling notify;
 
   public Map<RecipientType, NotifyInfo> notifyDetails;
+
+  public String committerEmail;
 }
diff --git a/java/com/google/gerrit/extensions/common/DownloadSchemeInfo.java b/java/com/google/gerrit/extensions/common/DownloadSchemeInfo.java
index 6f0b178..2c2b866 100644
--- a/java/com/google/gerrit/extensions/common/DownloadSchemeInfo.java
+++ b/java/com/google/gerrit/extensions/common/DownloadSchemeInfo.java
@@ -18,6 +18,7 @@
 
 public class DownloadSchemeInfo {
   public String url;
+  public String description;
   public Boolean isAuthRequired;
   public Boolean isAuthSupported;
   public Map<String, String> commands;
diff --git a/java/com/google/gerrit/extensions/common/FixReplacementInfo.java b/java/com/google/gerrit/extensions/common/FixReplacementInfo.java
index 9e5890e..6df4fb9 100644
--- a/java/com/google/gerrit/extensions/common/FixReplacementInfo.java
+++ b/java/com/google/gerrit/extensions/common/FixReplacementInfo.java
@@ -15,9 +15,26 @@
 package com.google.gerrit.extensions.common;
 
 import com.google.gerrit.extensions.client.Comment;
+import java.util.Objects;
 
 public class FixReplacementInfo {
   public String path;
   public Comment.Range range;
   public String replacement;
+
+  @Override
+  public boolean equals(Object o) {
+    if (!(o instanceof FixReplacementInfo)) {
+      return false;
+    }
+    FixReplacementInfo fs = (FixReplacementInfo) o;
+    return Objects.equals(path, fs.path)
+        && Objects.equals(range, fs.range)
+        && Objects.equals(replacement, fs.replacement);
+  }
+
+  @Override
+  public int hashCode() {
+    return Objects.hash(path, range, replacement);
+  }
 }
diff --git a/java/com/google/gerrit/extensions/common/FixSuggestionInfo.java b/java/com/google/gerrit/extensions/common/FixSuggestionInfo.java
index 7ba7fcc..50df366 100644
--- a/java/com/google/gerrit/extensions/common/FixSuggestionInfo.java
+++ b/java/com/google/gerrit/extensions/common/FixSuggestionInfo.java
@@ -15,9 +15,26 @@
 package com.google.gerrit.extensions.common;
 
 import java.util.List;
+import java.util.Objects;
 
 public class FixSuggestionInfo {
   public String fixId;
   public String description;
   public List<FixReplacementInfo> replacements;
+
+  @Override
+  public boolean equals(Object o) {
+    if (!(o instanceof FixSuggestionInfo)) {
+      return false;
+    }
+    FixSuggestionInfo fs = (FixSuggestionInfo) o;
+    return Objects.equals(fixId, fs.fixId)
+        && Objects.equals(description, fs.description)
+        && Objects.equals(replacements, fs.replacements);
+  }
+
+  @Override
+  public int hashCode() {
+    return Objects.hash(fixId, description, replacements);
+  }
 }
diff --git a/java/com/google/gerrit/extensions/common/RobotCommentInfo.java b/java/com/google/gerrit/extensions/common/RobotCommentInfo.java
index 8d8731f..780cab7 100644
--- a/java/com/google/gerrit/extensions/common/RobotCommentInfo.java
+++ b/java/com/google/gerrit/extensions/common/RobotCommentInfo.java
@@ -14,13 +14,12 @@
 
 package com.google.gerrit.extensions.common;
 
-import java.util.List;
 import java.util.Map;
 
+@Deprecated
 public class RobotCommentInfo extends CommentInfo {
   public String robotId;
   public String robotRunId;
   public String url;
   public Map<String, String> properties;
-  public List<FixSuggestionInfo> fixSuggestions;
 }
diff --git a/java/com/google/gerrit/server/StarredChangesUtil.java b/java/com/google/gerrit/extensions/common/package-info.java
similarity index 64%
copy from java/com/google/gerrit/server/StarredChangesUtil.java
copy to java/com/google/gerrit/extensions/common/package-info.java
index 0709b86..40f03b2 100644
--- a/java/com/google/gerrit/server/StarredChangesUtil.java
+++ b/java/com/google/gerrit/extensions/common/package-info.java
@@ -1,4 +1,4 @@
-// Copyright (C) 2015 The Android Open Source Project
+// Copyright (C) 2023 The Android Open Source Project
 //
 // Licensed under the Apache License, Version 2.0 (the "License");
 // you may not use this file except in compliance with the License.
@@ -12,8 +12,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package com.google.gerrit.server;
+@CheckReturnValue
+package com.google.gerrit.extensions.common;
 
-// TODO - delete this class. It cannot be deleted as part of the original refactoring since it's
-// used by a plugin.
-public interface StarredChangesUtil extends StarredChangesReader, StarredChangesWriter {}
+import com.google.errorprone.annotations.CheckReturnValue;
diff --git a/java/com/google/gerrit/extensions/common/testing/BUILD b/java/com/google/gerrit/extensions/common/testing/BUILD
index 33cbb99..c126928 100644
--- a/java/com/google/gerrit/extensions/common/testing/BUILD
+++ b/java/com/google/gerrit/extensions/common/testing/BUILD
@@ -13,6 +13,7 @@
         "//lib:jgit",
         "//lib/auto:auto-value",
         "//lib/auto:auto-value-annotations",
+        "//lib/errorprone:annotations",
         "//lib/truth",
     ],
 )
diff --git a/java/com/google/gerrit/extensions/common/testing/CommentInfoSubject.java b/java/com/google/gerrit/extensions/common/testing/CommentInfoSubject.java
index c34e439..049d6e4 100644
--- a/java/com/google/gerrit/extensions/common/testing/CommentInfoSubject.java
+++ b/java/com/google/gerrit/extensions/common/testing/CommentInfoSubject.java
@@ -17,6 +17,7 @@
 import static com.google.common.truth.Truth.assertAbout;
 import static com.google.gerrit.extensions.common.testing.AccountInfoSubject.accounts;
 import static com.google.gerrit.extensions.common.testing.RangeSubject.ranges;
+import static com.google.gerrit.truth.ListSubject.elements;
 
 import com.google.common.truth.BooleanSubject;
 import com.google.common.truth.ComparableSubject;
@@ -26,6 +27,7 @@
 import com.google.common.truth.Subject;
 import com.google.gerrit.extensions.client.Side;
 import com.google.gerrit.extensions.common.CommentInfo;
+import com.google.gerrit.extensions.common.FixSuggestionInfo;
 import com.google.gerrit.truth.ListSubject;
 import java.sql.Timestamp;
 import java.util.List;
@@ -52,6 +54,12 @@
     this.commentInfo = commentInfo;
   }
 
+  public ListSubject<FixSuggestionInfoSubject, FixSuggestionInfo> fixSuggestions() {
+    return check("fixSuggestions")
+        .about(elements())
+        .thatCustom(commentInfo.fixSuggestions, FixSuggestionInfoSubject.fixSuggestions());
+  }
+
   public StringSubject uuid() {
     return check("id").that(commentInfo().id);
   }
@@ -116,4 +124,8 @@
     isNotNull();
     return commentInfo;
   }
+
+  public FixSuggestionInfoSubject onlyFixSuggestion() {
+    return fixSuggestions().onlyElement();
+  }
 }
diff --git a/java/com/google/gerrit/server/StarredChangesUtil.java b/java/com/google/gerrit/extensions/common/testing/package-info.java
similarity index 64%
copy from java/com/google/gerrit/server/StarredChangesUtil.java
copy to java/com/google/gerrit/extensions/common/testing/package-info.java
index 0709b86..869891a 100644
--- a/java/com/google/gerrit/server/StarredChangesUtil.java
+++ b/java/com/google/gerrit/extensions/common/testing/package-info.java
@@ -1,4 +1,4 @@
-// Copyright (C) 2015 The Android Open Source Project
+// Copyright (C) 2023 The Android Open Source Project
 //
 // Licensed under the Apache License, Version 2.0 (the "License");
 // you may not use this file except in compliance with the License.
@@ -12,8 +12,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package com.google.gerrit.server;
+@CheckReturnValue
+package com.google.gerrit.extensions.common.testing;
 
-// TODO - delete this class. It cannot be deleted as part of the original refactoring since it's
-// used by a plugin.
-public interface StarredChangesUtil extends StarredChangesReader, StarredChangesWriter {}
+import com.google.errorprone.annotations.CheckReturnValue;
diff --git a/java/com/google/gerrit/extensions/conditions/BooleanCondition.java b/java/com/google/gerrit/extensions/conditions/BooleanCondition.java
index 9c354fb..b1c1e93 100644
--- a/java/com/google/gerrit/extensions/conditions/BooleanCondition.java
+++ b/java/com/google/gerrit/extensions/conditions/BooleanCondition.java
@@ -14,8 +14,8 @@
 
 package com.google.gerrit.extensions.conditions;
 
+import com.google.common.collect.ImmutableList;
 import com.google.common.collect.Iterables;
-import java.util.Collections;
 
 /** Delayed evaluation of a boolean condition. */
 public abstract class BooleanCondition {
@@ -270,8 +270,8 @@
     }
 
     @Override
-    public <T> Iterable<T> children(Class<T> type) {
-      return Collections.emptyList();
+    public <T> ImmutableList<T> children(Class<T> type) {
+      return ImmutableList.of();
     }
 
     @Override
diff --git a/java/com/google/gerrit/server/StarredChangesUtil.java b/java/com/google/gerrit/extensions/conditions/package-info.java
similarity index 64%
copy from java/com/google/gerrit/server/StarredChangesUtil.java
copy to java/com/google/gerrit/extensions/conditions/package-info.java
index 0709b86..8a6b726 100644
--- a/java/com/google/gerrit/server/StarredChangesUtil.java
+++ b/java/com/google/gerrit/extensions/conditions/package-info.java
@@ -1,4 +1,4 @@
-// Copyright (C) 2015 The Android Open Source Project
+// Copyright (C) 2023 The Android Open Source Project
 //
 // Licensed under the Apache License, Version 2.0 (the "License");
 // you may not use this file except in compliance with the License.
@@ -12,8 +12,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package com.google.gerrit.server;
+@CheckReturnValue
+package com.google.gerrit.extensions.conditions;
 
-// TODO - delete this class. It cannot be deleted as part of the original refactoring since it's
-// used by a plugin.
-public interface StarredChangesUtil extends StarredChangesReader, StarredChangesWriter {}
+import com.google.errorprone.annotations.CheckReturnValue;
diff --git a/java/com/google/gerrit/extensions/config/DownloadScheme.java b/java/com/google/gerrit/extensions/config/DownloadScheme.java
index 15801d4..5dad525 100644
--- a/java/com/google/gerrit/extensions/config/DownloadScheme.java
+++ b/java/com/google/gerrit/extensions/config/DownloadScheme.java
@@ -37,4 +37,9 @@
 
   /** Returns whether the download scheme is hidden in the UI */
   public abstract boolean isHidden();
+
+  /** Returns an optional description of the scheme */
+  public String getDescription() {
+    return null;
+  }
 }
diff --git a/java/com/google/gerrit/server/StarredChangesUtil.java b/java/com/google/gerrit/extensions/config/package-info.java
similarity index 64%
copy from java/com/google/gerrit/server/StarredChangesUtil.java
copy to java/com/google/gerrit/extensions/config/package-info.java
index 0709b86..2ddb32c 100644
--- a/java/com/google/gerrit/server/StarredChangesUtil.java
+++ b/java/com/google/gerrit/extensions/config/package-info.java
@@ -1,4 +1,4 @@
-// Copyright (C) 2015 The Android Open Source Project
+// Copyright (C) 2023 The Android Open Source Project
 //
 // Licensed under the Apache License, Version 2.0 (the "License");
 // you may not use this file except in compliance with the License.
@@ -12,8 +12,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package com.google.gerrit.server;
+@CheckReturnValue
+package com.google.gerrit.extensions.config;
 
-// TODO - delete this class. It cannot be deleted as part of the original refactoring since it's
-// used by a plugin.
-public interface StarredChangesUtil extends StarredChangesReader, StarredChangesWriter {}
+import com.google.errorprone.annotations.CheckReturnValue;
diff --git a/java/com/google/gerrit/extensions/events/GerritEvent.java b/java/com/google/gerrit/extensions/events/GerritEvent.java
index e43a981..abab852 100644
--- a/java/com/google/gerrit/extensions/events/GerritEvent.java
+++ b/java/com/google/gerrit/extensions/events/GerritEvent.java
@@ -19,4 +19,8 @@
 /** Base interface to be extended by Events. */
 public interface GerritEvent {
   NotifyHandling getNotify();
+
+  default String getInstanceId() {
+    return null;
+  }
 }
diff --git a/java/com/google/gerrit/server/StarredChangesUtil.java b/java/com/google/gerrit/extensions/events/package-info.java
similarity index 64%
copy from java/com/google/gerrit/server/StarredChangesUtil.java
copy to java/com/google/gerrit/extensions/events/package-info.java
index 0709b86..e0a454c 100644
--- a/java/com/google/gerrit/server/StarredChangesUtil.java
+++ b/java/com/google/gerrit/extensions/events/package-info.java
@@ -1,4 +1,4 @@
-// Copyright (C) 2015 The Android Open Source Project
+// Copyright (C) 2023 The Android Open Source Project
 //
 // Licensed under the Apache License, Version 2.0 (the "License");
 // you may not use this file except in compliance with the License.
@@ -12,8 +12,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package com.google.gerrit.server;
+@CheckReturnValue
+package com.google.gerrit.extensions.events;
 
-// TODO - delete this class. It cannot be deleted as part of the original refactoring since it's
-// used by a plugin.
-public interface StarredChangesUtil extends StarredChangesReader, StarredChangesWriter {}
+import com.google.errorprone.annotations.CheckReturnValue;
diff --git a/java/com/google/gerrit/server/StarredChangesUtil.java b/java/com/google/gerrit/extensions/package-info.java
similarity index 64%
copy from java/com/google/gerrit/server/StarredChangesUtil.java
copy to java/com/google/gerrit/extensions/package-info.java
index 0709b86..f886030 100644
--- a/java/com/google/gerrit/server/StarredChangesUtil.java
+++ b/java/com/google/gerrit/extensions/package-info.java
@@ -1,4 +1,4 @@
-// Copyright (C) 2015 The Android Open Source Project
+// Copyright (C) 2023 The Android Open Source Project
 //
 // Licensed under the Apache License, Version 2.0 (the "License");
 // you may not use this file except in compliance with the License.
@@ -12,8 +12,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package com.google.gerrit.server;
+@CheckReturnValue
+package com.google.gerrit.extensions;
 
-// TODO - delete this class. It cannot be deleted as part of the original refactoring since it's
-// used by a plugin.
-public interface StarredChangesUtil extends StarredChangesReader, StarredChangesWriter {}
+import com.google.errorprone.annotations.CheckReturnValue;
diff --git a/java/com/google/gerrit/extensions/registration/DynamicItem.java b/java/com/google/gerrit/extensions/registration/DynamicItem.java
index 3f848cb..4464af7 100644
--- a/java/com/google/gerrit/extensions/registration/DynamicItem.java
+++ b/java/com/google/gerrit/extensions/registration/DynamicItem.java
@@ -14,6 +14,7 @@
 
 package com.google.gerrit.extensions.registration;
 
+import com.google.errorprone.annotations.CanIgnoreReturnValue;
 import com.google.gerrit.common.Nullable;
 import com.google.inject.Binder;
 import com.google.inject.Key;
@@ -159,6 +160,7 @@
    * @param pluginName the name of the plugin providing the item.
    * @return handle to remove the item at a later point in time.
    */
+  @CanIgnoreReturnValue
   public RegistrationHandle set(T item, String pluginName) {
     return set(Providers.of(item), pluginName);
   }
@@ -170,6 +172,7 @@
    * @param pluginName name of the source providing the implementation.
    * @return handle to remove the item at a later point in time.
    */
+  @CanIgnoreReturnValue
   public RegistrationHandle set(Provider<T> impl, String pluginName) {
     final Extension<T> item = new Extension<>(pluginName, impl);
     Extension<T> old = null;
@@ -197,6 +200,7 @@
    * @param pluginName the name of the plugin providing the item.
    * @return a handle that can remove this item later, or hot-swap the item.
    */
+  @CanIgnoreReturnValue
   public ReloadableRegistrationHandle<T> set(Key<T> key, Provider<T> impl, String pluginName) {
     final Extension<T> item = new Extension<>(pluginName, impl);
     Extension<T> old = null;
diff --git a/java/com/google/gerrit/extensions/registration/DynamicSet.java b/java/com/google/gerrit/extensions/registration/DynamicSet.java
index 6dc8c6a..9925a66 100644
--- a/java/com/google/gerrit/extensions/registration/DynamicSet.java
+++ b/java/com/google/gerrit/extensions/registration/DynamicSet.java
@@ -20,6 +20,7 @@
 
 import com.google.common.collect.ImmutableSet;
 import com.google.common.collect.ImmutableSortedSet;
+import com.google.errorprone.annotations.CanIgnoreReturnValue;
 import com.google.gerrit.common.Nullable;
 import com.google.inject.Binder;
 import com.google.inject.Key;
@@ -249,6 +250,7 @@
    * @param item the item to add to the collection. Must not be null.
    * @return handle to remove the item at a later point in time.
    */
+  @CanIgnoreReturnValue
   public RegistrationHandle add(String pluginName, T item) {
     return add(pluginName, Providers.of(item));
   }
@@ -259,6 +261,7 @@
    * @param item the item to add to the collection. Must not be null.
    * @return handle to remove the item at a later point in time.
    */
+  @CanIgnoreReturnValue
   public RegistrationHandle add(String pluginName, Provider<T> item) {
     final AtomicReference<Extension<T>> ref =
         new AtomicReference<>(new Extension<>(pluginName, item));
@@ -281,6 +284,7 @@
    * @return a handle that can remove this item later, or hot-swap the item without it ever leaving
    *     the collection.
    */
+  @CanIgnoreReturnValue
   public ReloadableRegistrationHandle<T> add(String pluginName, Key<T> key, Provider<T> item) {
     AtomicReference<Extension<T>> ref = new AtomicReference<>(new Extension<>(pluginName, item));
     items.add(ref);
diff --git a/java/com/google/gerrit/extensions/registration/PrivateInternals_DynamicMapImpl.java b/java/com/google/gerrit/extensions/registration/PrivateInternals_DynamicMapImpl.java
index 67fc068..994a8fb 100644
--- a/java/com/google/gerrit/extensions/registration/PrivateInternals_DynamicMapImpl.java
+++ b/java/com/google/gerrit/extensions/registration/PrivateInternals_DynamicMapImpl.java
@@ -16,6 +16,7 @@
 
 import static java.util.Objects.requireNonNull;
 
+import com.google.errorprone.annotations.CanIgnoreReturnValue;
 import com.google.gerrit.common.Nullable;
 import com.google.gerrit.extensions.annotations.Export;
 import com.google.inject.Key;
@@ -51,6 +52,7 @@
    * @return a handle that can remove this item later, or hot-swap the item without it ever leaving
    *     the collection.
    */
+  @CanIgnoreReturnValue
   public ReloadableRegistrationHandle<T> put(String pluginName, Key<T> key, Provider<T> item) {
     requireNonNull(item);
     String exportName = ((Export) key.getAnnotation()).value();
diff --git a/java/com/google/gerrit/server/StarredChangesUtil.java b/java/com/google/gerrit/extensions/registration/package-info.java
similarity index 64%
copy from java/com/google/gerrit/server/StarredChangesUtil.java
copy to java/com/google/gerrit/extensions/registration/package-info.java
index 0709b86..bcd5880 100644
--- a/java/com/google/gerrit/server/StarredChangesUtil.java
+++ b/java/com/google/gerrit/extensions/registration/package-info.java
@@ -1,4 +1,4 @@
-// Copyright (C) 2015 The Android Open Source Project
+// Copyright (C) 2023 The Android Open Source Project
 //
 // Licensed under the Apache License, Version 2.0 (the "License");
 // you may not use this file except in compliance with the License.
@@ -12,8 +12,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package com.google.gerrit.server;
+@CheckReturnValue
+package com.google.gerrit.extensions.registration;
 
-// TODO - delete this class. It cannot be deleted as part of the original refactoring since it's
-// used by a plugin.
-public interface StarredChangesUtil extends StarredChangesReader, StarredChangesWriter {}
+import com.google.errorprone.annotations.CheckReturnValue;
diff --git a/java/com/google/gerrit/extensions/restapi/BinaryResult.java b/java/com/google/gerrit/extensions/restapi/BinaryResult.java
index 2ee376e..905c0db 100644
--- a/java/com/google/gerrit/extensions/restapi/BinaryResult.java
+++ b/java/com/google/gerrit/extensions/restapi/BinaryResult.java
@@ -16,6 +16,7 @@
 
 import static java.nio.charset.StandardCharsets.UTF_8;
 
+import com.google.errorprone.annotations.CanIgnoreReturnValue;
 import java.io.ByteArrayOutputStream;
 import java.io.Closeable;
 import java.io.IOException;
@@ -73,6 +74,7 @@
   }
 
   /** Set the MIME type of the result, and return {@code this}. */
+  @CanIgnoreReturnValue
   public BinaryResult setContentType(String contentType) {
     this.contentType = contentType != null ? contentType : OCTET_STREAM;
     return this;
@@ -84,6 +86,7 @@
   }
 
   /** Set the character set used to encode text data and return {@code this}. */
+  @CanIgnoreReturnValue
   public BinaryResult setCharacterEncoding(Charset encoding) {
     characterEncoding = encoding;
     return this;
@@ -95,6 +98,7 @@
   }
 
   /** Set the attachment file name and return {@code this}. */
+  @CanIgnoreReturnValue
   public BinaryResult setAttachmentName(String attachmentName) {
     this.attachmentName = attachmentName;
     return this;
@@ -106,6 +110,7 @@
   }
 
   /** Set the content length of the result; -1 if not known. */
+  @CanIgnoreReturnValue
   public BinaryResult setContentLength(long len) {
     this.contentLength = len;
     return this;
@@ -117,6 +122,7 @@
   }
 
   /** Disable gzip compression for already compressed responses. */
+  @CanIgnoreReturnValue
   public BinaryResult disableGzip() {
     this.gzip = false;
     return this;
@@ -128,6 +134,7 @@
   }
 
   /** Wrap the binary data in base64 encoding. */
+  @CanIgnoreReturnValue
   public BinaryResult base64() {
     base64 = true;
     return this;
diff --git a/java/com/google/gerrit/extensions/restapi/Response.java b/java/com/google/gerrit/extensions/restapi/Response.java
index 3ebae8d..85dd643 100644
--- a/java/com/google/gerrit/extensions/restapi/Response.java
+++ b/java/com/google/gerrit/extensions/restapi/Response.java
@@ -15,6 +15,7 @@
 package com.google.gerrit.extensions.restapi;
 
 import com.google.common.collect.ImmutableMultimap;
+import com.google.errorprone.annotations.CanIgnoreReturnValue;
 import java.util.concurrent.TimeUnit;
 
 /** Special return value to mean specific HTTP status codes in a REST API. */
@@ -90,6 +91,7 @@
 
   public abstract CacheControl caching();
 
+  @CanIgnoreReturnValue
   public abstract Response<T> caching(CacheControl c);
 
   @Override
diff --git a/java/com/google/gerrit/extensions/restapi/RestApiModule.java b/java/com/google/gerrit/extensions/restapi/RestApiModule.java
index 85bd5a1..6d0191e 100644
--- a/java/com/google/gerrit/extensions/restapi/RestApiModule.java
+++ b/java/com/google/gerrit/extensions/restapi/RestApiModule.java
@@ -14,6 +14,7 @@
 
 package com.google.gerrit.extensions.restapi;
 
+import com.google.errorprone.annotations.CanIgnoreReturnValue;
 import com.google.gerrit.extensions.annotations.Export;
 import com.google.gerrit.extensions.annotations.Exports;
 import com.google.gerrit.extensions.config.FactoryModule;
@@ -128,6 +129,7 @@
       this.binder = binder;
     }
 
+    @CanIgnoreReturnValue
     public <T extends RestReadView<P>> ScopedBindingBuilder to(Class<T> impl) {
       return binder.to(impl);
     }
@@ -136,11 +138,13 @@
       binder.toInstance(impl);
     }
 
+    @CanIgnoreReturnValue
     public <T extends RestReadView<P>> ScopedBindingBuilder toProvider(
         Class<? extends Provider<? extends T>> providerType) {
       return binder.toProvider(providerType);
     }
 
+    @CanIgnoreReturnValue
     public <T extends RestReadView<P>> ScopedBindingBuilder toProvider(
         Provider<? extends T> provider) {
       return binder.toProvider(provider);
@@ -154,6 +158,7 @@
       this.binder = binder;
     }
 
+    @CanIgnoreReturnValue
     public <T extends RestModifyView<P, ?>> ScopedBindingBuilder to(Class<T> impl) {
       return binder.to(impl);
     }
@@ -162,11 +167,13 @@
       binder.toInstance(impl);
     }
 
+    @CanIgnoreReturnValue
     public <T extends RestModifyView<P, ?>> ScopedBindingBuilder toProvider(
         Class<? extends Provider<? extends T>> providerType) {
       return binder.toProvider(providerType);
     }
 
+    @CanIgnoreReturnValue
     public <T extends RestModifyView<P, ?>> ScopedBindingBuilder toProvider(
         Provider<? extends T> provider) {
       return binder.toProvider(provider);
@@ -180,6 +187,7 @@
       this.binder = binder;
     }
 
+    @CanIgnoreReturnValue
     public <P extends RestResource, T extends RestCollectionModifyView<P, C, ?>>
         ScopedBindingBuilder to(Class<T> impl) {
       return binder.to(impl);
@@ -190,11 +198,13 @@
       binder.toInstance(impl);
     }
 
+    @CanIgnoreReturnValue
     public <P extends RestResource, T extends RestCollectionModifyView<P, C, ?>>
         ScopedBindingBuilder toProvider(Class<? extends Provider<? extends T>> providerType) {
       return binder.toProvider(providerType);
     }
 
+    @CanIgnoreReturnValue
     public <P extends RestResource, T extends RestCollectionModifyView<P, C, ?>>
         ScopedBindingBuilder toProvider(Provider<? extends T> provider) {
       return binder.toProvider(provider);
@@ -208,6 +218,7 @@
       this.binder = binder;
     }
 
+    @CanIgnoreReturnValue
     public <P extends RestResource, T extends RestCollectionCreateView<P, C, ?>>
         ScopedBindingBuilder to(Class<T> impl) {
       return binder.to(impl);
@@ -218,11 +229,13 @@
       binder.toInstance(impl);
     }
 
+    @CanIgnoreReturnValue
     public <P extends RestResource, T extends RestCollectionCreateView<P, C, ?>>
         ScopedBindingBuilder toProvider(Class<? extends Provider<? extends T>> providerType) {
       return binder.toProvider(providerType);
     }
 
+    @CanIgnoreReturnValue
     public <P extends RestResource, T extends RestCollectionCreateView<P, C, ?>>
         ScopedBindingBuilder toProvider(Provider<? extends T> provider) {
       return binder.toProvider(provider);
@@ -236,6 +249,7 @@
       this.binder = binder;
     }
 
+    @CanIgnoreReturnValue
     public <P extends RestResource, T extends RestCollectionDeleteMissingView<P, C, ?>>
         ScopedBindingBuilder to(Class<T> impl) {
       return binder.to(impl);
@@ -246,11 +260,13 @@
       binder.toInstance(impl);
     }
 
+    @CanIgnoreReturnValue
     public <P extends RestResource, T extends RestCollectionDeleteMissingView<P, C, ?>>
         ScopedBindingBuilder toProvider(Class<? extends Provider<? extends T>> providerType) {
       return binder.toProvider(providerType);
     }
 
+    @CanIgnoreReturnValue
     public <P extends RestResource, T extends RestCollectionDeleteMissingView<P, C, ?>>
         ScopedBindingBuilder toProvider(Provider<? extends T> provider) {
       return binder.toProvider(provider);
@@ -264,6 +280,7 @@
       this.binder = binder;
     }
 
+    @CanIgnoreReturnValue
     public <C extends RestResource, T extends ChildCollection<P, C>> ScopedBindingBuilder to(
         Class<T> impl) {
       return binder.to(impl);
@@ -273,11 +290,13 @@
       binder.toInstance(impl);
     }
 
+    @CanIgnoreReturnValue
     public <C extends RestResource, T extends ChildCollection<P, C>>
         ScopedBindingBuilder toProvider(Class<? extends Provider<? extends T>> providerType) {
       return binder.toProvider(providerType);
     }
 
+    @CanIgnoreReturnValue
     public <C extends RestResource, T extends ChildCollection<P, C>>
         ScopedBindingBuilder toProvider(Provider<? extends T> provider) {
       return binder.toProvider(provider);
diff --git a/java/com/google/gerrit/server/StarredChangesUtil.java b/java/com/google/gerrit/extensions/restapi/package-info.java
similarity index 64%
copy from java/com/google/gerrit/server/StarredChangesUtil.java
copy to java/com/google/gerrit/extensions/restapi/package-info.java
index 0709b86..f3762e5 100644
--- a/java/com/google/gerrit/server/StarredChangesUtil.java
+++ b/java/com/google/gerrit/extensions/restapi/package-info.java
@@ -1,4 +1,4 @@
-// Copyright (C) 2015 The Android Open Source Project
+// Copyright (C) 2023 The Android Open Source Project
 //
 // Licensed under the Apache License, Version 2.0 (the "License");
 // you may not use this file except in compliance with the License.
@@ -12,8 +12,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package com.google.gerrit.server;
+@CheckReturnValue
+package com.google.gerrit.extensions.restapi;
 
-// TODO - delete this class. It cannot be deleted as part of the original refactoring since it's
-// used by a plugin.
-public interface StarredChangesUtil extends StarredChangesReader, StarredChangesWriter {}
+import com.google.errorprone.annotations.CheckReturnValue;
diff --git a/java/com/google/gerrit/extensions/restapi/testing/BUILD b/java/com/google/gerrit/extensions/restapi/testing/BUILD
index da11ce8..a86edb8 100644
--- a/java/com/google/gerrit/extensions/restapi/testing/BUILD
+++ b/java/com/google/gerrit/extensions/restapi/testing/BUILD
@@ -9,6 +9,7 @@
         "//java/com/google/gerrit/entities",
         "//java/com/google/gerrit/extensions:api",
         "//java/com/google/gerrit/truth",
+        "//lib/errorprone:annotations",
         "//lib/truth",
     ],
 )
diff --git a/java/com/google/gerrit/server/StarredChangesUtil.java b/java/com/google/gerrit/extensions/restapi/testing/package-info.java
similarity index 64%
copy from java/com/google/gerrit/server/StarredChangesUtil.java
copy to java/com/google/gerrit/extensions/restapi/testing/package-info.java
index 0709b86..74e31e7 100644
--- a/java/com/google/gerrit/server/StarredChangesUtil.java
+++ b/java/com/google/gerrit/extensions/restapi/testing/package-info.java
@@ -1,4 +1,4 @@
-// Copyright (C) 2015 The Android Open Source Project
+// Copyright (C) 2023 The Android Open Source Project
 //
 // Licensed under the Apache License, Version 2.0 (the "License");
 // you may not use this file except in compliance with the License.
@@ -12,8 +12,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package com.google.gerrit.server;
+@CheckReturnValue
+package com.google.gerrit.extensions.restapi.testing;
 
-// TODO - delete this class. It cannot be deleted as part of the original refactoring since it's
-// used by a plugin.
-public interface StarredChangesUtil extends StarredChangesReader, StarredChangesWriter {}
+import com.google.errorprone.annotations.CheckReturnValue;
diff --git a/java/com/google/gerrit/server/StarredChangesUtil.java b/java/com/google/gerrit/extensions/systemstatus/package-info.java
similarity index 64%
copy from java/com/google/gerrit/server/StarredChangesUtil.java
copy to java/com/google/gerrit/extensions/systemstatus/package-info.java
index 0709b86..ed9a8cc 100644
--- a/java/com/google/gerrit/server/StarredChangesUtil.java
+++ b/java/com/google/gerrit/extensions/systemstatus/package-info.java
@@ -1,4 +1,4 @@
-// Copyright (C) 2015 The Android Open Source Project
+// Copyright (C) 2023 The Android Open Source Project
 //
 // Licensed under the Apache License, Version 2.0 (the "License");
 // you may not use this file except in compliance with the License.
@@ -12,8 +12,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package com.google.gerrit.server;
+@CheckReturnValue
+package com.google.gerrit.extensions.systemstatus;
 
-// TODO - delete this class. It cannot be deleted as part of the original refactoring since it's
-// used by a plugin.
-public interface StarredChangesUtil extends StarredChangesReader, StarredChangesWriter {}
+import com.google.errorprone.annotations.CheckReturnValue;
diff --git a/java/com/google/gerrit/server/StarredChangesUtil.java b/java/com/google/gerrit/extensions/validators/package-info.java
similarity index 64%
copy from java/com/google/gerrit/server/StarredChangesUtil.java
copy to java/com/google/gerrit/extensions/validators/package-info.java
index 0709b86..076519a 100644
--- a/java/com/google/gerrit/server/StarredChangesUtil.java
+++ b/java/com/google/gerrit/extensions/validators/package-info.java
@@ -1,4 +1,4 @@
-// Copyright (C) 2015 The Android Open Source Project
+// Copyright (C) 2023 The Android Open Source Project
 //
 // Licensed under the Apache License, Version 2.0 (the "License");
 // you may not use this file except in compliance with the License.
@@ -12,8 +12,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package com.google.gerrit.server;
+@CheckReturnValue
+package com.google.gerrit.extensions.validators;
 
-// TODO - delete this class. It cannot be deleted as part of the original refactoring since it's
-// used by a plugin.
-public interface StarredChangesUtil extends StarredChangesReader, StarredChangesWriter {}
+import com.google.errorprone.annotations.CheckReturnValue;
diff --git a/java/com/google/gerrit/extensions/webui/UiAction.java b/java/com/google/gerrit/extensions/webui/UiAction.java
index 9da0642..6c1d01d 100644
--- a/java/com/google/gerrit/extensions/webui/UiAction.java
+++ b/java/com/google/gerrit/extensions/webui/UiAction.java
@@ -15,6 +15,7 @@
 package com.google.gerrit.extensions.webui;
 
 import com.google.common.collect.ImmutableList;
+import com.google.errorprone.annotations.CanIgnoreReturnValue;
 import com.google.gerrit.common.Nullable;
 import com.google.gerrit.extensions.conditions.BooleanCondition;
 import com.google.gerrit.extensions.restapi.RestResource;
@@ -68,6 +69,7 @@
     }
 
     /** Set the label to appear on the button to activate this action. */
+    @CanIgnoreReturnValue
     public Description setLabel(String label) {
       this.label = label;
       return this;
@@ -78,6 +80,7 @@
     }
 
     /** Set the tool-tip text to appear when the mouse hovers on the button. */
+    @CanIgnoreReturnValue
     public Description setTitle(String title) {
       this.title = title;
       return this;
@@ -95,6 +98,7 @@
      * Set if the action's button is visible on screen for the current client. If not visible the
      * action description may not be sent to the client.
      */
+    @CanIgnoreReturnValue
     public Description setVisible(boolean visible) {
       return setVisible(BooleanCondition.valueOf(visible));
     }
@@ -103,6 +107,7 @@
      * Set if the action's button is visible on screen for the current client. If not visible the
      * action description may not be sent to the client.
      */
+    @CanIgnoreReturnValue
     public Description setVisible(BooleanCondition visible) {
       this.visible = visible;
       return this;
@@ -117,11 +122,13 @@
     }
 
     /** Set if the button should be invokable (true), or greyed out (false). */
+    @CanIgnoreReturnValue
     public Description setEnabled(boolean enabled) {
       return setEnabled(BooleanCondition.valueOf(enabled));
     }
 
     /** Set if the button should be invokable (true), or greyed out (false). */
+    @CanIgnoreReturnValue
     public Description setEnabled(BooleanCondition enabled) {
       this.enabled = enabled;
       return this;
@@ -135,6 +142,7 @@
       return ImmutableList.copyOf(enabledOptions);
     }
 
+    @CanIgnoreReturnValue
     public Description setOption(String optionName, boolean enabled) {
       if (enabled) {
         enabledOptions.add(optionName);
diff --git a/java/com/google/gerrit/server/StarredChangesUtil.java b/java/com/google/gerrit/extensions/webui/package-info.java
similarity index 64%
copy from java/com/google/gerrit/server/StarredChangesUtil.java
copy to java/com/google/gerrit/extensions/webui/package-info.java
index 0709b86..bc520f8 100644
--- a/java/com/google/gerrit/server/StarredChangesUtil.java
+++ b/java/com/google/gerrit/extensions/webui/package-info.java
@@ -1,4 +1,4 @@
-// Copyright (C) 2015 The Android Open Source Project
+// Copyright (C) 2023 The Android Open Source Project
 //
 // Licensed under the Apache License, Version 2.0 (the "License");
 // you may not use this file except in compliance with the License.
@@ -12,8 +12,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package com.google.gerrit.server;
+@CheckReturnValue
+package com.google.gerrit.extensions.webui;
 
-// TODO - delete this class. It cannot be deleted as part of the original refactoring since it's
-// used by a plugin.
-public interface StarredChangesUtil extends StarredChangesReader, StarredChangesWriter {}
+import com.google.errorprone.annotations.CheckReturnValue;
diff --git a/java/com/google/gerrit/git/BUILD b/java/com/google/gerrit/git/BUILD
index 98dacfa..3cd2be1 100644
--- a/java/com/google/gerrit/git/BUILD
+++ b/java/com/google/gerrit/git/BUILD
@@ -11,5 +11,7 @@
         "//lib/auto:auto-value",
         "//lib/auto:auto-value-annotations",
         "//lib/errorprone:annotations",
+        "//lib/flogger:api",
+        "//lib/log:log4j",
     ],
 )
diff --git a/java/com/google/gerrit/git/RefUpdateUtil.java b/java/com/google/gerrit/git/RefUpdateUtil.java
index c2db073..d0f738f 100644
--- a/java/com/google/gerrit/git/RefUpdateUtil.java
+++ b/java/com/google/gerrit/git/RefUpdateUtil.java
@@ -14,12 +14,18 @@
 
 package com.google.gerrit.git;
 
+import static com.google.common.collect.ImmutableList.toImmutableList;
+
 import com.google.common.annotations.VisibleForTesting;
+import com.google.common.base.Joiner;
+import com.google.common.flogger.FluentLogger;
 import com.google.errorprone.annotations.CanIgnoreReturnValue;
 import java.io.IOException;
+import java.util.Optional;
 import org.eclipse.jgit.internal.JGitText;
 import org.eclipse.jgit.lib.BatchRefUpdate;
 import org.eclipse.jgit.lib.NullProgressMonitor;
+import org.eclipse.jgit.lib.ObjectId;
 import org.eclipse.jgit.lib.RefUpdate;
 import org.eclipse.jgit.lib.Repository;
 import org.eclipse.jgit.revwalk.RevWalk;
@@ -27,6 +33,8 @@
 
 /** Static utilities for working with JGit's ref update APIs. */
 public class RefUpdateUtil {
+  private static final FluentLogger logger = FluentLogger.forEnclosingClass();
+
   /**
    * Execute a batch ref update, throwing a checked exception if not all updates succeeded.
    *
@@ -54,10 +62,36 @@
    * @throws IOException if any result was not {@code OK}.
    */
   public static void executeChecked(BatchRefUpdate bru, RevWalk rw) throws IOException {
+    logger.atFine().log(
+        "Executing ref updates: %s\n",
+        Joiner.on("\n")
+            .join(
+                bru.getCommands().stream()
+                    .map(
+                        cmd ->
+                            String.format(
+                                "%s (new tree ID: %s)",
+                                cmd, getNewTreeId(rw, cmd).map(ObjectId::name).orElse("n/a")))
+                    .collect(toImmutableList())));
     bru.execute(rw, NullProgressMonitor.INSTANCE);
     checkResults(bru);
   }
 
+  private static Optional<ObjectId> getNewTreeId(RevWalk revWalk, ReceiveCommand cmd) {
+    if (ReceiveCommand.Type.DELETE.equals(cmd.getType())) {
+      // Ref deletions do not have a new tree.
+      return Optional.empty();
+    }
+
+    try {
+      return Optional.of(revWalk.parseCommit(cmd.getNewId()).getTree());
+    } catch (IOException e) {
+      logger.atWarning().withCause(e).log(
+          "Failed parsing new commit %s for ref update: %s", cmd.getNewId().name(), cmd);
+      return Optional.empty();
+    }
+  }
+
   /**
    * Check results of all commands in the update batch, reducing to a single exception if there was
    * a failure.
diff --git a/java/com/google/gerrit/server/StarredChangesUtil.java b/java/com/google/gerrit/git/package-info.java
similarity index 64%
copy from java/com/google/gerrit/server/StarredChangesUtil.java
copy to java/com/google/gerrit/git/package-info.java
index 0709b86..8c4d4b1 100644
--- a/java/com/google/gerrit/server/StarredChangesUtil.java
+++ b/java/com/google/gerrit/git/package-info.java
@@ -1,4 +1,4 @@
-// Copyright (C) 2015 The Android Open Source Project
+// Copyright (C) 2023 The Android Open Source Project
 //
 // Licensed under the Apache License, Version 2.0 (the "License");
 // you may not use this file except in compliance with the License.
@@ -12,8 +12,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package com.google.gerrit.server;
+@CheckReturnValue
+package com.google.gerrit.git;
 
-// TODO - delete this class. It cannot be deleted as part of the original refactoring since it's
-// used by a plugin.
-public interface StarredChangesUtil extends StarredChangesReader, StarredChangesWriter {}
+import com.google.errorprone.annotations.CheckReturnValue;
diff --git a/java/com/google/gerrit/git/testing/BUILD b/java/com/google/gerrit/git/testing/BUILD
index fda9aff..63bcfba 100644
--- a/java/com/google/gerrit/git/testing/BUILD
+++ b/java/com/google/gerrit/git/testing/BUILD
@@ -10,6 +10,7 @@
         "//java/com/google/gerrit/common:annotations",
         "//lib:guava",
         "//lib:jgit",
+        "//lib/errorprone:annotations",
         "//lib/truth",
         "//lib/truth:truth-java8-extension",
     ],
diff --git a/java/com/google/gerrit/server/StarredChangesUtil.java b/java/com/google/gerrit/git/testing/package-info.java
similarity index 64%
copy from java/com/google/gerrit/server/StarredChangesUtil.java
copy to java/com/google/gerrit/git/testing/package-info.java
index 0709b86..7ed8c4d 100644
--- a/java/com/google/gerrit/server/StarredChangesUtil.java
+++ b/java/com/google/gerrit/git/testing/package-info.java
@@ -1,4 +1,4 @@
-// Copyright (C) 2015 The Android Open Source Project
+// Copyright (C) 2023 The Android Open Source Project
 //
 // Licensed under the Apache License, Version 2.0 (the "License");
 // you may not use this file except in compliance with the License.
@@ -12,8 +12,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package com.google.gerrit.server;
+@CheckReturnValue
+package com.google.gerrit.git.testing;
 
-// TODO - delete this class. It cannot be deleted as part of the original refactoring since it's
-// used by a plugin.
-public interface StarredChangesUtil extends StarredChangesReader, StarredChangesWriter {}
+import com.google.errorprone.annotations.CheckReturnValue;
diff --git a/java/com/google/gerrit/gpg/BUILD b/java/com/google/gerrit/gpg/BUILD
index 3958821..fb24f13 100644
--- a/java/com/google/gerrit/gpg/BUILD
+++ b/java/com/google/gerrit/gpg/BUILD
@@ -19,6 +19,7 @@
         "//lib/auto:auto-factory",
         "//lib/bouncycastle:bcpg-neverlink",
         "//lib/bouncycastle:bcprov-neverlink",
+        "//lib/errorprone:annotations",
         "//lib/flogger:api",
         "//lib/guice",
         "//lib/guice:guice-assistedinject",
diff --git a/java/com/google/gerrit/gpg/Fingerprint.java b/java/com/google/gerrit/gpg/Fingerprint.java
index c12ff8b..a0212ca 100644
--- a/java/com/google/gerrit/gpg/Fingerprint.java
+++ b/java/com/google/gerrit/gpg/Fingerprint.java
@@ -16,6 +16,7 @@
 
 import static com.google.common.base.Preconditions.checkArgument;
 
+import com.google.errorprone.annotations.CanIgnoreReturnValue;
 import java.util.Arrays;
 import java.util.Collections;
 import java.util.HashMap;
@@ -53,6 +54,7 @@
     return Collections.unmodifiableMap(result);
   }
 
+  @CanIgnoreReturnValue
   private static byte[] checkLength(byte[] fp) {
     checkArgument(fp.length == 20, "fingerprint must be 20 bytes, got %s", fp.length);
     return fp;
diff --git a/java/com/google/gerrit/gpg/GerritPublicKeyChecker.java b/java/com/google/gerrit/gpg/GerritPublicKeyChecker.java
index fff4045..3940cc9 100644
--- a/java/com/google/gerrit/gpg/GerritPublicKeyChecker.java
+++ b/java/com/google/gerrit/gpg/GerritPublicKeyChecker.java
@@ -22,6 +22,7 @@
 import com.google.common.collect.Maps;
 import com.google.common.flogger.FluentLogger;
 import com.google.common.io.BaseEncoding;
+import com.google.errorprone.annotations.CanIgnoreReturnValue;
 import com.google.gerrit.extensions.registration.DynamicItem;
 import com.google.gerrit.server.IdentifiedUser;
 import com.google.gerrit.server.account.AccountState;
@@ -129,6 +130,7 @@
    * user. (Other keys checked in the course of verifying the web of trust are checked against the
    * set of identities in the database belonging to the same user as the key.)
    */
+  @CanIgnoreReturnValue
   public GerritPublicKeyChecker setExpectedUser(IdentifiedUser expectedUser) {
     this.expectedUser = expectedUser;
     return this;
diff --git a/java/com/google/gerrit/gpg/PublicKeyChecker.java b/java/com/google/gerrit/gpg/PublicKeyChecker.java
index 946fee3..2f8f7e9 100644
--- a/java/com/google/gerrit/gpg/PublicKeyChecker.java
+++ b/java/com/google/gerrit/gpg/PublicKeyChecker.java
@@ -30,6 +30,7 @@
 import static org.bouncycastle.openpgp.PGPSignature.KEY_REVOCATION;
 
 import com.google.common.flogger.FluentLogger;
+import com.google.errorprone.annotations.CanIgnoreReturnValue;
 import com.google.gerrit.common.Nullable;
 import com.google.gerrit.extensions.common.GpgKeyInfo.Status;
 import java.io.IOException;
@@ -77,6 +78,7 @@
    *     construct a map, see {@link Fingerprint#byId(Iterable)}.
    * @return a reference to this object.
    */
+  @CanIgnoreReturnValue
   public PublicKeyChecker enableTrust(int maxTrustDepth, Map<Long, Fingerprint> trusted) {
     if (maxTrustDepth <= 0) {
       throw new IllegalArgumentException("maxTrustDepth must be positive, got: " + maxTrustDepth);
@@ -90,12 +92,14 @@
   }
 
   /** Disable web-of-trust checks. */
+  @CanIgnoreReturnValue
   public PublicKeyChecker disableTrust() {
     trusted = null;
     return this;
   }
 
   /** Set the public key store for reading keys referenced in signatures. */
+  @CanIgnoreReturnValue
   public PublicKeyChecker setStore(PublicKeyStore store) {
     if (store == null) {
       throw new IllegalArgumentException("PublicKeyStore is required");
@@ -112,6 +116,7 @@
    * @param effectiveTime effective time.
    * @return a reference to this object.
    */
+  @CanIgnoreReturnValue
   public PublicKeyChecker setEffectiveTime(Instant effectiveTime) {
     this.effectiveTime = effectiveTime;
     return this;
diff --git a/java/com/google/gerrit/gpg/api/GpgKeyApiImpl.java b/java/com/google/gerrit/gpg/api/GpgKeyApiImpl.java
index 2a05f35..713db48 100644
--- a/java/com/google/gerrit/gpg/api/GpgKeyApiImpl.java
+++ b/java/com/google/gerrit/gpg/api/GpgKeyApiImpl.java
@@ -55,7 +55,8 @@
   @Override
   public void delete() throws RestApiException {
     try {
-      delete.apply(rsrc, new Input());
+      @SuppressWarnings("unused")
+      var unused = delete.apply(rsrc, new Input());
     } catch (RestApiException e) {
       throw e;
     } catch (PGPException | IOException | ConfigInvalidException e) {
diff --git a/java/com/google/gerrit/server/StarredChangesUtil.java b/java/com/google/gerrit/gpg/api/package-info.java
similarity index 64%
copy from java/com/google/gerrit/server/StarredChangesUtil.java
copy to java/com/google/gerrit/gpg/api/package-info.java
index 0709b86..524dc1c 100644
--- a/java/com/google/gerrit/server/StarredChangesUtil.java
+++ b/java/com/google/gerrit/gpg/api/package-info.java
@@ -1,4 +1,4 @@
-// Copyright (C) 2015 The Android Open Source Project
+// Copyright (C) 2023 The Android Open Source Project
 //
 // Licensed under the Apache License, Version 2.0 (the "License");
 // you may not use this file except in compliance with the License.
@@ -12,8 +12,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package com.google.gerrit.server;
+@CheckReturnValue
+package com.google.gerrit.gpg.api;
 
-// TODO - delete this class. It cannot be deleted as part of the original refactoring since it's
-// used by a plugin.
-public interface StarredChangesUtil extends StarredChangesReader, StarredChangesWriter {}
+import com.google.errorprone.annotations.CheckReturnValue;
diff --git a/java/com/google/gerrit/server/StarredChangesUtil.java b/java/com/google/gerrit/gpg/package-info.java
similarity index 64%
copy from java/com/google/gerrit/server/StarredChangesUtil.java
copy to java/com/google/gerrit/gpg/package-info.java
index 0709b86..6e56395 100644
--- a/java/com/google/gerrit/server/StarredChangesUtil.java
+++ b/java/com/google/gerrit/gpg/package-info.java
@@ -1,4 +1,4 @@
-// Copyright (C) 2015 The Android Open Source Project
+// Copyright (C) 2023 The Android Open Source Project
 //
 // Licensed under the Apache License, Version 2.0 (the "License");
 // you may not use this file except in compliance with the License.
@@ -12,8 +12,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package com.google.gerrit.server;
+@CheckReturnValue
+package com.google.gerrit.gpg;
 
-// TODO - delete this class. It cannot be deleted as part of the original refactoring since it's
-// used by a plugin.
-public interface StarredChangesUtil extends StarredChangesReader, StarredChangesWriter {}
+import com.google.errorprone.annotations.CheckReturnValue;
diff --git a/java/com/google/gerrit/gpg/server/PostGpgKeys.java b/java/com/google/gerrit/gpg/server/PostGpgKeys.java
index 886e4dd..e514e92 100644
--- a/java/com/google/gerrit/gpg/server/PostGpgKeys.java
+++ b/java/com/google/gerrit/gpg/server/PostGpgKeys.java
@@ -25,8 +25,10 @@
 
 import com.google.common.base.Joiner;
 import com.google.common.base.Throwables;
+import com.google.common.collect.ImmutableCollection;
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableSet;
 import com.google.common.collect.Lists;
 import com.google.common.collect.Maps;
 import com.google.common.flogger.FluentLogger;
@@ -131,12 +133,12 @@
       throws RestApiException, PGPException, IOException, ConfigInvalidException {
     GpgKeys.checkVisible(self, rsrc);
 
-    Collection<ExternalId> existingExtIds =
+    ImmutableSet<ExternalId> existingExtIds =
         externalIds.byAccount(rsrc.getUser().getAccountId(), SCHEME_GPGKEY);
     try (PublicKeyStore store = storeProvider.get()) {
-      Map<ExternalId, Fingerprint> toRemove = readKeysToRemove(input, existingExtIds);
-      Collection<Fingerprint> fingerprintsToRemove = toRemove.values();
-      List<PGPPublicKeyRing> newKeys = readKeysToAdd(input, fingerprintsToRemove);
+      ImmutableMap<ExternalId, Fingerprint> toRemove = readKeysToRemove(input, existingExtIds);
+      ImmutableCollection<Fingerprint> fingerprintsToRemove = toRemove.values();
+      ImmutableList<PGPPublicKeyRing> newKeys = readKeysToAdd(input, fingerprintsToRemove);
       List<ExternalId> newExtIds = new ArrayList<>(existingExtIds.size());
 
       for (PGPPublicKeyRing keyRing : newKeys) {
diff --git a/java/com/google/gerrit/server/StarredChangesUtil.java b/java/com/google/gerrit/gpg/server/package-info.java
similarity index 64%
copy from java/com/google/gerrit/server/StarredChangesUtil.java
copy to java/com/google/gerrit/gpg/server/package-info.java
index 0709b86..5379c44 100644
--- a/java/com/google/gerrit/server/StarredChangesUtil.java
+++ b/java/com/google/gerrit/gpg/server/package-info.java
@@ -1,4 +1,4 @@
-// Copyright (C) 2015 The Android Open Source Project
+// Copyright (C) 2023 The Android Open Source Project
 //
 // Licensed under the Apache License, Version 2.0 (the "License");
 // you may not use this file except in compliance with the License.
@@ -12,8 +12,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package com.google.gerrit.server;
+@CheckReturnValue
+package com.google.gerrit.gpg.server;
 
-// TODO - delete this class. It cannot be deleted as part of the original refactoring since it's
-// used by a plugin.
-public interface StarredChangesUtil extends StarredChangesReader, StarredChangesWriter {}
+import com.google.errorprone.annotations.CheckReturnValue;
diff --git a/java/com/google/gerrit/gpg/testing/BUILD b/java/com/google/gerrit/gpg/testing/BUILD
index dc39071..d55360b 100644
--- a/java/com/google/gerrit/gpg/testing/BUILD
+++ b/java/com/google/gerrit/gpg/testing/BUILD
@@ -10,5 +10,6 @@
         "//lib:guava",
         "//lib:jgit",
         "//lib/bouncycastle:bcpg-neverlink",
+        "//lib/errorprone:annotations",
     ],
 )
diff --git a/java/com/google/gerrit/server/StarredChangesUtil.java b/java/com/google/gerrit/gpg/testing/package-info.java
similarity index 64%
copy from java/com/google/gerrit/server/StarredChangesUtil.java
copy to java/com/google/gerrit/gpg/testing/package-info.java
index 0709b86..d9606c3 100644
--- a/java/com/google/gerrit/server/StarredChangesUtil.java
+++ b/java/com/google/gerrit/gpg/testing/package-info.java
@@ -1,4 +1,4 @@
-// Copyright (C) 2015 The Android Open Source Project
+// Copyright (C) 2023 The Android Open Source Project
 //
 // Licensed under the Apache License, Version 2.0 (the "License");
 // you may not use this file except in compliance with the License.
@@ -12,8 +12,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package com.google.gerrit.server;
+@CheckReturnValue
+package com.google.gerrit.gpg.testing;
 
-// TODO - delete this class. It cannot be deleted as part of the original refactoring since it's
-// used by a plugin.
-public interface StarredChangesUtil extends StarredChangesReader, StarredChangesWriter {}
+import com.google.errorprone.annotations.CheckReturnValue;
diff --git a/java/com/google/gerrit/httpd/AllRequestFilter.java b/java/com/google/gerrit/httpd/AllRequestFilter.java
index 1c3cafe..0422655 100644
--- a/java/com/google/gerrit/httpd/AllRequestFilter.java
+++ b/java/com/google/gerrit/httpd/AllRequestFilter.java
@@ -70,7 +70,7 @@
      * Initializes a filter if needed
      *
      * @param filter The filter that should get initialized
-     * @return {@code true} iff filter is now initialized
+     * @return {@code true} if filter is now initialized
      * @throws ServletException if filter itself fails to init
      */
     private synchronized boolean initFilterIfNeeded(AllRequestFilter filter)
@@ -150,7 +150,8 @@
       filterConfig = config;
 
       for (AllRequestFilter f : filters) {
-        initFilterIfNeeded(f);
+        @SuppressWarnings("unused")
+        var unused = initFilterIfNeeded(f);
       }
     }
 
diff --git a/java/com/google/gerrit/httpd/GitOverHttpServlet.java b/java/com/google/gerrit/httpd/GitOverHttpServlet.java
index e513a72..1537655 100644
--- a/java/com/google/gerrit/httpd/GitOverHttpServlet.java
+++ b/java/com/google/gerrit/httpd/GitOverHttpServlet.java
@@ -19,7 +19,6 @@
 import com.google.common.annotations.VisibleForTesting;
 import com.google.common.cache.Cache;
 import com.google.common.collect.ImmutableListMultimap;
-import com.google.common.collect.ListMultimap;
 import com.google.common.collect.Lists;
 import com.google.common.flogger.FluentLogger;
 import com.google.gerrit.common.Nullable;
@@ -259,7 +258,8 @@
     return commandName.toString();
   }
 
-  private static ListMultimap<String, String> extractParameters(HttpServletRequest request) {
+  private static ImmutableListMultimap<String, String> extractParameters(
+      HttpServletRequest request) {
     if (request.getQueryString() == null) {
       return ImmutableListMultimap.of();
     }
diff --git a/java/com/google/gerrit/httpd/RequestContextFilter.java b/java/com/google/gerrit/httpd/RequestContextFilter.java
index effbac0..9428c5e 100644
--- a/java/com/google/gerrit/httpd/RequestContextFilter.java
+++ b/java/com/google/gerrit/httpd/RequestContextFilter.java
@@ -63,7 +63,8 @@
     try {
       chain.doFilter(request, response);
     } finally {
-      local.setContext(old);
+      @SuppressWarnings("unused")
+      var unused = local.setContext(old);
     }
   }
 }
diff --git a/java/com/google/gerrit/httpd/UrlModule.java b/java/com/google/gerrit/httpd/UrlModule.java
index aad6b57..7a100c7 100644
--- a/java/com/google/gerrit/httpd/UrlModule.java
+++ b/java/com/google/gerrit/httpd/UrlModule.java
@@ -43,7 +43,7 @@
 import org.eclipse.jgit.lib.Constants;
 
 class UrlModule extends ServletModule {
-  private AuthConfig authConfig;
+  private final AuthConfig authConfig;
 
   UrlModule(AuthConfig authConfig) {
     this.authConfig = authConfig;
@@ -86,7 +86,7 @@
     serveRegex("^/(?:a/)?tools/(.*)$").with(ToolServlet.class);
 
     // Serve auth check. Mainly used by PolyGerrit for checking if a user is still logged in.
-    serveRegex("^/(?:a/)?auth-check$").with(AuthorizationCheckServlet.class);
+    serveRegex("^/(?:a/)?auth-check(\\.svg)?$").with(AuthorizationCheckServlet.class);
 
     // Bind servlets for REST root collections.
     // The '/plugins/' root collection is already handled by HttpPluginServlet
diff --git a/java/com/google/gerrit/server/StarredChangesUtil.java b/java/com/google/gerrit/httpd/auth/become/package-info.java
similarity index 64%
copy from java/com/google/gerrit/server/StarredChangesUtil.java
copy to java/com/google/gerrit/httpd/auth/become/package-info.java
index 0709b86..6887673 100644
--- a/java/com/google/gerrit/server/StarredChangesUtil.java
+++ b/java/com/google/gerrit/httpd/auth/become/package-info.java
@@ -1,4 +1,4 @@
-// Copyright (C) 2015 The Android Open Source Project
+// Copyright (C) 2023 The Android Open Source Project
 //
 // Licensed under the Apache License, Version 2.0 (the "License");
 // you may not use this file except in compliance with the License.
@@ -12,8 +12,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package com.google.gerrit.server;
+@CheckReturnValue
+package com.google.gerrit.httpd.auth.become;
 
-// TODO - delete this class. It cannot be deleted as part of the original refactoring since it's
-// used by a plugin.
-public interface StarredChangesUtil extends StarredChangesReader, StarredChangesWriter {}
+import com.google.errorprone.annotations.CheckReturnValue;
diff --git a/java/com/google/gerrit/httpd/auth/container/HttpsClientSslCertAuthFilter.java b/java/com/google/gerrit/httpd/auth/container/HttpsClientSslCertAuthFilter.java
index 59a7379..82cef6b 100644
--- a/java/com/google/gerrit/httpd/auth/container/HttpsClientSslCertAuthFilter.java
+++ b/java/com/google/gerrit/httpd/auth/container/HttpsClientSslCertAuthFilter.java
@@ -63,7 +63,7 @@
       throw new ServletException(
           "Couldn't get the attribute javax.servlet.request.X509Certificate from the request");
     }
-    String name = certs[0].getSubjectDN().getName();
+    String name = certs[0].getSubjectX500Principal().getName();
     Matcher m = REGEX_USERID.matcher(name);
     String userName;
     if (m.find()) {
diff --git a/java/com/google/gerrit/server/StarredChangesUtil.java b/java/com/google/gerrit/httpd/auth/container/package-info.java
similarity index 64%
copy from java/com/google/gerrit/server/StarredChangesUtil.java
copy to java/com/google/gerrit/httpd/auth/container/package-info.java
index 0709b86..ca1c89f 100644
--- a/java/com/google/gerrit/server/StarredChangesUtil.java
+++ b/java/com/google/gerrit/httpd/auth/container/package-info.java
@@ -1,4 +1,4 @@
-// Copyright (C) 2015 The Android Open Source Project
+// Copyright (C) 2023 The Android Open Source Project
 //
 // Licensed under the Apache License, Version 2.0 (the "License");
 // you may not use this file except in compliance with the License.
@@ -12,8 +12,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package com.google.gerrit.server;
+@CheckReturnValue
+package com.google.gerrit.httpd.auth.container;
 
-// TODO - delete this class. It cannot be deleted as part of the original refactoring since it's
-// used by a plugin.
-public interface StarredChangesUtil extends StarredChangesReader, StarredChangesWriter {}
+import com.google.errorprone.annotations.CheckReturnValue;
diff --git a/java/com/google/gerrit/server/StarredChangesUtil.java b/java/com/google/gerrit/httpd/auth/ldap/package-info.java
similarity index 64%
copy from java/com/google/gerrit/server/StarredChangesUtil.java
copy to java/com/google/gerrit/httpd/auth/ldap/package-info.java
index 0709b86..2a81cc9 100644
--- a/java/com/google/gerrit/server/StarredChangesUtil.java
+++ b/java/com/google/gerrit/httpd/auth/ldap/package-info.java
@@ -1,4 +1,4 @@
-// Copyright (C) 2015 The Android Open Source Project
+// Copyright (C) 2023 The Android Open Source Project
 //
 // Licensed under the Apache License, Version 2.0 (the "License");
 // you may not use this file except in compliance with the License.
@@ -12,8 +12,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package com.google.gerrit.server;
+@CheckReturnValue
+package com.google.gerrit.httpd.auth.ldap;
 
-// TODO - delete this class. It cannot be deleted as part of the original refactoring since it's
-// used by a plugin.
-public interface StarredChangesUtil extends StarredChangesReader, StarredChangesWriter {}
+import com.google.errorprone.annotations.CheckReturnValue;
diff --git a/java/com/google/gerrit/httpd/auth/oauth/BUILD b/java/com/google/gerrit/httpd/auth/oauth/BUILD
index 2bc65de4..3ced4ab 100644
--- a/java/com/google/gerrit/httpd/auth/oauth/BUILD
+++ b/java/com/google/gerrit/httpd/auth/oauth/BUILD
@@ -17,6 +17,7 @@
         "//lib:guava",
         "//lib:jgit",
         "//lib:servlet-api",
+        "//lib/errorprone:annotations",
         "//lib/flogger:api",
         "//lib/guice",
         "//lib/guice:guice-servlet",
diff --git a/java/com/google/gerrit/httpd/auth/oauth/OAuthWebFilter.java b/java/com/google/gerrit/httpd/auth/oauth/OAuthWebFilter.java
index 935762f..453dc0b 100644
--- a/java/com/google/gerrit/httpd/auth/oauth/OAuthWebFilter.java
+++ b/java/com/google/gerrit/httpd/auth/oauth/OAuthWebFilter.java
@@ -102,7 +102,9 @@
         service = findService(provider);
       }
       oauthSession.setServiceProvider(service);
-      oauthSession.login(httpRequest, httpResponse, service);
+
+      @SuppressWarnings("unused")
+      var unused = oauthSession.login(httpRequest, httpResponse, service);
     } else {
       chain.doFilter(httpRequest, response);
     }
diff --git a/java/com/google/gerrit/server/StarredChangesUtil.java b/java/com/google/gerrit/httpd/auth/oauth/package-info.java
similarity index 64%
copy from java/com/google/gerrit/server/StarredChangesUtil.java
copy to java/com/google/gerrit/httpd/auth/oauth/package-info.java
index 0709b86..4f8c601 100644
--- a/java/com/google/gerrit/server/StarredChangesUtil.java
+++ b/java/com/google/gerrit/httpd/auth/oauth/package-info.java
@@ -1,4 +1,4 @@
-// Copyright (C) 2015 The Android Open Source Project
+// Copyright (C) 2023 The Android Open Source Project
 //
 // Licensed under the Apache License, Version 2.0 (the "License");
 // you may not use this file except in compliance with the License.
@@ -12,8 +12,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package com.google.gerrit.server;
+@CheckReturnValue
+package com.google.gerrit.httpd.auth.oauth;
 
-// TODO - delete this class. It cannot be deleted as part of the original refactoring since it's
-// used by a plugin.
-public interface StarredChangesUtil extends StarredChangesReader, StarredChangesWriter {}
+import com.google.errorprone.annotations.CheckReturnValue;
diff --git a/java/com/google/gerrit/httpd/auth/openid/BUILD b/java/com/google/gerrit/httpd/auth/openid/BUILD
index 29841aa..7afb8ac 100644
--- a/java/com/google/gerrit/httpd/auth/openid/BUILD
+++ b/java/com/google/gerrit/httpd/auth/openid/BUILD
@@ -17,6 +17,7 @@
         "//java/com/google/gerrit/server",
         "//lib:guava",
         "//lib:servlet-api",
+        "//lib/errorprone:annotations",
         "//lib/flogger:api",
         "//lib/guice",
         "//lib/guice:guice-servlet",
diff --git a/java/com/google/gerrit/httpd/auth/openid/LoginForm.java b/java/com/google/gerrit/httpd/auth/openid/LoginForm.java
index e7057ad..4ed9078 100644
--- a/java/com/google/gerrit/httpd/auth/openid/LoginForm.java
+++ b/java/com/google/gerrit/httpd/auth/openid/LoginForm.java
@@ -174,7 +174,9 @@
       if ((isGerritLogin(req) || oauthSession.isOAuthFinal(req))) {
         oauthSession.setServiceProvider(oauthProvider);
         oauthSession.setLinkMode(link);
-        oauthSession.login(req, res, oauthProvider);
+
+        @SuppressWarnings("unused")
+        var unused = oauthSession.login(req, res, oauthProvider);
       }
     }
   }
diff --git a/java/com/google/gerrit/httpd/auth/openid/OAuthWebFilterOverOpenID.java b/java/com/google/gerrit/httpd/auth/openid/OAuthWebFilterOverOpenID.java
index 3d9c819..043b7a1 100644
--- a/java/com/google/gerrit/httpd/auth/openid/OAuthWebFilterOverOpenID.java
+++ b/java/com/google/gerrit/httpd/auth/openid/OAuthWebFilterOverOpenID.java
@@ -72,7 +72,9 @@
         throw new IllegalStateException("service is unknown");
       }
       oauthSession.setServiceProvider(service);
-      oauthSession.login(httpRequest, httpResponse, service);
+
+      @SuppressWarnings("unused")
+      var unused = oauthSession.login(httpRequest, httpResponse, service);
     } else {
       chain.doFilter(httpRequest, response);
     }
diff --git a/java/com/google/gerrit/server/StarredChangesUtil.java b/java/com/google/gerrit/httpd/auth/openid/package-info.java
similarity index 64%
copy from java/com/google/gerrit/server/StarredChangesUtil.java
copy to java/com/google/gerrit/httpd/auth/openid/package-info.java
index 0709b86..5e597da 100644
--- a/java/com/google/gerrit/server/StarredChangesUtil.java
+++ b/java/com/google/gerrit/httpd/auth/openid/package-info.java
@@ -1,4 +1,4 @@
-// Copyright (C) 2015 The Android Open Source Project
+// Copyright (C) 2023 The Android Open Source Project
 //
 // Licensed under the Apache License, Version 2.0 (the "License");
 // you may not use this file except in compliance with the License.
@@ -12,8 +12,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package com.google.gerrit.server;
+@CheckReturnValue
+package com.google.gerrit.httpd.auth.openid;
 
-// TODO - delete this class. It cannot be deleted as part of the original refactoring since it's
-// used by a plugin.
-public interface StarredChangesUtil extends StarredChangesReader, StarredChangesWriter {}
+import com.google.errorprone.annotations.CheckReturnValue;
diff --git a/java/com/google/gerrit/httpd/auth/restapi/BUILD b/java/com/google/gerrit/httpd/auth/restapi/BUILD
index 9ab51c5..a85fd5e 100644
--- a/java/com/google/gerrit/httpd/auth/restapi/BUILD
+++ b/java/com/google/gerrit/httpd/auth/restapi/BUILD
@@ -9,6 +9,7 @@
         "//java/com/google/gerrit/common:annotations",
         "//java/com/google/gerrit/extensions:api",
         "//java/com/google/gerrit/server",
+        "//lib/errorprone:annotations",
         "//lib/flogger:api",
         "//lib/guice",
     ],
diff --git a/java/com/google/gerrit/server/StarredChangesUtil.java b/java/com/google/gerrit/httpd/auth/restapi/package-info.java
similarity index 64%
copy from java/com/google/gerrit/server/StarredChangesUtil.java
copy to java/com/google/gerrit/httpd/auth/restapi/package-info.java
index 0709b86..a56dbde 100644
--- a/java/com/google/gerrit/server/StarredChangesUtil.java
+++ b/java/com/google/gerrit/httpd/auth/restapi/package-info.java
@@ -1,4 +1,4 @@
-// Copyright (C) 2015 The Android Open Source Project
+// Copyright (C) 2023 The Android Open Source Project
 //
 // Licensed under the Apache License, Version 2.0 (the "License");
 // you may not use this file except in compliance with the License.
@@ -12,8 +12,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package com.google.gerrit.server;
+@CheckReturnValue
+package com.google.gerrit.httpd.auth.restapi;
 
-// TODO - delete this class. It cannot be deleted as part of the original refactoring since it's
-// used by a plugin.
-public interface StarredChangesUtil extends StarredChangesReader, StarredChangesWriter {}
+import com.google.errorprone.annotations.CheckReturnValue;
diff --git a/java/com/google/gerrit/httpd/gitweb/GitwebServlet.java b/java/com/google/gerrit/httpd/gitweb/GitwebServlet.java
index d6718ca..d3de8e0 100644
--- a/java/com/google/gerrit/httpd/gitweb/GitwebServlet.java
+++ b/java/com/google/gerrit/httpd/gitweb/GitwebServlet.java
@@ -35,6 +35,8 @@
 import com.google.common.annotations.VisibleForTesting;
 import com.google.common.base.CharMatcher;
 import com.google.common.base.Splitter;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Iterators;
 import com.google.common.flogger.FluentLogger;
 import com.google.gerrit.common.PageLinks;
 import com.google.gerrit.entities.Project;
@@ -75,7 +77,6 @@
 import java.net.URISyntaxException;
 import java.nio.file.Files;
 import java.nio.file.Path;
-import java.util.Collections;
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.List;
@@ -771,8 +772,8 @@
   }
 
   @SuppressWarnings("JdkObsolete")
-  private static Iterable<String> getHeaderNames(HttpServletRequest req) {
-    return Collections.list(req.getHeaderNames());
+  private static ImmutableList<String> getHeaderNames(HttpServletRequest req) {
+    return ImmutableList.copyOf(Iterators.forEnumeration(req.getHeaderNames()));
   }
 
   /** private utility class that manages the Environment passed to exec. */
diff --git a/java/com/google/gerrit/server/StarredChangesUtil.java b/java/com/google/gerrit/httpd/gitweb/package-info.java
similarity index 64%
copy from java/com/google/gerrit/server/StarredChangesUtil.java
copy to java/com/google/gerrit/httpd/gitweb/package-info.java
index 0709b86..614388e 100644
--- a/java/com/google/gerrit/server/StarredChangesUtil.java
+++ b/java/com/google/gerrit/httpd/gitweb/package-info.java
@@ -1,4 +1,4 @@
-// Copyright (C) 2015 The Android Open Source Project
+// Copyright (C) 2023 The Android Open Source Project
 //
 // Licensed under the Apache License, Version 2.0 (the "License");
 // you may not use this file except in compliance with the License.
@@ -12,8 +12,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package com.google.gerrit.server;
+@CheckReturnValue
+package com.google.gerrit.httpd.gitweb;
 
-// TODO - delete this class. It cannot be deleted as part of the original refactoring since it's
-// used by a plugin.
-public interface StarredChangesUtil extends StarredChangesReader, StarredChangesWriter {}
+import com.google.errorprone.annotations.CheckReturnValue;
diff --git a/java/com/google/gerrit/httpd/init/BUILD b/java/com/google/gerrit/httpd/init/BUILD
index 990b5d7..e6fc53c 100644
--- a/java/com/google/gerrit/httpd/init/BUILD
+++ b/java/com/google/gerrit/httpd/init/BUILD
@@ -31,6 +31,7 @@
         "//lib:jgit",
         "//lib:jgit-ssh-apache",
         "//lib:servlet-api",
+        "//lib/errorprone:annotations",
         "//lib/flogger:api",
         "//lib/guice",
         "//lib/guice:guice-servlet",
diff --git a/java/com/google/gerrit/httpd/init/SiteInitializer.java b/java/com/google/gerrit/httpd/init/SiteInitializer.java
index 04a0ddd..f9f93e1 100644
--- a/java/com/google/gerrit/httpd/init/SiteInitializer.java
+++ b/java/com/google/gerrit/httpd/init/SiteInitializer.java
@@ -19,7 +19,6 @@
 import com.google.gerrit.pgm.init.BaseInit;
 import com.google.gerrit.pgm.init.PluginsDistribution;
 import java.nio.file.Path;
-import java.nio.file.Paths;
 import java.util.List;
 
 public final class SiteInitializer {
@@ -45,24 +44,29 @@
   public void init() {
     try {
       if (sitePath != null) {
-        Path site = Paths.get(sitePath);
+        Path site = Path.of(sitePath);
         logger.atInfo().log("Initializing site at %s", site.toRealPath().normalize());
-        new BaseInit(site, false, pluginsDistribution, pluginsToInstall).run();
+
+        @SuppressWarnings("unused")
+        var unused = new BaseInit(site, false, pluginsDistribution, pluginsToInstall).run();
+
         return;
       }
 
       String path = System.getProperty(GERRIT_SITE_PATH);
       Path site = null;
       if (!Strings.isNullOrEmpty(path)) {
-        site = Paths.get(path);
+        site = Path.of(path);
       }
 
       if (site == null && initPath != null) {
-        site = Paths.get(initPath);
+        site = Path.of(initPath);
       }
       if (site != null) {
         logger.atInfo().log("Initializing site at %s", site.toRealPath().normalize());
-        new BaseInit(site, false, pluginsDistribution, pluginsToInstall).run();
+
+        @SuppressWarnings("unused")
+        var unused = new BaseInit(site, false, pluginsDistribution, pluginsToInstall).run();
       }
     } catch (Exception e) {
       logger.atSevere().withCause(e).log("Site init failed");
diff --git a/java/com/google/gerrit/httpd/init/WebAppInitializer.java b/java/com/google/gerrit/httpd/init/WebAppInitializer.java
index e1abcb1..fe4eb1c 100644
--- a/java/com/google/gerrit/httpd/init/WebAppInitializer.java
+++ b/java/com/google/gerrit/httpd/init/WebAppInitializer.java
@@ -132,7 +132,6 @@
 import java.lang.reflect.InvocationTargetException;
 import java.lang.reflect.Method;
 import java.nio.file.Path;
-import java.nio.file.Paths;
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.List;
@@ -176,7 +175,7 @@
     if (manager == null) {
       String path = System.getProperty(GERRIT_SITE_PATH);
       if (path != null) {
-        sitePath = Paths.get(path);
+        sitePath = Path.of(path);
       } else {
         throw new ProvisionException(GERRIT_SITE_PATH + " must be defined");
       }
diff --git a/java/com/google/gerrit/server/StarredChangesUtil.java b/java/com/google/gerrit/httpd/init/package-info.java
similarity index 64%
copy from java/com/google/gerrit/server/StarredChangesUtil.java
copy to java/com/google/gerrit/httpd/init/package-info.java
index 0709b86..8ca6c9f 100644
--- a/java/com/google/gerrit/server/StarredChangesUtil.java
+++ b/java/com/google/gerrit/httpd/init/package-info.java
@@ -1,4 +1,4 @@
-// Copyright (C) 2015 The Android Open Source Project
+// Copyright (C) 2023 The Android Open Source Project
 //
 // Licensed under the Apache License, Version 2.0 (the "License");
 // you may not use this file except in compliance with the License.
@@ -12,8 +12,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package com.google.gerrit.server;
+@CheckReturnValue
+package com.google.gerrit.httpd.init;
 
-// TODO - delete this class. It cannot be deleted as part of the original refactoring since it's
-// used by a plugin.
-public interface StarredChangesUtil extends StarredChangesReader, StarredChangesWriter {}
+import com.google.errorprone.annotations.CheckReturnValue;
diff --git a/java/com/google/gerrit/server/StarredChangesUtil.java b/java/com/google/gerrit/httpd/package-info.java
similarity index 64%
copy from java/com/google/gerrit/server/StarredChangesUtil.java
copy to java/com/google/gerrit/httpd/package-info.java
index 0709b86..e0bb8e0 100644
--- a/java/com/google/gerrit/server/StarredChangesUtil.java
+++ b/java/com/google/gerrit/httpd/package-info.java
@@ -1,4 +1,4 @@
-// Copyright (C) 2015 The Android Open Source Project
+// Copyright (C) 2023 The Android Open Source Project
 //
 // Licensed under the Apache License, Version 2.0 (the "License");
 // you may not use this file except in compliance with the License.
@@ -12,8 +12,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package com.google.gerrit.server;
+@CheckReturnValue
+package com.google.gerrit.httpd;
 
-// TODO - delete this class. It cannot be deleted as part of the original refactoring since it's
-// used by a plugin.
-public interface StarredChangesUtil extends StarredChangesReader, StarredChangesWriter {}
+import com.google.errorprone.annotations.CheckReturnValue;
diff --git a/java/com/google/gerrit/httpd/plugins/HttpAutoRegisterModuleGenerator.java b/java/com/google/gerrit/httpd/plugins/HttpAutoRegisterModuleGenerator.java
index 9ab2d72..3d0a139 100644
--- a/java/com/google/gerrit/httpd/plugins/HttpAutoRegisterModuleGenerator.java
+++ b/java/com/google/gerrit/httpd/plugins/HttpAutoRegisterModuleGenerator.java
@@ -30,6 +30,7 @@
 import java.util.Map;
 import javax.servlet.http.HttpServlet;
 
+@SuppressWarnings("MutableGuiceModule")
 class HttpAutoRegisterModuleGenerator extends ServletModule implements ModuleGenerator {
   private final Map<String, Class<HttpServlet>> serve = new HashMap<>();
   private final ListMultimap<TypeLiteral<?>, Class<?>> listeners = LinkedListMultimap.create();
diff --git a/java/com/google/gerrit/server/StarredChangesUtil.java b/java/com/google/gerrit/httpd/plugins/package-info.java
similarity index 64%
copy from java/com/google/gerrit/server/StarredChangesUtil.java
copy to java/com/google/gerrit/httpd/plugins/package-info.java
index 0709b86..1149405 100644
--- a/java/com/google/gerrit/server/StarredChangesUtil.java
+++ b/java/com/google/gerrit/httpd/plugins/package-info.java
@@ -1,4 +1,4 @@
-// Copyright (C) 2015 The Android Open Source Project
+// Copyright (C) 2023 The Android Open Source Project
 //
 // Licensed under the Apache License, Version 2.0 (the "License");
 // you may not use this file except in compliance with the License.
@@ -12,8 +12,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package com.google.gerrit.server;
+@CheckReturnValue
+package com.google.gerrit.httpd.plugins;
 
-// TODO - delete this class. It cannot be deleted as part of the original refactoring since it's
-// used by a plugin.
-public interface StarredChangesUtil extends StarredChangesReader, StarredChangesWriter {}
+import com.google.errorprone.annotations.CheckReturnValue;
diff --git a/java/com/google/gerrit/httpd/raw/AuthorizationCheckServlet.java b/java/com/google/gerrit/httpd/raw/AuthorizationCheckServlet.java
index e3e96df..1e043b1 100644
--- a/java/com/google/gerrit/httpd/raw/AuthorizationCheckServlet.java
+++ b/java/com/google/gerrit/httpd/raw/AuthorizationCheckServlet.java
@@ -14,6 +14,8 @@
 
 package com.google.gerrit.httpd.raw;
 
+import static java.nio.charset.StandardCharsets.UTF_8;
+
 import com.google.gerrit.server.CurrentUser;
 import com.google.gerrit.util.http.CacheHeaders;
 import com.google.inject.Inject;
@@ -44,8 +46,18 @@
   protected void doGet(HttpServletRequest req, HttpServletResponse res) throws IOException {
     CacheHeaders.setNotCacheable(res);
     if (user.get().isIdentifiedUser()) {
-      res.setContentLength(0);
-      res.setStatus(HttpServletResponse.SC_NO_CONTENT);
+      if (req.getRequestURI().endsWith(".svg")) {
+        String responseToClient =
+            "<?xml version=\"1.0\" encoding=\"UTF-8\"?><svg xmlns=\"http://www.w3.org/2000/svg\" width=\"1\" height=\"1\"/>";
+        res.setContentType("image/svg+xml");
+        res.setCharacterEncoding(UTF_8.name());
+        res.setStatus(HttpServletResponse.SC_OK);
+        res.getWriter().write(responseToClient);
+        res.getWriter().flush();
+      } else {
+        res.setContentLength(0);
+        res.setStatus(HttpServletResponse.SC_NO_CONTENT);
+      }
     } else {
       res.setStatus(HttpServletResponse.SC_FORBIDDEN);
     }
diff --git a/java/com/google/gerrit/httpd/raw/IndexPreloadingUtil.java b/java/com/google/gerrit/httpd/raw/IndexPreloadingUtil.java
index 4c42e79..cc11638 100644
--- a/java/com/google/gerrit/httpd/raw/IndexPreloadingUtil.java
+++ b/java/com/google/gerrit/httpd/raw/IndexPreloadingUtil.java
@@ -99,6 +99,7 @@
           ListChangesOption.DETAILED_LABELS,
           ListChangesOption.DOWNLOAD_COMMANDS,
           ListChangesOption.MESSAGES,
+          ListChangesOption.REVIEWER_UPDATES,
           ListChangesOption.SUBMITTABLE,
           ListChangesOption.WEB_LINKS,
           ListChangesOption.SKIP_DIFFSTAT,
diff --git a/java/com/google/gerrit/httpd/raw/StaticModule.java b/java/com/google/gerrit/httpd/raw/StaticModule.java
index 587d82a..514712d 100644
--- a/java/com/google/gerrit/httpd/raw/StaticModule.java
+++ b/java/com/google/gerrit/httpd/raw/StaticModule.java
@@ -19,6 +19,7 @@
 import static java.nio.file.Files.exists;
 import static java.nio.file.Files.isReadable;
 
+import com.google.common.annotations.VisibleForTesting;
 import com.google.common.cache.Cache;
 import com.google.common.collect.ImmutableList;
 import com.google.common.flogger.FluentLogger;
@@ -62,7 +63,7 @@
 
 public class StaticModule extends ServletModule {
   private static final FluentLogger logger = FluentLogger.forEnclosingClass();
-  public static final String CHANGE_NUMBER_URI_REGEX = "^(?:/c)?/([1-9][0-9]*)/?$";
+  public static final String CHANGE_NUMBER_URI_REGEX = "^(?:/c)?/([1-9][0-9]*)/?.*";
   private static final Pattern CHANGE_NUMBER_URI_PATTERN = Pattern.compile(CHANGE_NUMBER_URI_REGEX);
 
   /**
@@ -87,19 +88,17 @@
   private static final String ROBOTS_TXT_SERVLET = "RobotsTxtServlet";
 
   private final GerritOptions options;
-  private Paths paths;
+  private final Paths paths;
 
   @Inject
   public StaticModule(GerritOptions options) {
     this.options = options;
+    this.paths = new Paths(options);
   }
 
   @Provides
   @Singleton
   private Paths getPaths() {
-    if (paths == null) {
-      paths = new Paths(options);
-    }
     return paths;
   }
 
@@ -263,8 +262,7 @@
           // root directory
           warFs = null;
           unpackedWar =
-              java.nio.file.Paths.get(
-                  launcherLoadedFrom.getParentFile().getParentFile().getParentFile().toURI());
+              Path.of(launcherLoadedFrom.getParentFile().getParentFile().getParentFile().toURI());
           sourceRoot = null;
           development = false;
           return;
@@ -354,7 +352,7 @@
   }
 
   @Singleton
-  private static class PolyGerritFilter implements Filter {
+  protected static class PolyGerritFilter implements Filter {
     private final HttpServlet polyGerritIndex;
     private final PolyGerritUiServlet polygerritUI;
 
@@ -405,7 +403,8 @@
       return matchPath(POLYGERRIT_ASSET_PATHS, path);
     }
 
-    private static boolean isPolyGerritIndex(String path) {
+    @VisibleForTesting
+    protected static boolean isPolyGerritIndex(String path) {
       return !isChangeNumberUri(path) && matchPath(POLYGERRIT_INDEX_PATHS, path);
     }
 
diff --git a/java/com/google/gerrit/server/StarredChangesUtil.java b/java/com/google/gerrit/httpd/raw/package-info.java
similarity index 64%
copy from java/com/google/gerrit/server/StarredChangesUtil.java
copy to java/com/google/gerrit/httpd/raw/package-info.java
index 0709b86..6505062 100644
--- a/java/com/google/gerrit/server/StarredChangesUtil.java
+++ b/java/com/google/gerrit/httpd/raw/package-info.java
@@ -1,4 +1,4 @@
-// Copyright (C) 2015 The Android Open Source Project
+// Copyright (C) 2023 The Android Open Source Project
 //
 // Licensed under the Apache License, Version 2.0 (the "License");
 // you may not use this file except in compliance with the License.
@@ -12,8 +12,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package com.google.gerrit.server;
+@CheckReturnValue
+package com.google.gerrit.httpd.raw;
 
-// TODO - delete this class. It cannot be deleted as part of the original refactoring since it's
-// used by a plugin.
-public interface StarredChangesUtil extends StarredChangesReader, StarredChangesWriter {}
+import com.google.errorprone.annotations.CheckReturnValue;
diff --git a/java/com/google/gerrit/server/StarredChangesUtil.java b/java/com/google/gerrit/httpd/resources/package-info.java
similarity index 64%
copy from java/com/google/gerrit/server/StarredChangesUtil.java
copy to java/com/google/gerrit/httpd/resources/package-info.java
index 0709b86..94d35f6 100644
--- a/java/com/google/gerrit/server/StarredChangesUtil.java
+++ b/java/com/google/gerrit/httpd/resources/package-info.java
@@ -1,4 +1,4 @@
-// Copyright (C) 2015 The Android Open Source Project
+// Copyright (C) 2023 The Android Open Source Project
 //
 // Licensed under the Apache License, Version 2.0 (the "License");
 // you may not use this file except in compliance with the License.
@@ -12,8 +12,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package com.google.gerrit.server;
+@CheckReturnValue
+package com.google.gerrit.httpd.resources;
 
-// TODO - delete this class. It cannot be deleted as part of the original refactoring since it's
-// used by a plugin.
-public interface StarredChangesUtil extends StarredChangesReader, StarredChangesWriter {}
+import com.google.errorprone.annotations.CheckReturnValue;
diff --git a/java/com/google/gerrit/httpd/restapi/RestApiServlet.java b/java/com/google/gerrit/httpd/restapi/RestApiServlet.java
index 9b53c17..6951398 100644
--- a/java/com/google/gerrit/httpd/restapi/RestApiServlet.java
+++ b/java/com/google/gerrit/httpd/restapi/RestApiServlet.java
@@ -51,6 +51,7 @@
 import com.google.common.io.CountingOutputStream;
 import com.google.common.math.IntMath;
 import com.google.common.net.HttpHeaders;
+import com.google.errorprone.annotations.CanIgnoreReturnValue;
 import com.google.gerrit.common.Nullable;
 import com.google.gerrit.common.RawInputUtil;
 import com.google.gerrit.entities.Project;
@@ -520,6 +521,7 @@
                       (RestReadView<RestResource>) viewData.view,
                       rsrc);
             } else if (viewData.view instanceof RestModifyView<?, ?>) {
+              @SuppressWarnings("unchecked")
               RestModifyView<RestResource, Object> m =
                   (RestModifyView<RestResource, Object>) viewData.view;
 
@@ -535,6 +537,7 @@
                 }
               }
             } else if (viewData.view instanceof RestCollectionCreateView<?, ?, ?>) {
+              @SuppressWarnings("unchecked")
               RestCollectionCreateView<RestResource, RestResource, Object> m =
                   (RestCollectionCreateView<RestResource, RestResource, Object>) viewData.view;
 
@@ -549,6 +552,7 @@
                 }
               }
             } else if (viewData.view instanceof RestCollectionDeleteMissingView<?, ?, ?>) {
+              @SuppressWarnings("unchecked")
               RestCollectionDeleteMissingView<RestResource, RestResource, Object> m =
                   (RestCollectionDeleteMissingView<RestResource, RestResource, Object>)
                       viewData.view;
@@ -564,6 +568,7 @@
                 }
               }
             } else if (viewData.view instanceof RestCollectionModifyView<?, ?, ?>) {
+              @SuppressWarnings("unchecked")
               RestCollectionModifyView<RestResource, RestResource, Object> m =
                   (RestCollectionModifyView<RestResource, RestResource, Object>) viewData.view;
 
@@ -1306,6 +1311,7 @@
    * @param result the object that should be formatted as JSON
    * @return the length of the response
    */
+  @CanIgnoreReturnValue
   public static long replyJson(
       @Nullable HttpServletRequest req,
       HttpServletResponse res,
@@ -1397,6 +1403,7 @@
   }
 
   @SuppressWarnings("resource")
+  @CanIgnoreReturnValue
   static long replyBinaryResult(
       @Nullable HttpServletRequest req, HttpServletResponse res, BinaryResult bin)
       throws IOException {
@@ -1698,9 +1705,21 @@
     if (rootCollection instanceof ProjectsCollection) {
       requestInfo.project(Project.nameKey(resourceId));
     } else if (rootCollection instanceof ChangesCollection) {
-      Optional<ChangeNotes> changeNotes = globals.changeFinder.findOne(resourceId);
-      if (changeNotes.isPresent()) {
-        requestInfo.project(changeNotes.get().getProjectName());
+      try {
+        Optional<ChangeNotes> changeNotes =
+            globals
+                .retryHelper
+                .action(
+                    ActionType.INDEX_QUERY,
+                    "find-change",
+                    () -> globals.changeFinder.findOne(resourceId))
+                .call();
+        if (changeNotes.isPresent()) {
+          requestInfo.project(changeNotes.get().getProjectName());
+        }
+      } catch (Exception e) {
+        logger.atWarning().withCause(e).log(
+            "failed looking up change %s to populate project in request info", resourceId);
       }
     }
     return requestInfo.build();
@@ -1828,6 +1847,7 @@
     return uri;
   }
 
+  @CanIgnoreReturnValue
   public static long replyError(
       HttpServletRequest req,
       HttpServletResponse res,
@@ -1838,6 +1858,7 @@
     return replyError(req, res, statusCode, msg, CacheControl.NONE, err);
   }
 
+  @CanIgnoreReturnValue
   public static long replyError(
       HttpServletRequest req,
       HttpServletResponse res,
@@ -1866,6 +1887,7 @@
    * @param text the text reply
    * @return the length of the response
    */
+  @CanIgnoreReturnValue
   static long replyText(
       @Nullable HttpServletRequest req, HttpServletResponse res, boolean allowTracing, String text)
       throws IOException {
diff --git a/java/com/google/gerrit/server/StarredChangesUtil.java b/java/com/google/gerrit/httpd/restapi/package-info.java
similarity index 64%
copy from java/com/google/gerrit/server/StarredChangesUtil.java
copy to java/com/google/gerrit/httpd/restapi/package-info.java
index 0709b86..b38188f 100644
--- a/java/com/google/gerrit/server/StarredChangesUtil.java
+++ b/java/com/google/gerrit/httpd/restapi/package-info.java
@@ -1,4 +1,4 @@
-// Copyright (C) 2015 The Android Open Source Project
+// Copyright (C) 2023 The Android Open Source Project
 //
 // Licensed under the Apache License, Version 2.0 (the "License");
 // you may not use this file except in compliance with the License.
@@ -12,8 +12,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package com.google.gerrit.server;
+@CheckReturnValue
+package com.google.gerrit.httpd.restapi;
 
-// TODO - delete this class. It cannot be deleted as part of the original refactoring since it's
-// used by a plugin.
-public interface StarredChangesUtil extends StarredChangesReader, StarredChangesWriter {}
+import com.google.errorprone.annotations.CheckReturnValue;
diff --git a/java/com/google/gerrit/server/StarredChangesUtil.java b/java/com/google/gerrit/httpd/template/package-info.java
similarity index 64%
copy from java/com/google/gerrit/server/StarredChangesUtil.java
copy to java/com/google/gerrit/httpd/template/package-info.java
index 0709b86..374cc91 100644
--- a/java/com/google/gerrit/server/StarredChangesUtil.java
+++ b/java/com/google/gerrit/httpd/template/package-info.java
@@ -1,4 +1,4 @@
-// Copyright (C) 2015 The Android Open Source Project
+// Copyright (C) 2023 The Android Open Source Project
 //
 // Licensed under the Apache License, Version 2.0 (the "License");
 // you may not use this file except in compliance with the License.
@@ -12,8 +12,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package com.google.gerrit.server;
+@CheckReturnValue
+package com.google.gerrit.httpd.template;
 
-// TODO - delete this class. It cannot be deleted as part of the original refactoring since it's
-// used by a plugin.
-public interface StarredChangesUtil extends StarredChangesReader, StarredChangesWriter {}
+import com.google.errorprone.annotations.CheckReturnValue;
diff --git a/java/com/google/gerrit/index/Index.java b/java/com/google/gerrit/index/Index.java
index 3ed76ba..ec530c1 100644
--- a/java/com/google/gerrit/index/Index.java
+++ b/java/com/google/gerrit/index/Index.java
@@ -22,6 +22,7 @@
 import com.google.gerrit.index.query.Matchable;
 import com.google.gerrit.index.query.Predicate;
 import com.google.gerrit.index.query.QueryParseException;
+import java.io.IOException;
 import java.util.Optional;
 
 /**
@@ -169,4 +170,15 @@
   default Optional<Matchable<V>> getIndexFilter() {
     return Optional.empty();
   }
+
+  /**
+   * Creates a snapshot of the index.
+   *
+   * @param id an ID used for the snapshot.
+   * @return {@code true} if the snapshot was successful.
+   * @throws IOException if writing the snapshot to disk fails.
+   */
+  default boolean snapshot(String id) throws IOException {
+    return false;
+  }
 }
diff --git a/java/com/google/gerrit/index/IndexCollection.java b/java/com/google/gerrit/index/IndexCollection.java
index c61e6c7..66a9fba 100644
--- a/java/com/google/gerrit/index/IndexCollection.java
+++ b/java/com/google/gerrit/index/IndexCollection.java
@@ -16,6 +16,7 @@
 
 import com.google.common.annotations.VisibleForTesting;
 import com.google.common.collect.Lists;
+import com.google.errorprone.annotations.CanIgnoreReturnValue;
 import com.google.gerrit.extensions.events.LifecycleListener;
 import java.util.Collection;
 import java.util.Collections;
@@ -53,6 +54,7 @@
     return Collections.unmodifiableCollection(writeIndexes);
   }
 
+  @CanIgnoreReturnValue
   public synchronized I addWriteIndex(I index) {
     int version = index.getSchema().getVersion();
     for (int i = 0; i < writeIndexes.size(); i++) {
diff --git a/java/com/google/gerrit/index/IndexedField.java b/java/com/google/gerrit/index/IndexedField.java
index 94943d6..d1aaff6 100644
--- a/java/com/google/gerrit/index/IndexedField.java
+++ b/java/com/google/gerrit/index/IndexedField.java
@@ -23,6 +23,7 @@
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableMap;
 import com.google.common.reflect.TypeToken;
+import com.google.errorprone.annotations.CanIgnoreReturnValue;
 import com.google.gerrit.common.Nullable;
 import com.google.gerrit.entities.converter.ProtoConverter;
 import com.google.gerrit.exceptions.StorageException;
@@ -211,6 +212,7 @@
       return IndexedField.this;
     }
 
+    @CanIgnoreReturnValue
     private String checkName(String name) {
       CharMatcher m = CharMatcher.anyOf("abcdefghijklmnopqrstuvwxyz0123456789_");
       checkArgument(name != null && m.matchesAllOf(name), "illegal field name: %s", name);
@@ -350,6 +352,7 @@
       return this.getter(getter).fieldSetter(Optional.empty()).build();
     }
 
+    @CanIgnoreReturnValue
     private static String checkName(String name) {
       String allowedCharacters = "abcdefghijklmnopqrstuvwxyz0123456789_";
       CharMatcher m =
diff --git a/java/com/google/gerrit/index/Schema.java b/java/com/google/gerrit/index/Schema.java
index ab10d9e..dcd7591 100644
--- a/java/com/google/gerrit/index/Schema.java
+++ b/java/com/google/gerrit/index/Schema.java
@@ -26,6 +26,7 @@
 import com.google.common.collect.ImmutableSet;
 import com.google.common.collect.Sets;
 import com.google.common.flogger.FluentLogger;
+import com.google.errorprone.annotations.CanIgnoreReturnValue;
 import com.google.gerrit.common.Nullable;
 import com.google.gerrit.exceptions.StorageException;
 import com.google.gerrit.index.SchemaFieldDefs.SchemaField;
@@ -48,11 +49,13 @@
 
     private Optional<Integer> version = Optional.empty();
 
+    @CanIgnoreReturnValue
     public Builder<T> version(int version) {
       this.version = Optional.of(version);
       return this;
     }
 
+    @CanIgnoreReturnValue
     public Builder<T> add(Schema<T> schema) {
       this.indexedFields.addAll(schema.getIndexFields().values());
       this.searchFields.addAll(schema.getSchemaFields().values());
@@ -63,10 +66,12 @@
     }
 
     @SafeVarargs
+    @CanIgnoreReturnValue
     public final Builder<T> addSearchSpecs(IndexedField<T, ?>.SearchSpec... searchSpecs) {
       return addSearchSpecs(ImmutableList.copyOf(searchSpecs));
     }
 
+    @CanIgnoreReturnValue
     public Builder<T> addSearchSpecs(ImmutableList<IndexedField<T, ?>.SearchSpec> searchSpecs) {
       for (IndexedField<T, ?>.SearchSpec searchSpec : searchSpecs) {
         checkArgument(
@@ -80,22 +85,26 @@
     }
 
     @SafeVarargs
+    @CanIgnoreReturnValue
     public final Builder<T> addIndexedFields(IndexedField<T, ?>... fields) {
       return addIndexedFields(ImmutableList.copyOf(fields));
     }
 
+    @CanIgnoreReturnValue
     public Builder<T> addIndexedFields(ImmutableList<IndexedField<T, ?>> indexedFields) {
       this.indexedFields.addAll(indexedFields);
       return this;
     }
 
     @SafeVarargs
+    @CanIgnoreReturnValue
     public final Builder<T> remove(IndexedField<T, ?>.SearchSpec... searchSpecs) {
       this.searchFields.removeAll(Arrays.asList(searchSpecs));
       return this;
     }
 
     @SafeVarargs
+    @CanIgnoreReturnValue
     public final Builder<T> remove(IndexedField<T, ?>... indexedFields) {
       for (IndexedField<T, ?> field : indexedFields) {
         ImmutableMap<String, ? extends IndexedField<T, ?>.SearchSpec> searchSpecs =
@@ -134,6 +143,7 @@
     }
   }
 
+  @CanIgnoreReturnValue
   private static <T> SchemaField<T, ?> checkSame(SchemaField<T, ?> f1, SchemaField<T, ?> f2) {
     checkState(f1 == f2, "Mismatched %s fields: %s != %s", f1.getName(), f1, f2);
     return f1;
diff --git a/java/com/google/gerrit/server/StarredChangesUtil.java b/java/com/google/gerrit/index/package-info.java
similarity index 64%
copy from java/com/google/gerrit/server/StarredChangesUtil.java
copy to java/com/google/gerrit/index/package-info.java
index 0709b86..9c652fe 100644
--- a/java/com/google/gerrit/server/StarredChangesUtil.java
+++ b/java/com/google/gerrit/index/package-info.java
@@ -1,4 +1,4 @@
-// Copyright (C) 2015 The Android Open Source Project
+// Copyright (C) 2023 The Android Open Source Project
 //
 // Licensed under the Apache License, Version 2.0 (the "License");
 // you may not use this file except in compliance with the License.
@@ -12,8 +12,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package com.google.gerrit.server;
+@CheckReturnValue
+package com.google.gerrit.index;
 
-// TODO - delete this class. It cannot be deleted as part of the original refactoring since it's
-// used by a plugin.
-public interface StarredChangesUtil extends StarredChangesReader, StarredChangesWriter {}
+import com.google.errorprone.annotations.CheckReturnValue;
diff --git a/java/com/google/gerrit/index/project/BUILD b/java/com/google/gerrit/index/project/BUILD
index b423f84..b029513 100644
--- a/java/com/google/gerrit/index/project/BUILD
+++ b/java/com/google/gerrit/index/project/BUILD
@@ -9,6 +9,7 @@
         "//java/com/google/gerrit/index",
         "//java/com/google/gerrit/index:query_exception",
         "//lib:guava",
+        "//lib/errorprone:annotations",
         "//lib/guice",
     ],
 )
diff --git a/java/com/google/gerrit/index/project/ProjectSchemaDefinitions.java b/java/com/google/gerrit/index/project/ProjectSchemaDefinitions.java
index 6cd43db..3bccb0d 100644
--- a/java/com/google/gerrit/index/project/ProjectSchemaDefinitions.java
+++ b/java/com/google/gerrit/index/project/ProjectSchemaDefinitions.java
@@ -81,6 +81,9 @@
           .addSearchSpecs(ProjectField.PREFIX_NAME_SPEC)
           .build();
 
+  // Upgrade Lucene to 9.x requires reindexing.
+  static final Schema<ProjectData> V9 = schema(V8);
+
   /**
    * Name of the project index to be used when contacting index backends or loading configurations.
    */
diff --git a/java/com/google/gerrit/server/StarredChangesUtil.java b/java/com/google/gerrit/index/project/package-info.java
similarity index 64%
copy from java/com/google/gerrit/server/StarredChangesUtil.java
copy to java/com/google/gerrit/index/project/package-info.java
index 0709b86..8965ed5 100644
--- a/java/com/google/gerrit/server/StarredChangesUtil.java
+++ b/java/com/google/gerrit/index/project/package-info.java
@@ -1,4 +1,4 @@
-// Copyright (C) 2015 The Android Open Source Project
+// Copyright (C) 2023 The Android Open Source Project
 //
 // Licensed under the Apache License, Version 2.0 (the "License");
 // you may not use this file except in compliance with the License.
@@ -12,8 +12,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package com.google.gerrit.server;
+@CheckReturnValue
+package com.google.gerrit.index.project;
 
-// TODO - delete this class. It cannot be deleted as part of the original refactoring since it's
-// used by a plugin.
-public interface StarredChangesUtil extends StarredChangesReader, StarredChangesWriter {}
+import com.google.errorprone.annotations.CheckReturnValue;
diff --git a/java/com/google/gerrit/index/query/FilteredSource.java b/java/com/google/gerrit/index/query/FilteredSource.java
index fb31eb6..9746850 100644
--- a/java/com/google/gerrit/index/query/FilteredSource.java
+++ b/java/com/google/gerrit/index/query/FilteredSource.java
@@ -84,6 +84,11 @@
     return cardinality;
   }
 
+  /**
+   * Whether this data source matches with the given object.
+   *
+   * @param object object to be matched
+   */
   protected boolean match(T object) {
     return true;
   }
diff --git a/java/com/google/gerrit/index/query/InternalQuery.java b/java/com/google/gerrit/index/query/InternalQuery.java
index d610dbf..24ce0fe 100644
--- a/java/com/google/gerrit/index/query/InternalQuery.java
+++ b/java/com/google/gerrit/index/query/InternalQuery.java
@@ -20,6 +20,7 @@
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableSet;
 import com.google.common.collect.Lists;
+import com.google.errorprone.annotations.CanIgnoreReturnValue;
 import com.google.gerrit.common.Nullable;
 import com.google.gerrit.exceptions.StorageException;
 import com.google.gerrit.index.Index;
@@ -61,22 +62,26 @@
     return (Q) this;
   }
 
+  @CanIgnoreReturnValue
   final Q setStart(int start) {
     queryProcessor.setStart(start);
     return self();
   }
 
+  @CanIgnoreReturnValue
   public final Q setLimit(int n) {
-    queryProcessor.setUserProvidedLimit(n);
+    queryProcessor.setUserProvidedLimit(n, /* applyDefaultLimit */ false);
     return self();
   }
 
+  @CanIgnoreReturnValue
   public final Q enforceVisibility(boolean enforce) {
     queryProcessor.enforceVisibility(enforce);
     return self();
   }
 
   @SafeVarargs
+  @CanIgnoreReturnValue
   public final Q setRequestedFields(SchemaField<T, ?>... fields) {
     checkArgument(fields.length > 0, "requested field list is empty");
     queryProcessor.setRequestedFields(
@@ -84,6 +89,7 @@
     return self();
   }
 
+  @CanIgnoreReturnValue
   public final Q noFields() {
     queryProcessor.setRequestedFields(ImmutableSet.of());
     return self();
diff --git a/java/com/google/gerrit/index/query/QueryProcessor.java b/java/com/google/gerrit/index/query/QueryProcessor.java
index f49cecb..b8eb8bb 100644
--- a/java/com/google/gerrit/index/query/QueryProcessor.java
+++ b/java/com/google/gerrit/index/query/QueryProcessor.java
@@ -122,6 +122,7 @@
             .build();
   }
 
+  @CanIgnoreReturnValue
   public QueryProcessor<T> setStart(int n) {
     start = n;
     return this;
@@ -141,11 +142,18 @@
    * @param enforce whether to enforce visibility.
    * @return this.
    */
+  @CanIgnoreReturnValue
   public QueryProcessor<T> enforceVisibility(boolean enforce) {
     enforceVisibility = enforce;
     return this;
   }
 
+  /** Convenience method for API backward compatibility. */
+  @CanIgnoreReturnValue
+  public QueryProcessor<T> setUserProvidedLimit(int n) {
+    return setUserProvidedLimit(n, true);
+  }
+
   /**
    * Set an end-user-provided limit on the number of results returned.
    *
@@ -154,13 +162,20 @@
    * account and choose the one that makes the most sense.
    *
    * @param n limit; zero or negative means no limit.
+   * @param applyDefaultLimit Should the default limit be applied, if n <= 0? For internal queries
+   *     this should be false. For API endpoints this should be true.
    * @return this.
    */
-  public QueryProcessor<T> setUserProvidedLimit(int n) {
+  @CanIgnoreReturnValue
+  public QueryProcessor<T> setUserProvidedLimit(int n, boolean applyDefaultLimit) {
     userProvidedLimit = n;
+    if (applyDefaultLimit && userProvidedLimit <= 0 && indexConfig.defaultLimit() > 0) {
+      userProvidedLimit = indexConfig.defaultLimit();
+    }
     return this;
   }
 
+  @CanIgnoreReturnValue
   public QueryProcessor<T> setNoLimit(boolean isNoLimit) {
     this.isNoLimit = isNoLimit;
     return this;
@@ -172,6 +187,7 @@
     return this;
   }
 
+  @CanIgnoreReturnValue
   public QueryProcessor<T> setRequestedFields(Set<String> fields) {
     requestedFields = fields;
     return this;
@@ -433,8 +449,6 @@
     possibleLimits.add(getPermittedLimit());
     if (userProvidedLimit > 0) {
       possibleLimits.add(userProvidedLimit);
-    } else if (indexConfig.defaultLimit() > 0) {
-      possibleLimits.add(indexConfig.defaultLimit());
     }
     if (limitField != null) {
       Integer limitFromPredicate = LimitPredicate.getLimit(limitField, p);
@@ -465,8 +479,4 @@
   }
 
   protected abstract String formatForLogging(T t);
-
-  protected abstract int getIndexSize();
-
-  protected abstract int getBatchSize();
 }
diff --git a/java/com/google/gerrit/server/StarredChangesUtil.java b/java/com/google/gerrit/index/query/package-info.java
similarity index 64%
copy from java/com/google/gerrit/server/StarredChangesUtil.java
copy to java/com/google/gerrit/index/query/package-info.java
index 0709b86..ab99f47 100644
--- a/java/com/google/gerrit/server/StarredChangesUtil.java
+++ b/java/com/google/gerrit/index/query/package-info.java
@@ -1,4 +1,4 @@
-// Copyright (C) 2015 The Android Open Source Project
+// Copyright (C) 2023 The Android Open Source Project
 //
 // Licensed under the Apache License, Version 2.0 (the "License");
 // you may not use this file except in compliance with the License.
@@ -12,8 +12,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package com.google.gerrit.server;
+@CheckReturnValue
+package com.google.gerrit.index.query;
 
-// TODO - delete this class. It cannot be deleted as part of the original refactoring since it's
-// used by a plugin.
-public interface StarredChangesUtil extends StarredChangesReader, StarredChangesWriter {}
+import com.google.errorprone.annotations.CheckReturnValue;
diff --git a/java/com/google/gerrit/index/query/testing/BUILD b/java/com/google/gerrit/index/query/testing/BUILD
index 1785f49..1e61988 100644
--- a/java/com/google/gerrit/index/query/testing/BUILD
+++ b/java/com/google/gerrit/index/query/testing/BUILD
@@ -12,6 +12,7 @@
         "//antlr3:query_parser",
         "//lib:guava",
         "//lib/antlr:java-runtime",
+        "//lib/errorprone:annotations",
         "//lib/truth",
     ],
 )
diff --git a/java/com/google/gerrit/server/StarredChangesUtil.java b/java/com/google/gerrit/index/query/testing/package-info.java
similarity index 64%
copy from java/com/google/gerrit/server/StarredChangesUtil.java
copy to java/com/google/gerrit/index/query/testing/package-info.java
index 0709b86..20604ad 100644
--- a/java/com/google/gerrit/server/StarredChangesUtil.java
+++ b/java/com/google/gerrit/index/query/testing/package-info.java
@@ -1,4 +1,4 @@
-// Copyright (C) 2015 The Android Open Source Project
+// Copyright (C) 2023 The Android Open Source Project
 //
 // Licensed under the Apache License, Version 2.0 (the "License");
 // you may not use this file except in compliance with the License.
@@ -12,8 +12,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package com.google.gerrit.server;
+@CheckReturnValue
+package com.google.gerrit.index.query.testing;
 
-// TODO - delete this class. It cannot be deleted as part of the original refactoring since it's
-// used by a plugin.
-public interface StarredChangesUtil extends StarredChangesReader, StarredChangesWriter {}
+import com.google.errorprone.annotations.CheckReturnValue;
diff --git a/java/com/google/gerrit/index/testing/AbstractFakeIndex.java b/java/com/google/gerrit/index/testing/AbstractFakeIndex.java
index 86d062b2..65c02d9 100644
--- a/java/com/google/gerrit/index/testing/AbstractFakeIndex.java
+++ b/java/com/google/gerrit/index/testing/AbstractFakeIndex.java
@@ -135,7 +135,7 @@
 
   @Override
   public DataSource<V> getSource(Predicate<V> p, QueryOptions opts) {
-    List<V> results;
+    ImmutableList<V> results;
     synchronized (indexedDocuments) {
       Stream<V> valueStream =
           indexedDocuments.values().stream()
@@ -288,7 +288,10 @@
                   Integer.valueOf((String) doc.get(ChangeField.NUMERIC_ID_STR_SPEC.getName()))));
       for (SchemaField<ChangeData, ?> field : getSchema().getSchemaFields().values()) {
         boolean isProtoField = SchemaFieldDefs.isProtoField(field);
-        field.setIfPossible(cd, new FakeStoredValue(doc.get(field.getName()), isProtoField));
+
+        @SuppressWarnings("unused")
+        var unused =
+            field.setIfPossible(cd, new FakeStoredValue(doc.get(field.getName()), isProtoField));
       }
       return cd;
     }
diff --git a/java/com/google/gerrit/index/testing/BUILD b/java/com/google/gerrit/index/testing/BUILD
index 44bf70d..c9f1994 100644
--- a/java/com/google/gerrit/index/testing/BUILD
+++ b/java/com/google/gerrit/index/testing/BUILD
@@ -16,6 +16,7 @@
         "//lib:guava",
         "//lib:jgit",
         "//lib:protobuf",
+        "//lib/errorprone:annotations",
         "//lib/flogger:api",
         "//lib/guice",
         "//lib/guice:guice-assistedinject",
diff --git a/java/com/google/gerrit/index/testing/FakeIndexModule.java b/java/com/google/gerrit/index/testing/FakeIndexModule.java
index 126ff10..b4d5315 100644
--- a/java/com/google/gerrit/index/testing/FakeIndexModule.java
+++ b/java/com/google/gerrit/index/testing/FakeIndexModule.java
@@ -21,7 +21,6 @@
 import com.google.gerrit.server.index.account.AccountIndex;
 import com.google.gerrit.server.index.change.ChangeIndex;
 import com.google.gerrit.server.index.group.GroupIndex;
-import java.util.Map;
 
 /** Module to bind {@link FakeIndexModule}. */
 public class FakeIndexModule extends AbstractIndexModule {
@@ -30,15 +29,16 @@
   }
 
   public static FakeIndexModule singleVersionWithExplicitVersions(
-      Map<String, Integer> versions, int threads, boolean secondary) {
+      ImmutableMap<String, Integer> versions, int threads, boolean secondary) {
     return new FakeIndexModule(versions, threads, secondary);
   }
 
   public static FakeIndexModule latestVersion(boolean secondary) {
-    return new FakeIndexModule(null, -1 /* direct executor */, secondary);
+    return new FakeIndexModule(/* singleVersions= */ null, -1 /* direct executor */, secondary);
   }
 
-  private FakeIndexModule(Map<String, Integer> singleVersions, int threads, boolean secondary) {
+  private FakeIndexModule(
+      ImmutableMap<String, Integer> singleVersions, int threads, boolean secondary) {
     super(singleVersions, threads, secondary);
   }
 
diff --git a/java/com/google/gerrit/server/StarredChangesUtil.java b/java/com/google/gerrit/index/testing/package-info.java
similarity index 64%
copy from java/com/google/gerrit/server/StarredChangesUtil.java
copy to java/com/google/gerrit/index/testing/package-info.java
index 0709b86..0f6e5b2 100644
--- a/java/com/google/gerrit/server/StarredChangesUtil.java
+++ b/java/com/google/gerrit/index/testing/package-info.java
@@ -1,4 +1,4 @@
-// Copyright (C) 2015 The Android Open Source Project
+// Copyright (C) 2023 The Android Open Source Project
 //
 // Licensed under the Apache License, Version 2.0 (the "License");
 // you may not use this file except in compliance with the License.
@@ -12,8 +12,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package com.google.gerrit.server;
+@CheckReturnValue
+package com.google.gerrit.index.testing;
 
-// TODO - delete this class. It cannot be deleted as part of the original refactoring since it's
-// used by a plugin.
-public interface StarredChangesUtil extends StarredChangesReader, StarredChangesWriter {}
+import com.google.errorprone.annotations.CheckReturnValue;
diff --git a/java/com/google/gerrit/jgit/BUILD b/java/com/google/gerrit/jgit/BUILD
index 1041f1f..04f2220 100644
--- a/java/com/google/gerrit/jgit/BUILD
+++ b/java/com/google/gerrit/jgit/BUILD
@@ -9,5 +9,6 @@
     deps = [
         "//lib:gson",
         "//lib:jgit",
+        "//lib/errorprone:annotations",
     ],
 )
diff --git a/java/com/google/gerrit/server/StarredChangesUtil.java b/java/com/google/gerrit/jgit/diff/package-info.java
similarity index 64%
copy from java/com/google/gerrit/server/StarredChangesUtil.java
copy to java/com/google/gerrit/jgit/diff/package-info.java
index 0709b86..ba04b82 100644
--- a/java/com/google/gerrit/server/StarredChangesUtil.java
+++ b/java/com/google/gerrit/jgit/diff/package-info.java
@@ -1,4 +1,4 @@
-// Copyright (C) 2015 The Android Open Source Project
+// Copyright (C) 2023 The Android Open Source Project
 //
 // Licensed under the Apache License, Version 2.0 (the "License");
 // you may not use this file except in compliance with the License.
@@ -12,8 +12,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package com.google.gerrit.server;
+@CheckReturnValue
+package com.google.gerrit.jgit.diff;
 
-// TODO - delete this class. It cannot be deleted as part of the original refactoring since it's
-// used by a plugin.
-public interface StarredChangesUtil extends StarredChangesReader, StarredChangesWriter {}
+import com.google.errorprone.annotations.CheckReturnValue;
diff --git a/java/com/google/gerrit/json/BUILD b/java/com/google/gerrit/json/BUILD
index 7b2fe2f..601a7b4 100644
--- a/java/com/google/gerrit/json/BUILD
+++ b/java/com/google/gerrit/json/BUILD
@@ -9,6 +9,7 @@
         "//java/com/google/gerrit/entities",
         "//lib:gson",
         "//lib:guava",
+        "//lib/errorprone:annotations",
         "//lib/guice",
     ],
 )
diff --git a/java/com/google/gerrit/server/StarredChangesUtil.java b/java/com/google/gerrit/json/package-info.java
similarity index 64%
copy from java/com/google/gerrit/server/StarredChangesUtil.java
copy to java/com/google/gerrit/json/package-info.java
index 0709b86..000b667 100644
--- a/java/com/google/gerrit/server/StarredChangesUtil.java
+++ b/java/com/google/gerrit/json/package-info.java
@@ -1,4 +1,4 @@
-// Copyright (C) 2015 The Android Open Source Project
+// Copyright (C) 2023 The Android Open Source Project
 //
 // Licensed under the Apache License, Version 2.0 (the "License");
 // you may not use this file except in compliance with the License.
@@ -12,8 +12,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package com.google.gerrit.server;
+@CheckReturnValue
+package com.google.gerrit.json;
 
-// TODO - delete this class. It cannot be deleted as part of the original refactoring since it's
-// used by a plugin.
-public interface StarredChangesUtil extends StarredChangesReader, StarredChangesWriter {}
+import com.google.errorprone.annotations.CheckReturnValue;
diff --git a/java/com/google/gerrit/launcher/BUILD b/java/com/google/gerrit/launcher/BUILD
index 15fa0ce..309d1dd 100644
--- a/java/com/google/gerrit/launcher/BUILD
+++ b/java/com/google/gerrit/launcher/BUILD
@@ -6,4 +6,7 @@
     name = "launcher",
     srcs = ["GerritLauncher.java"],
     visibility = ["//visibility:public"],
+    deps = [
+        "//lib/errorprone:annotations",
+    ],
 )
diff --git a/java/com/google/gerrit/launcher/GerritLauncher.java b/java/com/google/gerrit/launcher/GerritLauncher.java
index 07a071a..989c82b 100644
--- a/java/com/google/gerrit/launcher/GerritLauncher.java
+++ b/java/com/google/gerrit/launcher/GerritLauncher.java
@@ -36,7 +36,6 @@
 import java.nio.file.Files;
 import java.nio.file.NoSuchFileException;
 import java.nio.file.Path;
-import java.nio.file.Paths;
 import java.security.CodeSource;
 import java.util.ArrayList;
 import java.util.Arrays;
@@ -706,7 +705,7 @@
     Path dir;
     String sourceRoot = System.getProperty("sourceRoot");
     if (sourceRoot != null) {
-      dir = Paths.get(sourceRoot);
+      dir = Path.of(sourceRoot);
       if (!Files.exists(dir)) {
         throw new FileNotFoundException("source root not found: " + dir);
       }
@@ -729,7 +728,7 @@
       }
 
       // Pop up to the top-level source folder by looking for WORKSPACE.
-      dir = Paths.get(u.getPath());
+      dir = Path.of(u.getPath());
       while (!Files.isRegularFile(dir.resolve("WORKSPACE"))) {
         Path parent = dir.getParent();
         if (parent == null) {
@@ -756,7 +755,7 @@
       Path rootPath = resolveInSourceRoot(".").normalize();
 
       Properties properties = loadBuildProperties(rootPath.resolve(".bazel_path"));
-      Path outputBase = Paths.get(properties.getProperty("output_base"));
+      Path outputBase = Path.of(properties.getProperty("output_base"));
 
       Path runtimeClasspath =
           rootPath.resolve("bazel-bin/tools/eclipse/main_classpath_collect.runtime_classpath");
diff --git a/java/com/google/gerrit/server/StarredChangesUtil.java b/java/com/google/gerrit/launcher/package-info.java
similarity index 64%
copy from java/com/google/gerrit/server/StarredChangesUtil.java
copy to java/com/google/gerrit/launcher/package-info.java
index 0709b86..b30810c 100644
--- a/java/com/google/gerrit/server/StarredChangesUtil.java
+++ b/java/com/google/gerrit/launcher/package-info.java
@@ -1,4 +1,4 @@
-// Copyright (C) 2015 The Android Open Source Project
+// Copyright (C) 2023 The Android Open Source Project
 //
 // Licensed under the Apache License, Version 2.0 (the "License");
 // you may not use this file except in compliance with the License.
@@ -12,8 +12,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package com.google.gerrit.server;
+@CheckReturnValue
+package com.google.gerrit.launcher;
 
-// TODO - delete this class. It cannot be deleted as part of the original refactoring since it's
-// used by a plugin.
-public interface StarredChangesUtil extends StarredChangesReader, StarredChangesWriter {}
+import com.google.errorprone.annotations.CheckReturnValue;
diff --git a/java/com/google/gerrit/lifecycle/BUILD b/java/com/google/gerrit/lifecycle/BUILD
index a3f3d81..fa3c2a3 100644
--- a/java/com/google/gerrit/lifecycle/BUILD
+++ b/java/com/google/gerrit/lifecycle/BUILD
@@ -7,6 +7,7 @@
     deps = [
         "//java/com/google/gerrit/extensions:api",
         "//lib:guava",
+        "//lib/errorprone:annotations",
         "//lib/flogger:api",
         "//lib/guice",
     ],
diff --git a/java/com/google/gerrit/server/StarredChangesUtil.java b/java/com/google/gerrit/lifecycle/package-info.java
similarity index 64%
copy from java/com/google/gerrit/server/StarredChangesUtil.java
copy to java/com/google/gerrit/lifecycle/package-info.java
index 0709b86..753cc8b 100644
--- a/java/com/google/gerrit/server/StarredChangesUtil.java
+++ b/java/com/google/gerrit/lifecycle/package-info.java
@@ -1,4 +1,4 @@
-// Copyright (C) 2015 The Android Open Source Project
+// Copyright (C) 2023 The Android Open Source Project
 //
 // Licensed under the Apache License, Version 2.0 (the "License");
 // you may not use this file except in compliance with the License.
@@ -12,8 +12,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package com.google.gerrit.server;
+@CheckReturnValue
+package com.google.gerrit.lifecycle;
 
-// TODO - delete this class. It cannot be deleted as part of the original refactoring since it's
-// used by a plugin.
-public interface StarredChangesUtil extends StarredChangesReader, StarredChangesWriter {}
+import com.google.errorprone.annotations.CheckReturnValue;
diff --git a/java/com/google/gerrit/lucene/AbstractLuceneIndex.java b/java/com/google/gerrit/lucene/AbstractLuceneIndex.java
index 938cd67..e00c394 100644
--- a/java/com/google/gerrit/lucene/AbstractLuceneIndex.java
+++ b/java/com/google/gerrit/lucene/AbstractLuceneIndex.java
@@ -25,6 +25,7 @@
 import com.google.common.collect.ListMultimap;
 import com.google.common.collect.Sets;
 import com.google.common.flogger.FluentLogger;
+import com.google.common.io.Files;
 import com.google.common.primitives.Ints;
 import com.google.common.util.concurrent.AbstractFuture;
 import com.google.common.util.concurrent.Futures;
@@ -54,6 +55,8 @@
 import com.google.gerrit.server.logging.LoggingContextAwareScheduledExecutorService;
 import com.google.protobuf.MessageLite;
 import java.io.IOException;
+import java.nio.file.FileAlreadyExistsException;
+import java.nio.file.Path;
 import java.sql.Timestamp;
 import java.util.Set;
 import java.util.concurrent.Callable;
@@ -74,8 +77,10 @@
 import org.apache.lucene.document.StoredField;
 import org.apache.lucene.document.StringField;
 import org.apache.lucene.document.TextField;
+import org.apache.lucene.index.IndexCommit;
 import org.apache.lucene.index.IndexWriter;
 import org.apache.lucene.index.IndexableField;
+import org.apache.lucene.index.SnapshotDeletionPolicy;
 import org.apache.lucene.index.Term;
 import org.apache.lucene.search.ControlledRealTimeReopenThread;
 import org.apache.lucene.search.IndexSearcher;
@@ -88,6 +93,7 @@
 import org.apache.lucene.search.TopFieldDocs;
 import org.apache.lucene.store.AlreadyClosedException;
 import org.apache.lucene.store.Directory;
+import org.apache.lucene.store.FSDirectory;
 
 /** Basic Lucene index implementation. */
 public abstract class AbstractLuceneIndex<K, V> implements Index<K, V> {
@@ -134,6 +140,9 @@
     String index = Joiner.on('_').skipNulls().join(name, subIndex);
     long commitPeriod = writerConfig.getCommitWithinMs();
 
+    writerConfig.setIndexDeletionPolicy(
+        new SnapshotDeletionPolicy(writerConfig.getIndexDeletionPolicy()));
+
     if (commitPeriod < 0) {
       writer = new AutoCommitWriter(dir, writerConfig.getLuceneConfig());
     } else if (commitPeriod == 0) {
@@ -516,6 +525,34 @@
     return schema;
   }
 
+  @Override
+  public boolean snapshot(String id) throws IOException {
+    SnapshotDeletionPolicy snapshooter =
+        (SnapshotDeletionPolicy) writer.getConfig().getIndexDeletionPolicy();
+
+    IndexCommit commit = snapshooter.snapshot();
+    try {
+      Path sourceDir = canonical(((FSDirectory) commit.getDirectory()).getDirectory());
+      Path indexDir = canonical(sitePaths.index_dir);
+      Path targetDir =
+          indexDir.resolve("snapshots").resolve(id).resolve(indexDir.relativize(sourceDir));
+      if (targetDir.toFile().exists()) {
+        throw new FileAlreadyExistsException(targetDir.toString());
+      }
+      targetDir.toFile().mkdirs();
+      for (String file : commit.getFileNames()) {
+        Files.copy(sourceDir.resolve(file).toFile(), targetDir.resolve(file).toFile());
+      }
+    } finally {
+      snapshooter.release(commit);
+    }
+    return true;
+  }
+
+  private static Path canonical(Path p) throws IOException {
+    return p.toFile().getCanonicalFile().toPath();
+  }
+
   protected class LuceneQuerySource implements DataSource<V> {
     private final QueryOptions opts;
     private final Query query;
diff --git a/java/com/google/gerrit/lucene/BUILD b/java/com/google/gerrit/lucene/BUILD
index 879e706..6584bfd 100644
--- a/java/com/google/gerrit/lucene/BUILD
+++ b/java/com/google/gerrit/lucene/BUILD
@@ -10,7 +10,8 @@
         "//java/com/google/gerrit/index",
         "//java/com/google/gerrit/index:query_exception",
         "//lib:guava",
-        "//lib/lucene:lucene-core-and-backward-codecs",
+        "//lib/errorprone:annotations",
+        "//lib/lucene:lucene-core",
     ],
 )
 
@@ -35,11 +36,12 @@
         "//lib:guava",
         "//lib:jgit",
         "//lib:protobuf",
+        "//lib/errorprone:annotations",
         "//lib/flogger:api",
         "//lib/guice",
         "//lib/guice:guice-assistedinject",
         "//lib/lucene:lucene-analyzers-common",
-        "//lib/lucene:lucene-core-and-backward-codecs",
+        "//lib/lucene:lucene-core",
         "//lib/lucene:lucene-misc",
     ],
 )
diff --git a/java/com/google/gerrit/lucene/GerritIndexWriterConfig.java b/java/com/google/gerrit/lucene/GerritIndexWriterConfig.java
index f6b2f0e..bec63bd 100644
--- a/java/com/google/gerrit/lucene/GerritIndexWriterConfig.java
+++ b/java/com/google/gerrit/lucene/GerritIndexWriterConfig.java
@@ -22,6 +22,7 @@
 import org.apache.lucene.analysis.CharArraySet;
 import org.apache.lucene.analysis.standard.StandardAnalyzer;
 import org.apache.lucene.index.ConcurrentMergeScheduler;
+import org.apache.lucene.index.IndexDeletionPolicy;
 import org.apache.lucene.index.IndexWriterConfig;
 import org.apache.lucene.index.IndexWriterConfig.OpenMode;
 import org.eclipse.jgit.lib.Config;
@@ -77,6 +78,14 @@
     }
   }
 
+  void setIndexDeletionPolicy(IndexDeletionPolicy indexDeletionPolicy) {
+    luceneConfig.setIndexDeletionPolicy(indexDeletionPolicy);
+  }
+
+  IndexDeletionPolicy getIndexDeletionPolicy() {
+    return luceneConfig.getIndexDeletionPolicy();
+  }
+
   CustomMappingAnalyzer getAnalyzer() {
     return analyzer;
   }
diff --git a/java/com/google/gerrit/lucene/LuceneChangeIndex.java b/java/com/google/gerrit/lucene/LuceneChangeIndex.java
index 6718e36..9fcf5ae 100644
--- a/java/com/google/gerrit/lucene/LuceneChangeIndex.java
+++ b/java/com/google/gerrit/lucene/LuceneChangeIndex.java
@@ -284,6 +284,11 @@
     openIndex.markReady(ready);
   }
 
+  @Override
+  public boolean snapshot(String id) throws IOException {
+    return openIndex.snapshot(id) && closedIndex.snapshot(id);
+  }
+
   private Sort getSort() {
     return new Sort(
         new SortField(UPDATED_SORT_FIELD, SortField.Type.LONG, true),
@@ -417,13 +422,16 @@
               TopFieldDocs subIndexHits =
                   searchers[i].searchAfter(
                       searchAfter, query, maxRemainingHits, sort, /* doDocScores= */ false);
+              assignShardIndexValues(subIndexHits, i);
               searchAfterHitsCount += subIndexHits.scoreDocs.length;
               hits.add(subIndexHits);
               searchAfterBySubIndex.put(
                   subIndex, Iterables.getLast(Arrays.asList(subIndexHits.scoreDocs), searchAfter));
             }
           } else {
-            hits.add(searchers[i].search(query, queryLimit, sort));
+            TopFieldDocs subIndexHits = searchers[i].search(query, queryLimit, sort);
+            assignShardIndexValues(subIndexHits, i);
+            hits.add(subIndexHits);
           }
         }
         TopDocs docs = TopDocs.merge(sort, queryLimit, hits.stream().toArray(TopFieldDocs[]::new));
@@ -447,6 +455,25 @@
       }
     }
 
+    /*
+     * Assign shard index values to the score documents.
+     *
+     * <p>TopDocs.merge()'s API has been changed to stop allowing passing in a parameter to
+     * indicate if it should set shard indices for hits as they are seen during the merge
+     * process. This is done to simplify the API to be more dynamic in terms of passing in
+     * custom tie breakers. If shard indices are to be used for tie breaking docs with equal
+     * scores during TopDocs.merge(), then it is mandatory that the input ScoreDocs have their
+     * shard indices set to valid values prior to calling merge().
+     *
+     * @param doc document
+     * @param shard index
+     */
+    private void assignShardIndexValues(TopFieldDocs doc, int shard) {
+      for (int docID = 0; docID < doc.scoreDocs.length; docID++) {
+        doc.scoreDocs[docID].shardIndex = shard;
+      }
+    }
+
     /**
      * Returns null for the first page or when pagination type is not {@link
      * PaginationType#SEARCH_AFTER search-after}, otherwise returns the last doc from previous
@@ -556,7 +583,8 @@
 
     for (SchemaField<ChangeData, ?> field : getSchema().getSchemaFields().values()) {
       if (fields.contains(field.getName())) {
-        field.setIfPossible(cd, new LuceneStoredValue(doc.get(field.getName())));
+        @SuppressWarnings("unused")
+        var unused = field.setIfPossible(cd, new LuceneStoredValue(doc.get(field.getName())));
       }
     }
     return cd;
diff --git a/java/com/google/gerrit/lucene/LuceneIndexModule.java b/java/com/google/gerrit/lucene/LuceneIndexModule.java
index 3aa9c6e..89b83a4 100644
--- a/java/com/google/gerrit/lucene/LuceneIndexModule.java
+++ b/java/com/google/gerrit/lucene/LuceneIndexModule.java
@@ -26,7 +26,6 @@
 import com.google.gerrit.server.index.change.ChangeIndex;
 import com.google.gerrit.server.index.group.GroupIndex;
 import com.google.gerrit.server.index.options.AutoFlush;
-import java.util.Map;
 import org.apache.lucene.search.BooleanQuery;
 import org.eclipse.jgit.lib.Config;
 
@@ -41,17 +40,17 @@
 
   @VisibleForTesting
   public static LuceneIndexModule singleVersionWithExplicitVersions(
-      Map<String, Integer> versions, int threads, boolean slave) {
+      ImmutableMap<String, Integer> versions, int threads, boolean slave) {
     return new LuceneIndexModule(versions, threads, slave, AutoFlush.ENABLED);
   }
 
   public static LuceneIndexModule singleVersionWithExplicitVersions(
-      Map<String, Integer> versions, int threads, boolean slave, AutoFlush autoFlush) {
+      ImmutableMap<String, Integer> versions, int threads, boolean slave, AutoFlush autoFlush) {
     return new LuceneIndexModule(versions, threads, slave, autoFlush);
   }
 
   public static LuceneIndexModule latestVersion(boolean slave, AutoFlush autoFlush) {
-    return new LuceneIndexModule(null, 0, slave, autoFlush);
+    return new LuceneIndexModule(/* singleVersions= */ null, 0, slave, autoFlush);
   }
 
   static boolean isInMemoryTest(Config cfg) {
@@ -59,7 +58,10 @@
   }
 
   private LuceneIndexModule(
-      Map<String, Integer> singleVersions, int threads, boolean slave, AutoFlush autoFlush) {
+      ImmutableMap<String, Integer> singleVersions,
+      int threads,
+      boolean slave,
+      AutoFlush autoFlush) {
     super(singleVersions, threads, slave);
     this.autoFlush = autoFlush;
   }
diff --git a/java/com/google/gerrit/server/StarredChangesUtil.java b/java/com/google/gerrit/lucene/package-info.java
similarity index 64%
copy from java/com/google/gerrit/server/StarredChangesUtil.java
copy to java/com/google/gerrit/lucene/package-info.java
index 0709b86..fb5f8ec 100644
--- a/java/com/google/gerrit/server/StarredChangesUtil.java
+++ b/java/com/google/gerrit/lucene/package-info.java
@@ -1,4 +1,4 @@
-// Copyright (C) 2015 The Android Open Source Project
+// Copyright (C) 2023 The Android Open Source Project
 //
 // Licensed under the Apache License, Version 2.0 (the "License");
 // you may not use this file except in compliance with the License.
@@ -12,8 +12,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package com.google.gerrit.server;
+@CheckReturnValue
+package com.google.gerrit.lucene;
 
-// TODO - delete this class. It cannot be deleted as part of the original refactoring since it's
-// used by a plugin.
-public interface StarredChangesUtil extends StarredChangesReader, StarredChangesWriter {}
+import com.google.errorprone.annotations.CheckReturnValue;
diff --git a/java/com/google/gerrit/mail/BUILD b/java/com/google/gerrit/mail/BUILD
index 0fe6c43..8830a66 100644
--- a/java/com/google/gerrit/mail/BUILD
+++ b/java/com/google/gerrit/mail/BUILD
@@ -10,6 +10,7 @@
         "//lib:guava",
         "//lib/auto:auto-value",
         "//lib/auto:auto-value-annotations",
+        "//lib/errorprone:annotations",
         "//lib/flogger:api",
         "//lib/jsoup",
         "//lib/mime4j:core",
diff --git a/java/com/google/gerrit/mail/MailMessage.java b/java/com/google/gerrit/mail/MailMessage.java
index 2ce6cbb..b2385a7 100644
--- a/java/com/google/gerrit/mail/MailMessage.java
+++ b/java/com/google/gerrit/mail/MailMessage.java
@@ -16,6 +16,7 @@
 
 import com.google.auto.value.AutoValue;
 import com.google.common.collect.ImmutableList;
+import com.google.errorprone.annotations.CanIgnoreReturnValue;
 import com.google.gerrit.common.Nullable;
 import com.google.gerrit.entities.Address;
 import java.time.Instant;
@@ -72,6 +73,7 @@
 
     public abstract ImmutableList.Builder<Address> toBuilder();
 
+    @CanIgnoreReturnValue
     public Builder addTo(Address val) {
       toBuilder().add(val);
       return this;
@@ -79,6 +81,7 @@
 
     public abstract ImmutableList.Builder<Address> ccBuilder();
 
+    @CanIgnoreReturnValue
     public Builder addCc(Address val) {
       ccBuilder().add(val);
       return this;
@@ -88,6 +91,7 @@
 
     public abstract ImmutableList.Builder<String> additionalHeadersBuilder();
 
+    @CanIgnoreReturnValue
     public Builder addAdditionalHeader(String val) {
       additionalHeadersBuilder().add(val);
       return this;
diff --git a/java/com/google/gerrit/server/StarredChangesUtil.java b/java/com/google/gerrit/mail/package-info.java
similarity index 64%
copy from java/com/google/gerrit/server/StarredChangesUtil.java
copy to java/com/google/gerrit/mail/package-info.java
index 0709b86..5db0133 100644
--- a/java/com/google/gerrit/server/StarredChangesUtil.java
+++ b/java/com/google/gerrit/mail/package-info.java
@@ -1,4 +1,4 @@
-// Copyright (C) 2015 The Android Open Source Project
+// Copyright (C) 2023 The Android Open Source Project
 //
 // Licensed under the Apache License, Version 2.0 (the "License");
 // you may not use this file except in compliance with the License.
@@ -12,8 +12,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package com.google.gerrit.server;
+@CheckReturnValue
+package com.google.gerrit.mail;
 
-// TODO - delete this class. It cannot be deleted as part of the original refactoring since it's
-// used by a plugin.
-public interface StarredChangesUtil extends StarredChangesReader, StarredChangesWriter {}
+import com.google.errorprone.annotations.CheckReturnValue;
diff --git a/java/com/google/gerrit/metrics/Description.java b/java/com/google/gerrit/metrics/Description.java
index f5963af..d4d3bb3 100644
--- a/java/com/google/gerrit/metrics/Description.java
+++ b/java/com/google/gerrit/metrics/Description.java
@@ -17,6 +17,7 @@
 import com.google.common.base.Strings;
 import com.google.common.collect.ImmutableMap;
 import com.google.common.collect.Maps;
+import com.google.errorprone.annotations.CanIgnoreReturnValue;
 import java.util.Map;
 import java.util.concurrent.TimeUnit;
 
@@ -73,6 +74,7 @@
    * @param unitName name of the unit, e.g. "requests", "seconds", etc.
    * @return this
    */
+  @CanIgnoreReturnValue
   public Description setUnit(String unitName) {
     annotations.put(UNIT, unitName);
     return this;
@@ -84,6 +86,7 @@
    *
    * @return this
    */
+  @CanIgnoreReturnValue
   public Description setConstant() {
     annotations.put(CONSTANT, TRUE_VALUE);
     return this;
@@ -95,6 +98,7 @@
    *
    * @return this
    */
+  @CanIgnoreReturnValue
   public Description setRate() {
     annotations.put(RATE, TRUE_VALUE);
     return this;
@@ -106,6 +110,7 @@
    *
    * @return this
    */
+  @CanIgnoreReturnValue
   public Description setGauge() {
     annotations.put(GAUGE, TRUE_VALUE);
     return this;
@@ -117,6 +122,7 @@
    *
    * @return this
    */
+  @CanIgnoreReturnValue
   public Description setCumulative() {
     annotations.put(CUMULATIVE, TRUE_VALUE);
     return this;
@@ -128,6 +134,7 @@
    * @param ordering field ordering
    * @return this
    */
+  @CanIgnoreReturnValue
   public Description setFieldOrdering(FieldOrdering ordering) {
     annotations.put(FIELD_ORDERING, ordering.name());
     return this;
diff --git a/java/com/google/gerrit/metrics/MetricMaker.java b/java/com/google/gerrit/metrics/MetricMaker.java
index 3f9bab1..00f0792 100644
--- a/java/com/google/gerrit/metrics/MetricMaker.java
+++ b/java/com/google/gerrit/metrics/MetricMaker.java
@@ -140,15 +140,18 @@
    * @param trigger trigger to connect
    * @return registration handle
    */
+  @CanIgnoreReturnValue
   public RegistrationHandle newTrigger(CallbackMetric<?> metric1, Runnable trigger) {
     return newTrigger(ImmutableSet.of(metric1), trigger);
   }
 
+  @CanIgnoreReturnValue
   public RegistrationHandle newTrigger(
       CallbackMetric<?> metric1, CallbackMetric<?> metric2, Runnable trigger) {
     return newTrigger(ImmutableSet.of(metric1, metric2), trigger);
   }
 
+  @CanIgnoreReturnValue
   public RegistrationHandle newTrigger(
       CallbackMetric<?> metric1,
       CallbackMetric<?> metric2,
@@ -157,6 +160,7 @@
     return newTrigger(ImmutableSet.of(metric1, metric2, metric3), trigger);
   }
 
+  @CanIgnoreReturnValue
   public abstract RegistrationHandle newTrigger(Set<CallbackMetric<?>> metrics, Runnable trigger);
 
   /**
diff --git a/java/com/google/gerrit/metrics/TimerContext.java b/java/com/google/gerrit/metrics/TimerContext.java
index a3754c5..0e01de0 100644
--- a/java/com/google/gerrit/metrics/TimerContext.java
+++ b/java/com/google/gerrit/metrics/TimerContext.java
@@ -14,6 +14,8 @@
 
 package com.google.gerrit.metrics;
 
+import com.google.errorprone.annotations.CanIgnoreReturnValue;
+
 abstract class TimerContext implements AutoCloseable {
   private final long startNanos;
   private boolean stopped;
@@ -40,6 +42,7 @@
    * @return the elapsed time in nanoseconds.
    * @throws IllegalStateException if the timer is already stopped.
    */
+  @CanIgnoreReturnValue
   public long stop() {
     if (!stopped) {
       stopped = true;
diff --git a/java/com/google/gerrit/metrics/dropwizard/BUILD b/java/com/google/gerrit/metrics/dropwizard/BUILD
index dbb8f5e..130eb8b 100644
--- a/java/com/google/gerrit/metrics/dropwizard/BUILD
+++ b/java/com/google/gerrit/metrics/dropwizard/BUILD
@@ -12,6 +12,7 @@
         "//lib:args4j",
         "//lib:guava",
         "//lib/dropwizard:dropwizard-core",
+        "//lib/errorprone:annotations",
         "//lib/flogger:api",
         "//lib/guice",
     ],
diff --git a/java/com/google/gerrit/metrics/dropwizard/BucketedCallback.java b/java/com/google/gerrit/metrics/dropwizard/BucketedCallback.java
index da9ec70..fdfe129 100644
--- a/java/com/google/gerrit/metrics/dropwizard/BucketedCallback.java
+++ b/java/com/google/gerrit/metrics/dropwizard/BucketedCallback.java
@@ -19,6 +19,7 @@
 import com.codahale.metrics.MetricRegistry;
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.Maps;
+import com.google.errorprone.annotations.CanIgnoreReturnValue;
 import com.google.gerrit.metrics.Description;
 import com.google.gerrit.metrics.Field;
 import java.util.Iterator;
@@ -85,14 +86,17 @@
     }
   }
 
+  @CanIgnoreReturnValue
   ValueGauge getOrCreate(Object f1, Object f2) {
     return getOrCreate(ImmutableList.of(f1, f2));
   }
 
+  @CanIgnoreReturnValue
   ValueGauge getOrCreate(Object f1, Object f2, Object f3) {
     return getOrCreate(ImmutableList.of(f1, f2, f3));
   }
 
+  @CanIgnoreReturnValue
   ValueGauge getOrCreate(Object key) {
     ValueGauge c = cells.get(key);
     if (c != null) {
diff --git a/java/com/google/gerrit/server/StarredChangesUtil.java b/java/com/google/gerrit/metrics/dropwizard/package-info.java
similarity index 64%
copy from java/com/google/gerrit/server/StarredChangesUtil.java
copy to java/com/google/gerrit/metrics/dropwizard/package-info.java
index 0709b86..e91c94d 100644
--- a/java/com/google/gerrit/server/StarredChangesUtil.java
+++ b/java/com/google/gerrit/metrics/dropwizard/package-info.java
@@ -1,4 +1,4 @@
-// Copyright (C) 2015 The Android Open Source Project
+// Copyright (C) 2023 The Android Open Source Project
 //
 // Licensed under the Apache License, Version 2.0 (the "License");
 // you may not use this file except in compliance with the License.
@@ -12,8 +12,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package com.google.gerrit.server;
+@CheckReturnValue
+package com.google.gerrit.metrics.dropwizard;
 
-// TODO - delete this class. It cannot be deleted as part of the original refactoring since it's
-// used by a plugin.
-public interface StarredChangesUtil extends StarredChangesReader, StarredChangesWriter {}
+import com.google.errorprone.annotations.CheckReturnValue;
diff --git a/java/com/google/gerrit/server/StarredChangesUtil.java b/java/com/google/gerrit/metrics/package-info.java
similarity index 64%
copy from java/com/google/gerrit/server/StarredChangesUtil.java
copy to java/com/google/gerrit/metrics/package-info.java
index 0709b86..7d691d9 100644
--- a/java/com/google/gerrit/server/StarredChangesUtil.java
+++ b/java/com/google/gerrit/metrics/package-info.java
@@ -1,4 +1,4 @@
-// Copyright (C) 2015 The Android Open Source Project
+// Copyright (C) 2023 The Android Open Source Project
 //
 // Licensed under the Apache License, Version 2.0 (the "License");
 // you may not use this file except in compliance with the License.
@@ -12,8 +12,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package com.google.gerrit.server;
+@CheckReturnValue
+package com.google.gerrit.metrics;
 
-// TODO - delete this class. It cannot be deleted as part of the original refactoring since it's
-// used by a plugin.
-public interface StarredChangesUtil extends StarredChangesReader, StarredChangesWriter {}
+import com.google.errorprone.annotations.CheckReturnValue;
diff --git a/java/com/google/gerrit/server/StarredChangesUtil.java b/java/com/google/gerrit/metrics/proc/package-info.java
similarity index 64%
copy from java/com/google/gerrit/server/StarredChangesUtil.java
copy to java/com/google/gerrit/metrics/proc/package-info.java
index 0709b86..0136afd 100644
--- a/java/com/google/gerrit/server/StarredChangesUtil.java
+++ b/java/com/google/gerrit/metrics/proc/package-info.java
@@ -1,4 +1,4 @@
-// Copyright (C) 2015 The Android Open Source Project
+// Copyright (C) 2023 The Android Open Source Project
 //
 // Licensed under the Apache License, Version 2.0 (the "License");
 // you may not use this file except in compliance with the License.
@@ -12,8 +12,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package com.google.gerrit.server;
+@CheckReturnValue
+package com.google.gerrit.metrics.proc;
 
-// TODO - delete this class. It cannot be deleted as part of the original refactoring since it's
-// used by a plugin.
-public interface StarredChangesUtil extends StarredChangesReader, StarredChangesWriter {}
+import com.google.errorprone.annotations.CheckReturnValue;
diff --git a/java/com/google/gerrit/pgm/BUILD b/java/com/google/gerrit/pgm/BUILD
index 8523e8a..0967130 100644
--- a/java/com/google/gerrit/pgm/BUILD
+++ b/java/com/google/gerrit/pgm/BUILD
@@ -51,6 +51,7 @@
         "//lib/auto:auto-value",
         "//lib/auto:auto-value-annotations",
         "//lib/commons:lang3",
+        "//lib/errorprone:annotations",
         "//lib/flogger:api",
         "//lib/guice",
         "//lib/guice:guice-assistedinject",
diff --git a/java/com/google/gerrit/pgm/ChangeExternalIdCaseSensitivity.java b/java/com/google/gerrit/pgm/ChangeExternalIdCaseSensitivity.java
index 00e8fa4..9c74f78 100644
--- a/java/com/google/gerrit/pgm/ChangeExternalIdCaseSensitivity.java
+++ b/java/com/google/gerrit/pgm/ChangeExternalIdCaseSensitivity.java
@@ -14,6 +14,7 @@
 
 package com.google.gerrit.pgm;
 
+import com.google.common.collect.ImmutableSet;
 import com.google.common.flogger.FluentLogger;
 import com.google.gerrit.extensions.config.FactoryModule;
 import com.google.gerrit.lifecycle.LifecycleManager;
@@ -35,7 +36,6 @@
 import com.google.inject.Key;
 import com.google.inject.assistedinject.FactoryModuleBuilder;
 import java.io.IOException;
-import java.util.Collection;
 import org.eclipse.jgit.errors.ConfigInvalidException;
 import org.eclipse.jgit.lib.Config;
 import org.eclipse.jgit.lib.ProgressMonitor;
@@ -108,7 +108,7 @@
       return 0;
     }
 
-    Collection<ExternalId> todo = externalIds.all();
+    ImmutableSet<ExternalId> todo = externalIds.all();
     monitor.beginTask("Converting external ID note names", todo.size());
 
     manager.start();
diff --git a/java/com/google/gerrit/pgm/Daemon.java b/java/com/google/gerrit/pgm/Daemon.java
index 6230136..d1307b1 100644
--- a/java/com/google/gerrit/pgm/Daemon.java
+++ b/java/com/google/gerrit/pgm/Daemon.java
@@ -103,6 +103,8 @@
 import com.google.gerrit.server.mail.receive.MailReceiver.MailReceiverModule;
 import com.google.gerrit.server.mail.send.SmtpEmailSender.SmtpEmailSenderModule;
 import com.google.gerrit.server.mime.MimeUtil2Module;
+import com.google.gerrit.server.notedb.NoteDbDraftCommentsModule;
+import com.google.gerrit.server.notedb.NoteDbStarredChangesModule;
 import com.google.gerrit.server.notedb.RepoSequence.RepoSequenceModule;
 import com.google.gerrit.server.patch.DiffExecutorModule;
 import com.google.gerrit.server.permissions.DefaultPermissionBackendModule;
@@ -272,7 +274,8 @@
     }
     if (doInit) {
       try {
-        new Init(getSitePath()).run();
+        @SuppressWarnings("unused")
+        var unused = new Init(getSitePath()).run();
       } catch (Exception e) {
         throw die("Init failed", e);
       }
@@ -471,6 +474,8 @@
     modules.add(new AccountNoteDbWriteStorageModule());
     modules.add(new AccountNoteDbReadStorageModule());
     modules.add(new RepoSequenceModule());
+    modules.add(new NoteDbDraftCommentsModule());
+    modules.add(new NoteDbStarredChangesModule());
     modules.add(cfgInjector.getInstance(GerritGlobalModule.class));
     modules.add(new GerritApiModule());
     modules.add(new ProjectQueryBuilderModule());
diff --git a/java/com/google/gerrit/pgm/Init.java b/java/com/google/gerrit/pgm/Init.java
index c05bff5..d3e9988 100644
--- a/java/com/google/gerrit/pgm/Init.java
+++ b/java/com/google/gerrit/pgm/Init.java
@@ -143,7 +143,7 @@
 
   @Override
   protected void afterInit(SiteRun run) throws Exception {
-    List<SchemaDefinitions<?>> schemaDefs =
+    ImmutableList<SchemaDefinitions<?>> schemaDefs =
         ImmutableList.of(
             AccountSchemaDefinitions.INSTANCE,
             ChangeSchemaDefinitions.INSTANCE,
@@ -302,7 +302,9 @@
         .message(String.format("Init complete, reindexing %s with:", String.join(",", indices)));
     getConsoleUI().message(" reindex " + reindexArgs.stream().collect(joining(" ")));
     Reindex reindexPgm = new Reindex();
-    reindexPgm.main(reindexArgs.stream().toArray(String[]::new));
+
+    @SuppressWarnings("unused")
+    var unused = reindexPgm.main(reindexArgs.stream().toArray(String[]::new));
   }
 
   private static boolean nullOrEmpty(List<?> list) {
diff --git a/java/com/google/gerrit/pgm/JythonShell.java b/java/com/google/gerrit/pgm/JythonShell.java
index d85bdc0..dcd89dc 100644
--- a/java/com/google/gerrit/pgm/JythonShell.java
+++ b/java/com/google/gerrit/pgm/JythonShell.java
@@ -70,12 +70,14 @@
     pyObject = findClass("org.python.core.PyObject");
     pySystemState = findClass("org.python.core.PySystemState");
 
-    runMethod(
-        pySystemState,
-        pySystemState,
-        "initialize",
-        new Class<?>[] {Properties.class, Properties.class},
-        new Object[] {null, env});
+    @SuppressWarnings("unused")
+    var unused =
+        runMethod(
+            pySystemState,
+            pySystemState,
+            "initialize",
+            new Class<?>[] {Properties.class, Properties.class},
+            new Object[] {null, env});
 
     try {
       shell = console.getConstructor(new Class<?>[] {}).newInstance();
@@ -125,10 +127,12 @@
   }
 
   protected void printInjectedVariable(String id) {
-    runInterpreter(
-        "exec",
-        new Class<?>[] {String.class},
-        new Object[] {"print '\"%s\" is \"%s\"' % (\"" + id + "\", " + id + ")"});
+    @SuppressWarnings("unused")
+    var unused =
+        runInterpreter(
+            "exec",
+            new Class<?>[] {String.class},
+            new Object[] {"print '\"%s\" is \"%s\"' % (\"" + id + "\", " + id + ")"});
   }
 
   public void run() {
@@ -136,19 +140,26 @@
       printInjectedVariable(key);
     }
     reload();
-    runInterpreter(
-        "interact",
-        new Class<?>[] {String.class, pyObject},
-        new Object[] {
-          getDefaultBanner()
-              + " running for Gerrit "
-              + com.google.gerrit.common.Version.getVersion(),
-          null,
-        });
+
+    @SuppressWarnings("unused")
+    var unused =
+        runInterpreter(
+            "interact",
+            new Class<?>[] {String.class, pyObject},
+            new Object[] {
+              getDefaultBanner()
+                  + " running for Gerrit "
+                  + com.google.gerrit.common.Version.getVersion(),
+              null,
+            });
   }
 
   public void set(String key, Object content) {
-    runInterpreter("set", new Class<?>[] {String.class, Object.class}, new Object[] {key, content});
+    @SuppressWarnings("unused")
+    var unused =
+        runInterpreter(
+            "set", new Class<?>[] {String.class, Object.class}, new Object[] {key, content});
+
     injectedVariables.add(key);
   }
 
@@ -181,12 +192,14 @@
     try {
       File script = new File(parent, p);
       if (script.canExecute()) {
-        runMethod0(
-            console,
-            shell,
-            "execfile",
-            new Class<?>[] {String.class},
-            new Object[] {script.getAbsolutePath()});
+        @SuppressWarnings("unused")
+        var unused =
+            runMethod0(
+                console,
+                shell,
+                "execfile",
+                new Class<?>[] {String.class},
+                new Object[] {script.getAbsolutePath()});
       } else {
         logger.atInfo().log(
             "User initialization file %s is not found or not executable", script.getAbsolutePath());
@@ -200,12 +213,14 @@
 
   protected void execStream(InputStream in, String p) {
     try {
-      runMethod0(
-          console,
-          shell,
-          "execfile",
-          new Class<?>[] {InputStream.class, String.class},
-          new Object[] {in, p});
+      @SuppressWarnings("unused")
+      var unused =
+          runMethod0(
+              console,
+              shell,
+              "execfile",
+              new Class<?>[] {InputStream.class, String.class},
+              new Object[] {in, p});
     } catch (InvocationTargetException e) {
       logger.atSevere().withCause(e).log("Exception occurred while loading %s", p);
     }
diff --git a/java/com/google/gerrit/pgm/LocalUsernamesToLowerCase.java b/java/com/google/gerrit/pgm/LocalUsernamesToLowerCase.java
index 6967fb1..db8cc16 100644
--- a/java/com/google/gerrit/pgm/LocalUsernamesToLowerCase.java
+++ b/java/com/google/gerrit/pgm/LocalUsernamesToLowerCase.java
@@ -16,6 +16,7 @@
 
 import static com.google.gerrit.server.account.externalids.ExternalId.SCHEME_GERRIT;
 
+import com.google.common.collect.ImmutableSet;
 import com.google.gerrit.exceptions.DuplicateKeyException;
 import com.google.gerrit.extensions.config.FactoryModule;
 import com.google.gerrit.lifecycle.LifecycleManager;
@@ -35,7 +36,6 @@
 import com.google.inject.Injector;
 import com.google.inject.Provider;
 import java.io.IOException;
-import java.util.Collection;
 import java.util.Locale;
 import java.util.Optional;
 import org.apache.commons.lang3.StringUtils;
@@ -78,7 +78,7 @@
             })
         .injectMembers(this);
 
-    Collection<ExternalId> todo = externalIds.all();
+    ImmutableSet<ExternalId> todo = externalIds.all();
     monitor.beginTask("Converting local usernames", todo.size());
 
     try (Repository repo = repoManager.openRepository(allUsersName)) {
diff --git a/java/com/google/gerrit/pgm/Ls.java b/java/com/google/gerrit/pgm/Ls.java
index 4b48148..b705b3f 100644
--- a/java/com/google/gerrit/pgm/Ls.java
+++ b/java/com/google/gerrit/pgm/Ls.java
@@ -16,6 +16,7 @@
 
 import static com.google.common.collect.ImmutableList.toImmutableList;
 
+import com.google.common.collect.ImmutableList;
 import com.google.gerrit.launcher.GerritLauncher;
 import com.google.gerrit.pgm.util.AbstractProgram;
 import java.io.IOException;
@@ -49,7 +50,7 @@
     return 0;
   }
 
-  private static Iterable<? extends ZipEntry> entriesOf(ZipFile zipFile) {
+  private static ImmutableList<? extends ZipEntry> entriesOf(ZipFile zipFile) {
     return zipFile.stream().collect(toImmutableList());
   }
 }
diff --git a/java/com/google/gerrit/pgm/Reindex.java b/java/com/google/gerrit/pgm/Reindex.java
index a2e780d..2ed2b76 100644
--- a/java/com/google/gerrit/pgm/Reindex.java
+++ b/java/com/google/gerrit/pgm/Reindex.java
@@ -18,6 +18,7 @@
 import static java.util.stream.Collectors.toSet;
 
 import com.google.common.cache.Cache;
+import com.google.common.collect.ImmutableMap;
 import com.google.common.collect.Sets;
 import com.google.gerrit.common.Die;
 import com.google.gerrit.extensions.config.FactoryModule;
@@ -44,6 +45,8 @@
 import com.google.gerrit.server.index.options.AutoFlush;
 import com.google.gerrit.server.index.options.BuildBloomFilter;
 import com.google.gerrit.server.index.options.IsFirstInsertForEntry;
+import com.google.gerrit.server.notedb.NoteDbDraftCommentsModule;
+import com.google.gerrit.server.notedb.NoteDbStarredChangesModule;
 import com.google.gerrit.server.notedb.RepoSequence.RepoSequenceModule;
 import com.google.gerrit.server.plugins.PluginGuiceEnvironment;
 import com.google.gerrit.server.util.ReplicaUtil;
@@ -59,9 +62,7 @@
 import java.lang.reflect.Method;
 import java.util.ArrayList;
 import java.util.Collection;
-import java.util.HashMap;
 import java.util.List;
-import java.util.Map;
 import java.util.Set;
 import java.util.TreeSet;
 import java.util.concurrent.TimeUnit;
@@ -173,10 +174,10 @@
   }
 
   private Injector createSysInjector() {
-    Map<String, Integer> versions = new HashMap<>();
-    if (changesVersion != null) {
-      versions.put(ChangeSchemaDefinitions.INSTANCE.getName(), changesVersion);
-    }
+    ImmutableMap<String, Integer> versions =
+        changesVersion != null
+            ? ImmutableMap.of(ChangeSchemaDefinitions.INSTANCE.getName(), changesVersion)
+            : ImmutableMap.of();
     boolean replica = ReplicaUtil.isReplica(globalConfig);
     List<Module> modules = new ArrayList<>();
     modules.add(new WorkQueueModule());
@@ -194,7 +195,7 @@
         Class<?> clazz = Class.forName("com.google.gerrit.index.testing.FakeIndexModule");
         Method m =
             clazz.getMethod(
-                "singleVersionWithExplicitVersions", Map.class, int.class, boolean.class);
+                "singleVersionWithExplicitVersions", ImmutableMap.class, int.class, boolean.class);
         indexModule = (Module) m.invoke(null, versions, threads, replica);
       } catch (NoSuchMethodException
           | ClassNotFoundException
@@ -230,6 +231,8 @@
     modules.add(new AccountNoteDbWriteStorageModule());
     modules.add(new AccountNoteDbReadStorageModule());
     modules.add(new RepoSequenceModule());
+    modules.add(new NoteDbDraftCommentsModule());
+    modules.add(new NoteDbStarredChangesModule());
 
     return dbInjector.createChildInjector(
         ModuleOverloader.override(
diff --git a/java/com/google/gerrit/pgm/SwitchSecureStore.java b/java/com/google/gerrit/pgm/SwitchSecureStore.java
index 063fcdb..3cd0c47 100644
--- a/java/com/google/gerrit/pgm/SwitchSecureStore.java
+++ b/java/com/google/gerrit/pgm/SwitchSecureStore.java
@@ -31,7 +31,6 @@
 import java.io.IOException;
 import java.nio.file.Files;
 import java.nio.file.Path;
-import java.nio.file.Paths;
 import java.util.Arrays;
 import java.util.List;
 import java.util.jar.JarFile;
@@ -63,7 +62,7 @@
   @Override
   public int run() throws Exception {
     SitePaths sitePaths = new SitePaths(getSitePath());
-    Path newSecureStorePath = Paths.get(newSecureStoreLib);
+    Path newSecureStorePath = Path.of(newSecureStoreLib);
     if (!Files.exists(newSecureStorePath)) {
       logger.atSevere().log("File %s doesn't exist", newSecureStorePath.toAbsolutePath());
       return -1;
diff --git a/java/com/google/gerrit/pgm/WarDistribution.java b/java/com/google/gerrit/pgm/WarDistribution.java
index 013c850..37178b0 100644
--- a/java/com/google/gerrit/pgm/WarDistribution.java
+++ b/java/com/google/gerrit/pgm/WarDistribution.java
@@ -18,6 +18,7 @@
 import static com.google.gerrit.pgm.init.InitPlugins.JAR;
 import static com.google.gerrit.pgm.init.InitPlugins.PLUGIN_DIR;
 
+import com.google.common.collect.ImmutableList;
 import com.google.common.flogger.FluentLogger;
 import com.google.gerrit.launcher.GerritLauncher;
 import com.google.gerrit.pgm.init.PluginsDistribution;
@@ -64,7 +65,7 @@
     throw new UnsupportedOperationException();
   }
 
-  private static Iterable<? extends ZipEntry> entriesOf(ZipFile zipFile) {
+  private static ImmutableList<? extends ZipEntry> entriesOf(ZipFile zipFile) {
     return zipFile.stream().collect(toImmutableList());
   }
 }
diff --git a/java/com/google/gerrit/pgm/http/jetty/BUILD b/java/com/google/gerrit/pgm/http/jetty/BUILD
index cd188f5..e006c91 100644
--- a/java/com/google/gerrit/pgm/http/jetty/BUILD
+++ b/java/com/google/gerrit/pgm/http/jetty/BUILD
@@ -18,6 +18,7 @@
         "//lib:guava",
         "//lib:jgit",
         "//lib:servlet-api",
+        "//lib/errorprone:annotations",
         "//lib/flogger:api",
         "//lib/guice",
         "//lib/guice:guice-assistedinject",
diff --git a/java/com/google/gerrit/server/StarredChangesUtil.java b/java/com/google/gerrit/pgm/http/jetty/package-info.java
similarity index 64%
copy from java/com/google/gerrit/server/StarredChangesUtil.java
copy to java/com/google/gerrit/pgm/http/jetty/package-info.java
index 0709b86..45a3b4f 100644
--- a/java/com/google/gerrit/server/StarredChangesUtil.java
+++ b/java/com/google/gerrit/pgm/http/jetty/package-info.java
@@ -1,4 +1,4 @@
-// Copyright (C) 2015 The Android Open Source Project
+// Copyright (C) 2023 The Android Open Source Project
 //
 // Licensed under the Apache License, Version 2.0 (the "License");
 // you may not use this file except in compliance with the License.
@@ -12,8 +12,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package com.google.gerrit.server;
+@CheckReturnValue
+package com.google.gerrit.pgm.http.jetty;
 
-// TODO - delete this class. It cannot be deleted as part of the original refactoring since it's
-// used by a plugin.
-public interface StarredChangesUtil extends StarredChangesReader, StarredChangesWriter {}
+import com.google.errorprone.annotations.CheckReturnValue;
diff --git a/java/com/google/gerrit/pgm/init/BUILD b/java/com/google/gerrit/pgm/init/BUILD
index 0c0d937..73c3760 100644
--- a/java/com/google/gerrit/pgm/init/BUILD
+++ b/java/com/google/gerrit/pgm/init/BUILD
@@ -25,6 +25,7 @@
         "//lib:guava",
         "//lib:jgit",
         "//lib/commons:validator",
+        "//lib/errorprone:annotations",
         "//lib/flogger:api",
         "//lib/guice",
         "//lib/guice:guice-assistedinject",
diff --git a/java/com/google/gerrit/pgm/init/BaseInit.java b/java/com/google/gerrit/pgm/init/BaseInit.java
index abaefb2..1f56512 100644
--- a/java/com/google/gerrit/pgm/init/BaseInit.java
+++ b/java/com/google/gerrit/pgm/init/BaseInit.java
@@ -62,7 +62,6 @@
 import java.nio.file.FileVisitResult;
 import java.nio.file.Files;
 import java.nio.file.Path;
-import java.nio.file.Paths;
 import java.nio.file.SimpleFileVisitor;
 import java.nio.file.attribute.BasicFileAttributes;
 import java.util.ArrayList;
@@ -313,7 +312,7 @@
       return null;
     }
 
-    Path secureStoreLib = Paths.get(secureStore);
+    Path secureStoreLib = Path.of(secureStore);
     if (!Files.exists(secureStoreLib)) {
       throw new InvalidSecureStoreException(String.format("File %s doesn't exist", secureStore));
     }
diff --git a/java/com/google/gerrit/pgm/init/InitAdminUser.java b/java/com/google/gerrit/pgm/init/InitAdminUser.java
index 3dce974..44ad96e 100644
--- a/java/com/google/gerrit/pgm/init/InitAdminUser.java
+++ b/java/com/google/gerrit/pgm/init/InitAdminUser.java
@@ -40,7 +40,6 @@
 import java.io.IOException;
 import java.nio.file.Files;
 import java.nio.file.Path;
-import java.nio.file.Paths;
 import java.util.ArrayList;
 import java.util.List;
 import java.util.Optional;
@@ -186,7 +185,7 @@
   @Nullable
   private AccountSshKey readSshKey(Account.Id id) throws IOException {
     String defaultPublicSshKeyFile = "";
-    Path defaultPublicSshKeyPath = Paths.get(System.getProperty("user.home"), ".ssh", "id_rsa.pub");
+    Path defaultPublicSshKeyPath = Path.of(System.getProperty("user.home"), ".ssh", "id_rsa.pub");
     if (Files.exists(defaultPublicSshKeyPath)) {
       defaultPublicSshKeyFile = defaultPublicSshKeyPath.toString();
     }
@@ -195,7 +194,7 @@
   }
 
   private AccountSshKey createSshKey(Account.Id id, String keyFile) throws IOException {
-    Path p = Paths.get(keyFile);
+    Path p = Path.of(keyFile);
     if (!Files.exists(p)) {
       throw new IOException(String.format("Cannot add public SSH key: %s is not a file", keyFile));
     }
diff --git a/java/com/google/gerrit/pgm/init/VersionedAuthorizedKeysOnInit.java b/java/com/google/gerrit/pgm/init/VersionedAuthorizedKeysOnInit.java
index acde91f..34f6615 100644
--- a/java/com/google/gerrit/pgm/init/VersionedAuthorizedKeysOnInit.java
+++ b/java/com/google/gerrit/pgm/init/VersionedAuthorizedKeysOnInit.java
@@ -17,6 +17,7 @@
 import static com.google.common.base.Preconditions.checkState;
 
 import com.google.common.base.Strings;
+import com.google.errorprone.annotations.CanIgnoreReturnValue;
 import com.google.gerrit.entities.Account;
 import com.google.gerrit.entities.RefNames;
 import com.google.gerrit.pgm.init.api.AllUsersNameOnInitProvider;
@@ -63,6 +64,7 @@
     keys = AuthorizedKeys.parse(accountId, readUTF8(AuthorizedKeys.FILE_NAME));
   }
 
+  @CanIgnoreReturnValue
   public AccountSshKey addKey(String pub) {
     checkState(keys != null, "SSH keys not loaded yet");
     int seq = keys.isEmpty() ? 1 : keys.size() + 1;
diff --git a/java/com/google/gerrit/pgm/init/api/AllProjectsConfig.java b/java/com/google/gerrit/pgm/init/api/AllProjectsConfig.java
index abd7d43..226f12b 100644
--- a/java/com/google/gerrit/pgm/init/api/AllProjectsConfig.java
+++ b/java/com/google/gerrit/pgm/init/api/AllProjectsConfig.java
@@ -15,6 +15,7 @@
 package com.google.gerrit.pgm.init.api;
 
 import com.google.common.flogger.FluentLogger;
+import com.google.errorprone.annotations.CanIgnoreReturnValue;
 import com.google.gerrit.entities.Project;
 import com.google.gerrit.entities.RefNames;
 import com.google.gerrit.server.config.AllProjectsConfigProvider;
@@ -58,6 +59,7 @@
   }
 
   @Override
+  @CanIgnoreReturnValue
   public AllProjectsConfig load() throws IOException, ConfigInvalidException {
     super.load();
     return this;
diff --git a/java/com/google/gerrit/pgm/init/api/GitRepositoryManagerOnInit.java b/java/com/google/gerrit/pgm/init/api/GitRepositoryManagerOnInit.java
index fabad49..dd29783 100644
--- a/java/com/google/gerrit/pgm/init/api/GitRepositoryManagerOnInit.java
+++ b/java/com/google/gerrit/pgm/init/api/GitRepositoryManagerOnInit.java
@@ -44,7 +44,8 @@
   @Override
   public Status getRepositoryStatus(NameKey name) {
     try {
-      openRepository(name);
+      @SuppressWarnings("unused")
+      var unused = openRepository(name);
     } catch (RepositoryNotFoundException e) {
       return Status.NON_EXISTENT;
     } catch (IOException e) {
diff --git a/java/com/google/gerrit/pgm/init/api/Section.java b/java/com/google/gerrit/pgm/init/api/Section.java
index 5cc4b5d..720c1f8 100644
--- a/java/com/google/gerrit/pgm/init/api/Section.java
+++ b/java/com/google/gerrit/pgm/init/api/Section.java
@@ -14,6 +14,7 @@
 
 package com.google.gerrit.pgm.init.api;
 
+import com.google.errorprone.annotations.CanIgnoreReturnValue;
 import com.google.gerrit.common.Nullable;
 import com.google.gerrit.server.config.SitePaths;
 import com.google.gerrit.server.securestore.SecureStore;
@@ -100,10 +101,12 @@
     set(name, (String) null);
   }
 
+  @CanIgnoreReturnValue
   public String string(String title, String name, String dv) {
     return string(title, name, dv, false);
   }
 
+  @CanIgnoreReturnValue
   public String string(final String title, String name, String dv, boolean nullIfDefault) {
     final String ov = get(name);
     String nv = ui.readString(ov != null ? ov : dv, "%s", title);
@@ -120,11 +123,13 @@
     return site.resolve(string(title, name, defValue));
   }
 
+  @CanIgnoreReturnValue
   public <T extends Enum<?>, E extends EnumSet<? extends T>> T select(
       String title, String name, T defValue) {
     return select(title, name, defValue, false);
   }
 
+  @CanIgnoreReturnValue
   public <T extends Enum<?>, E extends EnumSet<? extends T>> T select(
       String title, String name, T defValue, boolean nullIfDefault) {
     @SuppressWarnings("rawtypes")
@@ -134,11 +139,13 @@
     return select(title, name, defValue, allowedValues, nullIfDefault);
   }
 
+  @CanIgnoreReturnValue
   public <T extends Enum<?>, E extends EnumSet<? extends T>> T select(
       String title, String name, T defValue, E allowedValues) {
     return select(title, name, defValue, allowedValues, false);
   }
 
+  @CanIgnoreReturnValue
   public <T extends Enum<?>, A extends EnumSet<? extends T>> T select(
       String title, String name, T defValue, A allowedValues, boolean nullIfDefault) {
     final boolean set = get(name) != null;
@@ -167,6 +174,7 @@
   }
 
   @Nullable
+  @CanIgnoreReturnValue
   public String password(String username, String password) {
     final String ov = getSecure(password);
 
@@ -196,6 +204,7 @@
     return nv;
   }
 
+  @CanIgnoreReturnValue
   public String passwordForKey(String prompt, String passwordKey) {
     String ov = getSecure(passwordKey);
     if (ov != null) {
diff --git a/java/com/google/gerrit/pgm/init/api/VersionedMetaDataOnInit.java b/java/com/google/gerrit/pgm/init/api/VersionedMetaDataOnInit.java
index f779601..0a82c98 100644
--- a/java/com/google/gerrit/pgm/init/api/VersionedMetaDataOnInit.java
+++ b/java/com/google/gerrit/pgm/init/api/VersionedMetaDataOnInit.java
@@ -14,6 +14,7 @@
 
 package com.google.gerrit.pgm.init.api;
 
+import com.google.errorprone.annotations.CanIgnoreReturnValue;
 import com.google.gerrit.entities.Project;
 import com.google.gerrit.server.GerritPersonIdentProvider;
 import com.google.gerrit.server.config.SitePaths;
@@ -54,6 +55,7 @@
     return ref;
   }
 
+  @CanIgnoreReturnValue
   public VersionedMetaDataOnInit load() throws IOException, ConfigInvalidException {
     File path = getPath();
     if (path != null) {
@@ -89,7 +91,9 @@
       commit.setCommitter(ident);
       commit.setMessage(msg);
 
-      onSave(commit);
+      if (!onSave(commit)) {
+        return;
+      }
 
       ObjectId res = newTree.writeTree(inserter);
       if (res.equals(srcTree)) {
diff --git a/java/com/google/gerrit/server/StarredChangesUtil.java b/java/com/google/gerrit/pgm/init/api/package-info.java
similarity index 64%
copy from java/com/google/gerrit/server/StarredChangesUtil.java
copy to java/com/google/gerrit/pgm/init/api/package-info.java
index 0709b86..ef26020 100644
--- a/java/com/google/gerrit/server/StarredChangesUtil.java
+++ b/java/com/google/gerrit/pgm/init/api/package-info.java
@@ -1,4 +1,4 @@
-// Copyright (C) 2015 The Android Open Source Project
+// Copyright (C) 2023 The Android Open Source Project
 //
 // Licensed under the Apache License, Version 2.0 (the "License");
 // you may not use this file except in compliance with the License.
@@ -12,8 +12,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package com.google.gerrit.server;
+@CheckReturnValue
+package com.google.gerrit.pgm.init.api;
 
-// TODO - delete this class. It cannot be deleted as part of the original refactoring since it's
-// used by a plugin.
-public interface StarredChangesUtil extends StarredChangesReader, StarredChangesWriter {}
+import com.google.errorprone.annotations.CheckReturnValue;
diff --git a/java/com/google/gerrit/pgm/init/index/IndexModuleOnInit.java b/java/com/google/gerrit/pgm/init/index/IndexModuleOnInit.java
index d1d0729..65e7493 100644
--- a/java/com/google/gerrit/pgm/init/index/IndexModuleOnInit.java
+++ b/java/com/google/gerrit/pgm/init/index/IndexModuleOnInit.java
@@ -15,9 +15,9 @@
 package com.google.gerrit.pgm.init.index;
 
 import com.google.common.collect.FluentIterable;
-import com.google.common.collect.ImmutableCollection;
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableSet;
 import com.google.gerrit.extensions.events.LifecycleListener;
 import com.google.gerrit.index.IndexDefinition;
 import com.google.gerrit.index.SchemaDefinitions;
@@ -40,12 +40,11 @@
 import com.google.inject.util.Providers;
 import java.util.Collection;
 import java.util.Map;
-import java.util.Set;
 
 public class IndexModuleOnInit extends AbstractModule {
   static final String INDEX_MANAGER = "IndexModuleOnInit/IndexManager";
 
-  private static final ImmutableCollection<SchemaDefinitions<?>> ALL_SCHEMA_DEFS =
+  private static final ImmutableList<SchemaDefinitions<?>> ALL_SCHEMA_DEFS =
       ImmutableList.of(AccountSchemaDefinitions.INSTANCE, GroupSchemaDefinitions.INSTANCE);
 
   @Override
@@ -83,10 +82,11 @@
   @Provides
   Collection<IndexDefinition<?, ?, ?>> getIndexDefinitions(
       AccountIndexDefinition accounts, GroupIndexDefinition groups) {
-    Collection<IndexDefinition<?, ?, ?>> result = ImmutableList.of(accounts, groups);
-    Set<String> expected =
+    ImmutableList<IndexDefinition<?, ?, ?>> result = ImmutableList.of(accounts, groups);
+    ImmutableSet<String> expected =
         FluentIterable.from(ALL_SCHEMA_DEFS).transform(SchemaDefinitions::getName).toSet();
-    Set<String> actual = FluentIterable.from(result).transform(IndexDefinition::getName).toSet();
+    ImmutableSet<String> actual =
+        FluentIterable.from(result).transform(IndexDefinition::getName).toSet();
     if (!expected.equals(actual)) {
       throw new ProvisionException(
           "need index definitions for all schemas: " + expected + " != " + actual);
diff --git a/java/com/google/gerrit/server/StarredChangesUtil.java b/java/com/google/gerrit/pgm/init/index/lucene/package-info.java
similarity index 64%
copy from java/com/google/gerrit/server/StarredChangesUtil.java
copy to java/com/google/gerrit/pgm/init/index/lucene/package-info.java
index 0709b86..e72368b 100644
--- a/java/com/google/gerrit/server/StarredChangesUtil.java
+++ b/java/com/google/gerrit/pgm/init/index/lucene/package-info.java
@@ -1,4 +1,4 @@
-// Copyright (C) 2015 The Android Open Source Project
+// Copyright (C) 2023 The Android Open Source Project
 //
 // Licensed under the Apache License, Version 2.0 (the "License");
 // you may not use this file except in compliance with the License.
@@ -12,8 +12,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package com.google.gerrit.server;
+@CheckReturnValue
+package com.google.gerrit.pgm.init.index.lucene;
 
-// TODO - delete this class. It cannot be deleted as part of the original refactoring since it's
-// used by a plugin.
-public interface StarredChangesUtil extends StarredChangesReader, StarredChangesWriter {}
+import com.google.errorprone.annotations.CheckReturnValue;
diff --git a/java/com/google/gerrit/server/StarredChangesUtil.java b/java/com/google/gerrit/pgm/init/index/package-info.java
similarity index 64%
copy from java/com/google/gerrit/server/StarredChangesUtil.java
copy to java/com/google/gerrit/pgm/init/index/package-info.java
index 0709b86..cb32880 100644
--- a/java/com/google/gerrit/server/StarredChangesUtil.java
+++ b/java/com/google/gerrit/pgm/init/index/package-info.java
@@ -1,4 +1,4 @@
-// Copyright (C) 2015 The Android Open Source Project
+// Copyright (C) 2023 The Android Open Source Project
 //
 // Licensed under the Apache License, Version 2.0 (the "License");
 // you may not use this file except in compliance with the License.
@@ -12,8 +12,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package com.google.gerrit.server;
+@CheckReturnValue
+package com.google.gerrit.pgm.init.index;
 
-// TODO - delete this class. It cannot be deleted as part of the original refactoring since it's
-// used by a plugin.
-public interface StarredChangesUtil extends StarredChangesReader, StarredChangesWriter {}
+import com.google.errorprone.annotations.CheckReturnValue;
diff --git a/java/com/google/gerrit/server/StarredChangesUtil.java b/java/com/google/gerrit/pgm/init/package-info.java
similarity index 64%
copy from java/com/google/gerrit/server/StarredChangesUtil.java
copy to java/com/google/gerrit/pgm/init/package-info.java
index 0709b86..f4f4945 100644
--- a/java/com/google/gerrit/server/StarredChangesUtil.java
+++ b/java/com/google/gerrit/pgm/init/package-info.java
@@ -1,4 +1,4 @@
-// Copyright (C) 2015 The Android Open Source Project
+// Copyright (C) 2023 The Android Open Source Project
 //
 // Licensed under the Apache License, Version 2.0 (the "License");
 // you may not use this file except in compliance with the License.
@@ -12,8 +12,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package com.google.gerrit.server;
+@CheckReturnValue
+package com.google.gerrit.pgm.init;
 
-// TODO - delete this class. It cannot be deleted as part of the original refactoring since it's
-// used by a plugin.
-public interface StarredChangesUtil extends StarredChangesReader, StarredChangesWriter {}
+import com.google.errorprone.annotations.CheckReturnValue;
diff --git a/java/com/google/gerrit/server/StarredChangesUtil.java b/java/com/google/gerrit/pgm/package-info.java
similarity index 64%
copy from java/com/google/gerrit/server/StarredChangesUtil.java
copy to java/com/google/gerrit/pgm/package-info.java
index 0709b86..3e7920f 100644
--- a/java/com/google/gerrit/server/StarredChangesUtil.java
+++ b/java/com/google/gerrit/pgm/package-info.java
@@ -1,4 +1,4 @@
-// Copyright (C) 2015 The Android Open Source Project
+// Copyright (C) 2023 The Android Open Source Project
 //
 // Licensed under the Apache License, Version 2.0 (the "License");
 // you may not use this file except in compliance with the License.
@@ -12,8 +12,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package com.google.gerrit.server;
+@CheckReturnValue
+package com.google.gerrit.pgm;
 
-// TODO - delete this class. It cannot be deleted as part of the original refactoring since it's
-// used by a plugin.
-public interface StarredChangesUtil extends StarredChangesReader, StarredChangesWriter {}
+import com.google.errorprone.annotations.CheckReturnValue;
diff --git a/java/com/google/gerrit/server/StarredChangesUtil.java b/java/com/google/gerrit/pgm/rules/package-info.java
similarity index 64%
copy from java/com/google/gerrit/server/StarredChangesUtil.java
copy to java/com/google/gerrit/pgm/rules/package-info.java
index 0709b86..5ac4176 100644
--- a/java/com/google/gerrit/server/StarredChangesUtil.java
+++ b/java/com/google/gerrit/pgm/rules/package-info.java
@@ -1,4 +1,4 @@
-// Copyright (C) 2015 The Android Open Source Project
+// Copyright (C) 2023 The Android Open Source Project
 //
 // Licensed under the Apache License, Version 2.0 (the "License");
 // you may not use this file except in compliance with the License.
@@ -12,8 +12,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package com.google.gerrit.server;
+@CheckReturnValue
+package com.google.gerrit.pgm.rules;
 
-// TODO - delete this class. It cannot be deleted as part of the original refactoring since it's
-// used by a plugin.
-public interface StarredChangesUtil extends StarredChangesReader, StarredChangesWriter {}
+import com.google.errorprone.annotations.CheckReturnValue;
diff --git a/java/com/google/gerrit/pgm/util/BUILD b/java/com/google/gerrit/pgm/util/BUILD
index 5b01c9c..ad4ce88 100644
--- a/java/com/google/gerrit/pgm/util/BUILD
+++ b/java/com/google/gerrit/pgm/util/BUILD
@@ -23,6 +23,7 @@
         "//lib:gson",
         "//lib:guava",
         "//lib:jgit",
+        "//lib/errorprone:annotations",
         "//lib/flogger:api",
         "//lib/guice",
         "//lib/log:log4j",
diff --git a/java/com/google/gerrit/pgm/util/BatchProgramModule.java b/java/com/google/gerrit/pgm/util/BatchProgramModule.java
index 21ae8e1..55312b1 100644
--- a/java/com/google/gerrit/pgm/util/BatchProgramModule.java
+++ b/java/com/google/gerrit/pgm/util/BatchProgramModule.java
@@ -27,7 +27,6 @@
 import com.google.gerrit.extensions.registration.DynamicMap;
 import com.google.gerrit.extensions.registration.DynamicSet;
 import com.google.gerrit.extensions.restapi.RestView;
-import com.google.gerrit.server.ChangeDraftUpdate;
 import com.google.gerrit.server.CurrentUser;
 import com.google.gerrit.server.DefaultRefLogIdentityProvider;
 import com.google.gerrit.server.IdentifiedUser;
@@ -73,9 +72,9 @@
 import com.google.gerrit.server.git.ChangesByProjectCache;
 import com.google.gerrit.server.git.PureRevertCache;
 import com.google.gerrit.server.git.TagCache;
-import com.google.gerrit.server.notedb.ChangeDraftNotesUpdate;
 import com.google.gerrit.server.notedb.NoteDbModule;
 import com.google.gerrit.server.patch.DiffExecutorModule;
+import com.google.gerrit.server.patch.DiffOperationsForCommitValidation;
 import com.google.gerrit.server.patch.DiffOperationsImpl;
 import com.google.gerrit.server.patch.PatchListCacheImpl;
 import com.google.gerrit.server.permissions.DefaultPermissionBackendModule;
@@ -114,7 +113,7 @@
 
 /** Module for programs that perform batch operations on a site. */
 public class BatchProgramModule extends FactoryModule {
-  private Injector parentInjector;
+  private final Injector parentInjector;
 
   public BatchProgramModule(Injector parentInjector) {
     this.parentInjector = parentInjector;
@@ -164,6 +163,7 @@
     bind(CurrentUser.class).to(InternalUser.class);
     factory(PatchSetInserter.Factory.class);
     factory(RebaseChangeOp.Factory.class);
+    factory(DiffOperationsForCommitValidation.Factory.class);
 
     bind(new TypeLiteral<ImmutableSet<GroupReference>>() {})
         .annotatedWith(AdministrateServerGroups.class)
@@ -204,7 +204,6 @@
     factory(DistinctVotersPredicate.Factory.class);
     factory(HasSubmoduleUpdatePredicate.Factory.class);
     factory(ProjectState.Factory.class);
-    bind(ChangeDraftUpdate.ChangeDraftUpdateFactory.class).to(ChangeDraftNotesUpdate.Factory.class);
 
     DynamicMap.mapOf(binder(), ChangeQueryBuilder.ChangeOperatorFactory.class);
     DynamicMap.mapOf(binder(), ChangeQueryBuilder.ChangeHasOperandFactory.class);
diff --git a/java/com/google/gerrit/pgm/util/SiteProgram.java b/java/com/google/gerrit/pgm/util/SiteProgram.java
index ff0b31e..aeaa1d6 100644
--- a/java/com/google/gerrit/pgm/util/SiteProgram.java
+++ b/java/com/google/gerrit/pgm/util/SiteProgram.java
@@ -42,7 +42,6 @@
 import com.google.inject.util.Providers;
 import java.nio.file.Files;
 import java.nio.file.Path;
-import java.nio.file.Paths;
 import java.util.ArrayList;
 import java.util.List;
 import org.kohsuke.args4j.Option;
@@ -53,10 +52,10 @@
       aliases = {"-d"},
       usage = "Local directory containing site data")
   void setSitePath(String path) {
-    sitePath = Paths.get(path).normalize();
+    sitePath = Path.of(path).normalize();
   }
 
-  private Path sitePath = Paths.get(".");
+  private Path sitePath = Path.of(".");
 
   protected SiteProgram() {}
 
diff --git a/java/com/google/gerrit/server/StarredChangesUtil.java b/java/com/google/gerrit/pgm/util/package-info.java
similarity index 64%
copy from java/com/google/gerrit/server/StarredChangesUtil.java
copy to java/com/google/gerrit/pgm/util/package-info.java
index 0709b86..3bd1fd9 100644
--- a/java/com/google/gerrit/server/StarredChangesUtil.java
+++ b/java/com/google/gerrit/pgm/util/package-info.java
@@ -1,4 +1,4 @@
-// Copyright (C) 2015 The Android Open Source Project
+// Copyright (C) 2023 The Android Open Source Project
 //
 // Licensed under the Apache License, Version 2.0 (the "License");
 // you may not use this file except in compliance with the License.
@@ -12,8 +12,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package com.google.gerrit.server;
+@CheckReturnValue
+package com.google.gerrit.pgm.util;
 
-// TODO - delete this class. It cannot be deleted as part of the original refactoring since it's
-// used by a plugin.
-public interface StarredChangesUtil extends StarredChangesReader, StarredChangesWriter {}
+import com.google.errorprone.annotations.CheckReturnValue;
diff --git a/java/com/google/gerrit/prettify/BUILD b/java/com/google/gerrit/prettify/BUILD
index a5c8b77..5b07849 100644
--- a/java/com/google/gerrit/prettify/BUILD
+++ b/java/com/google/gerrit/prettify/BUILD
@@ -10,5 +10,6 @@
         "//lib:jgit",
         "//lib/auto:auto-value",
         "//lib/auto:auto-value-annotations",
+        "//lib/errorprone:annotations",
     ],
 )
diff --git a/java/com/google/gerrit/server/StarredChangesUtil.java b/java/com/google/gerrit/prettify/common/package-info.java
similarity index 64%
copy from java/com/google/gerrit/server/StarredChangesUtil.java
copy to java/com/google/gerrit/prettify/common/package-info.java
index 0709b86..5f70f74 100644
--- a/java/com/google/gerrit/server/StarredChangesUtil.java
+++ b/java/com/google/gerrit/prettify/common/package-info.java
@@ -1,4 +1,4 @@
-// Copyright (C) 2015 The Android Open Source Project
+// Copyright (C) 2023 The Android Open Source Project
 //
 // Licensed under the Apache License, Version 2.0 (the "License");
 // you may not use this file except in compliance with the License.
@@ -12,8 +12,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package com.google.gerrit.server;
+@CheckReturnValue
+package com.google.gerrit.prettify.common;
 
-// TODO - delete this class. It cannot be deleted as part of the original refactoring since it's
-// used by a plugin.
-public interface StarredChangesUtil extends StarredChangesReader, StarredChangesWriter {}
+import com.google.errorprone.annotations.CheckReturnValue;
diff --git a/java/com/google/gerrit/prettify/common/testing/BUILD b/java/com/google/gerrit/prettify/common/testing/BUILD
index 5057fdb..ecc28ff 100644
--- a/java/com/google/gerrit/prettify/common/testing/BUILD
+++ b/java/com/google/gerrit/prettify/common/testing/BUILD
@@ -9,6 +9,7 @@
     deps = [
         "//java/com/google/gerrit/prettify:server",
         "//lib:guava",
+        "//lib/errorprone:annotations",
         "//lib/truth",
     ],
 )
diff --git a/java/com/google/gerrit/server/StarredChangesUtil.java b/java/com/google/gerrit/prettify/common/testing/package-info.java
similarity index 64%
copy from java/com/google/gerrit/server/StarredChangesUtil.java
copy to java/com/google/gerrit/prettify/common/testing/package-info.java
index 0709b86..430ba69 100644
--- a/java/com/google/gerrit/server/StarredChangesUtil.java
+++ b/java/com/google/gerrit/prettify/common/testing/package-info.java
@@ -1,4 +1,4 @@
-// Copyright (C) 2015 The Android Open Source Project
+// Copyright (C) 2023 The Android Open Source Project
 //
 // Licensed under the Apache License, Version 2.0 (the "License");
 // you may not use this file except in compliance with the License.
@@ -12,8 +12,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package com.google.gerrit.server;
+@CheckReturnValue
+package com.google.gerrit.prettify.common.testing;
 
-// TODO - delete this class. It cannot be deleted as part of the original refactoring since it's
-// used by a plugin.
-public interface StarredChangesUtil extends StarredChangesReader, StarredChangesWriter {}
+import com.google.errorprone.annotations.CheckReturnValue;
diff --git a/java/com/google/gerrit/proto/BUILD b/java/com/google/gerrit/proto/BUILD
index 98558c5..82af646 100644
--- a/java/com/google/gerrit/proto/BUILD
+++ b/java/com/google/gerrit/proto/BUILD
@@ -6,5 +6,6 @@
     visibility = ["//visibility:public"],
     deps = [
         "//lib:protobuf",
+        "//lib/errorprone:annotations",
     ],
 )
diff --git a/java/com/google/gerrit/server/StarredChangesUtil.java b/java/com/google/gerrit/proto/package-info.java
similarity index 64%
copy from java/com/google/gerrit/server/StarredChangesUtil.java
copy to java/com/google/gerrit/proto/package-info.java
index 0709b86..8c4522f 100644
--- a/java/com/google/gerrit/server/StarredChangesUtil.java
+++ b/java/com/google/gerrit/proto/package-info.java
@@ -1,4 +1,4 @@
-// Copyright (C) 2015 The Android Open Source Project
+// Copyright (C) 2023 The Android Open Source Project
 //
 // Licensed under the Apache License, Version 2.0 (the "License");
 // you may not use this file except in compliance with the License.
@@ -12,8 +12,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package com.google.gerrit.server;
+@CheckReturnValue
+package com.google.gerrit.proto;
 
-// TODO - delete this class. It cannot be deleted as part of the original refactoring since it's
-// used by a plugin.
-public interface StarredChangesUtil extends StarredChangesReader, StarredChangesWriter {}
+import com.google.errorprone.annotations.CheckReturnValue;
diff --git a/java/com/google/gerrit/proto/testing/BUILD b/java/com/google/gerrit/proto/testing/BUILD
index 069bb46..17a2eaf 100644
--- a/java/com/google/gerrit/proto/testing/BUILD
+++ b/java/com/google/gerrit/proto/testing/BUILD
@@ -9,6 +9,7 @@
     deps = [
         "//lib:guava",
         "//lib/commons:lang3",
+        "//lib/errorprone:annotations",
         "//lib/guice",
         "//lib/truth",
     ],
diff --git a/java/com/google/gerrit/server/StarredChangesUtil.java b/java/com/google/gerrit/proto/testing/package-info.java
similarity index 64%
copy from java/com/google/gerrit/server/StarredChangesUtil.java
copy to java/com/google/gerrit/proto/testing/package-info.java
index 0709b86..6f19454 100644
--- a/java/com/google/gerrit/server/StarredChangesUtil.java
+++ b/java/com/google/gerrit/proto/testing/package-info.java
@@ -1,4 +1,4 @@
-// Copyright (C) 2015 The Android Open Source Project
+// Copyright (C) 2023 The Android Open Source Project
 //
 // Licensed under the Apache License, Version 2.0 (the "License");
 // you may not use this file except in compliance with the License.
@@ -12,8 +12,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package com.google.gerrit.server;
+@CheckReturnValue
+package com.google.gerrit.proto.testing;
 
-// TODO - delete this class. It cannot be deleted as part of the original refactoring since it's
-// used by a plugin.
-public interface StarredChangesUtil extends StarredChangesReader, StarredChangesWriter {}
+import com.google.errorprone.annotations.CheckReturnValue;
diff --git a/java/com/google/gerrit/server/BUILD b/java/com/google/gerrit/server/BUILD
index 000f095..96d888a 100644
--- a/java/com/google/gerrit/server/BUILD
+++ b/java/com/google/gerrit/server/BUILD
@@ -105,7 +105,7 @@
         "//lib/jsoup",
         "//lib/log:log4j",
         "//lib/lucene:lucene-analyzers-common",
-        "//lib/lucene:lucene-core-and-backward-codecs",
+        "//lib/lucene:lucene-core",
         "//lib/lucene:lucene-queryparser",
         "//lib/mime4j:core",
         "//lib/mime4j:dom",
diff --git a/java/com/google/gerrit/server/ChangeDraftUpdate.java b/java/com/google/gerrit/server/ChangeDraftUpdate.java
index eb33fb5..d9cea31 100644
--- a/java/com/google/gerrit/server/ChangeDraftUpdate.java
+++ b/java/com/google/gerrit/server/ChangeDraftUpdate.java
@@ -21,6 +21,7 @@
 import com.google.gerrit.server.notedb.ChangeNotes;
 import java.time.Instant;
 import java.util.List;
+import java.util.Optional;
 import org.eclipse.jgit.lib.PersonIdent;
 
 /** An interface for updating draft comments. */
@@ -49,11 +50,7 @@
    * Marks a comment for deletion. Called when the comment is deleted because the user published it.
    *
    * <p>NOTE for implementers: The actual deletion of a published draft should only happen after the
-   * published comment is successfully updated. For more context, see {@link
-   * com.google.gerrit.server.notedb.NoteDbUpdateManager#execute(boolean)}.
-   *
-   * <p>TODO(nitzan) - add generalized support for the above sync issue. The implementation should
-   * support deletion of published drafts from multiple ChangeDraftUpdateFactory instances.
+   * published comment is successfully updated. Please use {@link ChangeDraftUpdateExecutor}.
    */
   void markDraftCommentAsPublished(HumanComment c);
 
@@ -67,4 +64,24 @@
    * comments storage and the drafts one.
    */
   void addAllDraftCommentsForDeletion(List<Comment> comments);
+
+  /** Whether all updates in this updater can run asynchronously. */
+  boolean canRunAsync();
+
+  /**
+   * A unique identifier for the draft, used by the storage system. For example, NoteDB's ref name.
+   */
+  String getStorageKey();
+
+  /**
+   * Converts this update to the given subtype if possible. Returns {@link Optional#empty()}
+   * otherwise.
+   */
+  default <UpdateT extends ChangeDraftUpdate> Optional<UpdateT> toOptionalChangeDraftUpdateSubtype(
+      Class<UpdateT> subtype) {
+    if (this.getClass().isAssignableFrom(subtype)) {
+      return Optional.of((UpdateT) this);
+    }
+    return Optional.empty();
+  }
 }
diff --git a/java/com/google/gerrit/server/ChangeDraftUpdateExecutor.java b/java/com/google/gerrit/server/ChangeDraftUpdateExecutor.java
new file mode 100644
index 0000000..8c932fc
--- /dev/null
+++ b/java/com/google/gerrit/server/ChangeDraftUpdateExecutor.java
@@ -0,0 +1,129 @@
+// Copyright (C) 2023 The Android Open Source Project
+//
+// Licensed 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 com.google.gerrit.server;
+
+import static com.google.common.collect.ImmutableList.toImmutableList;
+
+import com.google.common.collect.ListMultimap;
+import com.google.common.collect.MultimapBuilder;
+import com.google.gerrit.common.Nullable;
+import com.google.gerrit.entities.Change;
+import com.google.gerrit.server.update.BatchUpdateListener;
+import java.io.IOException;
+import java.util.Collection;
+import java.util.Optional;
+import org.eclipse.jgit.lib.BatchRefUpdate;
+import org.eclipse.jgit.lib.PersonIdent;
+import org.eclipse.jgit.transport.PushCertificate;
+
+/**
+ * An interface for executing updates of multiple {@link ChangeDraftUpdate} instances.
+ *
+ * <p>Expected usage flow:
+ *
+ * <ol>
+ *   <li>Inject an instance of {@link AbstractFactory}.
+ *   <li>Create an instance of this interface using the factory.
+ *   <li>Call ({@link #queueAllDraftUpdates} or {@link #queueDeletionForChangeDrafts} for all
+ *       expected updates. The changes are marked to be executed either synchronously or
+ *       asynchronously, based on {@link #canRunAsync}.
+ *   <li>Call both {@link #executeAllSyncUpdates} and {@link #executeAllAsyncUpdates} methods.
+ *       Running these methods with no pending updates is a no-op.
+ * </ol>
+ */
+public interface ChangeDraftUpdateExecutor {
+  interface AbstractFactory {
+    // Guice cannot bind either:
+    // - A parameterized entity.
+    // - A factory creating an interface (rather than a class).
+    // To overcome this - we declare the create method in this non-parameterized interface, then
+    // extend it with a factory returning an actual class.
+    ChangeDraftUpdateExecutor create(CurrentUser currentUser);
+  }
+
+  interface Factory<T extends ChangeDraftUpdateExecutor> extends AbstractFactory {
+    @Override
+    T create(CurrentUser currentUser);
+  }
+
+  /**
+   * Queues all provided updates for later execution.
+   *
+   * <p>The updates are queued to either run synchronously just after change repositories updates,
+   * or to run asynchronously afterwards, based on {@link #canRunAsync}.
+   */
+  void queueAllDraftUpdates(ListMultimap<String, ChangeDraftUpdate> updates) throws IOException;
+
+  /**
+   * Extracts all drafts (of all authors) for the given change and queue their deletion.
+   *
+   * <p>See {@link #canRunAsync} for whether the deletions are scheduled as synchronous or
+   * asynchronous.
+   */
+  void queueDeletionForChangeDrafts(Change.Id id) throws IOException;
+
+  /**
+   * Execute all previously queued sync updates.
+   *
+   * <p>NOTE that {@link BatchUpdateListener#beforeUpdateRefs} events are not fired by this method.
+   * post-update events can be fired by the caller only for implementations that return a valid
+   * {@link BatchRefUpdate}.
+   *
+   * @param dryRun whether this is a dry run - i.e. no updates should be made
+   * @param refLogIdent user to log as the update creator
+   * @param refLogMessage message to put in the updates log
+   * @return the executed update, if supported by the implementing class
+   * @throws IOException in case of an update failure.
+   */
+  Optional<BatchRefUpdate> executeAllSyncUpdates(
+      boolean dryRun, @Nullable PersonIdent refLogIdent, @Nullable String refLogMessage)
+      throws IOException;
+
+  /**
+   * Execute all previously queued async updates.
+   *
+   * @param refLogIdent user to log as the update creator
+   * @param refLogMessage message to put in the updates log
+   * @param pushCert to use for the update
+   */
+  void executeAllAsyncUpdates(
+      @Nullable PersonIdent refLogIdent,
+      @Nullable String refLogMessage,
+      @Nullable PushCertificate pushCert);
+
+  /** Returns whether any updates are queued. */
+  boolean isEmpty();
+
+  /** Returns the given updates that match the provided type. */
+  default <UpdateT extends ChangeDraftUpdate> ListMultimap<String, UpdateT> filterTypedUpdates(
+      ListMultimap<String, ChangeDraftUpdate> updates, Class<UpdateT> updateType) {
+    ListMultimap<String, UpdateT> res = MultimapBuilder.hashKeys().arrayListValues().build();
+    for (String key : updates.keySet()) {
+      res.putAll(
+          key,
+          updates.get(key).stream()
+              .map(u -> u.toOptionalChangeDraftUpdateSubtype(updateType))
+              .filter(Optional::isPresent)
+              .map(Optional::get)
+              .collect(toImmutableList()));
+    }
+    return res;
+  }
+
+  /** Returns whether all provided updates can run asynchronously. */
+  default boolean canRunAsync(Collection<? extends ChangeDraftUpdate> updates) {
+    return updates.stream().allMatch(u -> u.canRunAsync());
+  }
+}
diff --git a/java/com/google/gerrit/server/ChangeMessagesUtil.java b/java/com/google/gerrit/server/ChangeMessagesUtil.java
index 400da58..7e8271b 100644
--- a/java/com/google/gerrit/server/ChangeMessagesUtil.java
+++ b/java/com/google/gerrit/server/ChangeMessagesUtil.java
@@ -16,6 +16,7 @@
 
 import static com.google.common.collect.ImmutableSet.toImmutableSet;
 
+import com.google.errorprone.annotations.CanIgnoreReturnValue;
 import com.google.gerrit.common.Nullable;
 import com.google.gerrit.entities.Account;
 import com.google.gerrit.entities.ChangeMessage;
@@ -83,6 +84,7 @@
    * @return message built from {@code messageTemplate}. Templates are replaced, so it might contain
    *     user identifiable information.
    */
+  @CanIgnoreReturnValue
   public String setChangeMessage(
       ChangeUpdate update, String messageTemplate, @Nullable String tag) {
     update.setChangeMessage(messageTemplate);
@@ -91,6 +93,7 @@
   }
 
   /** See {@link #setChangeMessage(ChangeUpdate, String, String)}. */
+  @CanIgnoreReturnValue
   public String setChangeMessage(ChangeContext ctx, String messageTemplate, @Nullable String tag) {
     return setChangeMessage(
         ctx.getUpdate(ctx.getChange().currentPatchSetId()), messageTemplate, tag);
diff --git a/java/com/google/gerrit/server/ChangeUtil.java b/java/com/google/gerrit/server/ChangeUtil.java
index dd86f88..5b78658 100644
--- a/java/com/google/gerrit/server/ChangeUtil.java
+++ b/java/com/google/gerrit/server/ChangeUtil.java
@@ -146,7 +146,8 @@
             Constants.encode("tree " + ObjectId.zeroId().name() + "\n\n" + newCommitMessage));
 
     // Check that the commit message without footers is not empty
-    CommitMessageUtil.checkAndSanitizeCommitMessage(revCommit.getShortMessage());
+    @SuppressWarnings("unused")
+    var unused = CommitMessageUtil.checkAndSanitizeCommitMessage(revCommit.getShortMessage());
 
     List<String> changeIdFooters = getChangeIdsFromFooter(revCommit);
     if (requireChangeId && changeIdFooters.isEmpty()) {
diff --git a/java/com/google/gerrit/server/CommentVerifier.java b/java/com/google/gerrit/server/CommentVerifier.java
new file mode 100644
index 0000000..b6e4321
--- /dev/null
+++ b/java/com/google/gerrit/server/CommentVerifier.java
@@ -0,0 +1,52 @@
+// Copyright (C) 2023 The Android Open Source Project
+//
+// Licensed 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 com.google.gerrit.server;
+
+import static com.google.common.base.Preconditions.checkArgument;
+import static com.google.common.base.Preconditions.checkState;
+
+import com.google.errorprone.annotations.CanIgnoreReturnValue;
+import com.google.gerrit.entities.Account;
+import com.google.gerrit.entities.Comment;
+import org.eclipse.jgit.lib.PersonIdent;
+
+/** Verifier for {@link Comment} objects */
+public final class CommentVerifier {
+  public static void verify(
+      Comment c, Account.Id accountId, Account.Id realAccountId, PersonIdent authorIdent) {
+    checkArgument(c.getCommitId() != null, "commit ID required for comment: %s", c);
+    checkAccountId(accountId, authorIdent);
+    checkArgument(
+        c.author.getId().equals(accountId),
+        "The author for the following comment does not match the author of this CommentVerifier (%s): %s",
+        accountId,
+        c);
+    checkArgument(
+        c.getRealAuthor().getId().equals(realAccountId),
+        "The real author for the following comment does not match the real"
+            + " author of this CommentVerifier (%s): %s",
+        realAccountId,
+        c);
+  }
+
+  @CanIgnoreReturnValue
+  private static Account.Id checkAccountId(Account.Id accountId, PersonIdent authorIdent) {
+    checkState(
+        accountId != null,
+        "author identity for CommentVerifier is not from an IdentifiedUser: %s",
+        authorIdent.toExternalString());
+    return accountId;
+  }
+}
diff --git a/java/com/google/gerrit/server/CommentsUtil.java b/java/com/google/gerrit/server/CommentsUtil.java
index 9d5d46a..30b8747 100644
--- a/java/com/google/gerrit/server/CommentsUtil.java
+++ b/java/com/google/gerrit/server/CommentsUtil.java
@@ -21,20 +21,24 @@
 import static java.util.stream.Collectors.toList;
 
 import com.google.common.collect.ComparisonChain;
+import com.google.common.collect.ImmutableList;
 import com.google.common.collect.Lists;
 import com.google.common.collect.Ordering;
 import com.google.gerrit.common.Nullable;
 import com.google.gerrit.entities.Change;
 import com.google.gerrit.entities.ChangeMessage;
 import com.google.gerrit.entities.Comment;
+import com.google.gerrit.entities.FixReplacement;
+import com.google.gerrit.entities.FixSuggestion;
 import com.google.gerrit.entities.HumanComment;
-import com.google.gerrit.entities.Patch;
 import com.google.gerrit.entities.PatchSet;
 import com.google.gerrit.entities.Project;
 import com.google.gerrit.entities.RobotComment;
 import com.google.gerrit.exceptions.StorageException;
 import com.google.gerrit.extensions.client.Side;
 import com.google.gerrit.extensions.common.CommentInfo;
+import com.google.gerrit.extensions.common.FixReplacementInfo;
+import com.google.gerrit.extensions.common.FixSuggestionInfo;
 import com.google.gerrit.server.config.GerritServerId;
 import com.google.gerrit.server.git.GitRepositoryManager;
 import com.google.gerrit.server.notedb.ChangeNotes;
@@ -132,7 +136,8 @@
       short side,
       String message,
       @Nullable Boolean unresolved,
-      @Nullable String parentUuid) {
+      @Nullable String parentUuid,
+      @Nullable List<FixSuggestion> fixSuggestions) {
     if (unresolved == null) {
       if (parentUuid == null) {
         // Default to false if comment is not descended from another.
@@ -157,6 +162,7 @@
             serverId,
             unresolved);
     c.parentUuid = parentUuid;
+    c.fixSuggestions = fixSuggestions;
     currentUser.updateRealAccountId(c::setRealAuthor);
     return c;
   }
@@ -209,13 +215,8 @@
     return robotCommentsByChange(notes).stream().filter(c -> c.key.uuid.equals(uuid)).findFirst();
   }
 
-  public List<HumanComment> publishedByChangeFile(ChangeNotes notes, String file) {
-    return commentsOnFile(notes.load().getHumanComments().values(), file);
-  }
-
   public List<HumanComment> publishedByPatchSet(ChangeNotes notes, PatchSet.Id psId) {
-    return removeCommentsOnAncestorOfCommitMessage(
-        commentsOnPatchSet(notes.load().getHumanComments().values(), psId));
+    return commentsOnPatchSet(notes.load().getHumanComments().values(), psId);
   }
 
   public List<RobotComment> robotCommentsByPatchSet(ChangeNotes notes, PatchSet.Id psId) {
@@ -298,18 +299,6 @@
         Optional.ofNullable(cm.getAuthor()).map(a -> a.get()),
         Optional.ofNullable(comment.author).map(a -> a._accountId));
   }
-  /**
-   * For the commit message the A side in a diff view is always empty when a comparison against an
-   * ancestor is done, so there can't be any comments on this ancestor. However earlier we showed
-   * the auto-merge commit message on side A when for a merge commit a comparison against the
-   * auto-merge was done. From that time there may still be comments on the auto-merge commit
-   * message and those we want to filter out.
-   */
-  private List<HumanComment> removeCommentsOnAncestorOfCommitMessage(List<HumanComment> list) {
-    return list.stream()
-        .filter(c -> c.side != 0 || !Patch.COMMIT_MSG.equals(c.key.filename))
-        .collect(toList());
-  }
 
   public void putHumanComments(
       ChangeUpdate update, Comment.Status status, Iterable<HumanComment> comments) {
@@ -335,18 +324,6 @@
     update.deleteCommentByRewritingHistory(commentKey.uuid, newMessage);
   }
 
-  private static List<HumanComment> commentsOnFile(
-      Collection<HumanComment> allComments, String file) {
-    List<HumanComment> result = new ArrayList<>(allComments.size());
-    for (HumanComment c : allComments) {
-      String currentFilename = c.key.filename;
-      if (currentFilename.equals(file)) {
-        result.add(c);
-      }
-    }
-    return sort(result);
-  }
-
   private static <T extends Comment> List<T> commentsOnPatchSet(
       Collection<T> allComments, PatchSet.Id psId) {
     List<T> result = new ArrayList<>(allComments.size());
@@ -429,4 +406,35 @@
     comments.sort(COMMENT_ORDER);
     return comments;
   }
+
+  @Nullable
+  public static ImmutableList<FixSuggestion> createFixSuggestionsFromInput(
+      List<FixSuggestionInfo> fixSuggestionInfos) {
+    if (fixSuggestionInfos == null) {
+      return null;
+    }
+
+    ImmutableList.Builder<FixSuggestion> fixSuggestions =
+        ImmutableList.builderWithExpectedSize(fixSuggestionInfos.size());
+    for (FixSuggestionInfo fixSuggestionInfo : fixSuggestionInfos) {
+      fixSuggestions.add(createFixSuggestionFromInput(fixSuggestionInfo));
+    }
+    return fixSuggestions.build();
+  }
+
+  public static FixSuggestion createFixSuggestionFromInput(FixSuggestionInfo fixSuggestionInfo) {
+    List<FixReplacement> fixReplacements = toFixReplacements(fixSuggestionInfo.replacements);
+    String fixId = ChangeUtil.messageUuid();
+    return new FixSuggestion(fixId, fixSuggestionInfo.description, fixReplacements);
+  }
+
+  public static List<FixReplacement> toFixReplacements(
+      List<FixReplacementInfo> fixReplacementInfos) {
+    return fixReplacementInfos.stream().map(CommentsUtil::toFixReplacement).collect(toList());
+  }
+
+  public static FixReplacement toFixReplacement(FixReplacementInfo fixReplacementInfo) {
+    Comment.Range range = new Comment.Range(fixReplacementInfo.range);
+    return new FixReplacement(fixReplacementInfo.path, range, fixReplacementInfo.replacement);
+  }
 }
diff --git a/java/com/google/gerrit/server/ReviewerStatusUpdate.java b/java/com/google/gerrit/server/ReviewerStatusUpdate.java
index 1e0aa43..5486359 100644
--- a/java/com/google/gerrit/server/ReviewerStatusUpdate.java
+++ b/java/com/google/gerrit/server/ReviewerStatusUpdate.java