diff --git a/.github/DEPENDENCY_DECISIONS.md b/.github/DEPENDENCY_DECISIONS.md
index d81f271..805739d 100644
--- a/.github/DEPENDENCY_DECISIONS.md
+++ b/.github/DEPENDENCY_DECISIONS.md
@@ -2,7 +2,7 @@
Initial review: 2026-09-26. Focused API follow-up: 2026-09-27. Dependabot
proposal-policy follow-up: 2026-09-28. Jackson and proposal-routing follow-up:
-2026-10-05.
+2026-10-05. Maven 3.10.0 wrapper follow-up: 2026-10-11.
PRs [#176](https://github.com/OWASP/owasp-java-encoder/pull/176) and
[#188](https://github.com/OWASP/owasp-java-encoder/pull/188) mixed ordinary build
@@ -100,6 +100,32 @@ platform brings the same or a newer Jackson, remove the pin, its ignore rules an
the policy assertion together. Enforcer rules do not inspect plugin realms, so a
stale exact pin would silently downgrade Boot's own Jackson.
+## Maven 3.10.0 wrapper — 2026-10-11
+
+Accept the Maven **3.10.0** distribution proposed by Dependabot PR
+[#235](https://github.com/OWASP/owasp-java-encoder/pull/235). That PR failed only
+on the wrapper version assertion and re-committed `mvnw.cmd` with CRLF in the index,
+so a separate change completes the checklist above. The wrapper scripts remain
+Apache Maven Wrapper 3.3.4 and `mvnw.cmd` is unchanged. The signing profile now
+requires exactly 3.10.0. Ordinary builds keep the `[3.9.16,)` minimum.
+
+The ZIP's SHA-256 matches the wrapper properties and the SHA-512 published by
+Apache, and its detached signature verifies against a key in Apache Maven's
+`KEYS` file. Maven 3.10.0 moves to Resolver 2, breadth-first classpath ordering,
+stricter model validation, origin-scoped repository credentials and a Super POM
+without plugin management or `release-profile`
+([release notes](https://maven.apache.org/docs/3.10.0/release-notes.html)).
+This build already declares its plugin versions, `attach-sources`/`attach-javadocs`
+executions, encodings and `outputTimestamp`. Effective POMs from 3.9.16 and 3.10.0
+differ only in two unused Super POM plugin-management entries (assembly and
+release) and, with `performRelease=true`, a redundant `true`
+on the source, Javadoc and deploy plugins. Payload evidence is in the
+[Maven 3.10.0 validation](../releases/maven-3.10.0-validation.md).
+
+Credential scoping is the one release-path change this review cannot exercise
+without the project's Central token. The first signed `clean deploy` under 3.10.0
+should confirm that staging still authenticates before the release relies on it.
+
## Deferred proposals and reconsideration conditions
| Proposal | Disposition and required evidence before reconsideration |
diff --git a/.mvn/wrapper/maven-wrapper.properties b/.mvn/wrapper/maven-wrapper.properties
index a3c407f..e193f5b 100644
--- a/.mvn/wrapper/maven-wrapper.properties
+++ b/.mvn/wrapper/maven-wrapper.properties
@@ -1,4 +1,4 @@
wrapperVersion=3.3.4
distributionType=only-script
-distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.16/apache-maven-3.9.16-bin.zip
-distributionSha256Sum=5af3b743dd8b876b5c45da33b676251e5f1687712644abb4ee519ca56e1d89ce
+distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.10.0/apache-maven-3.10.0-bin.zip
+distributionSha256Sum=1f6d9909266510f039f59aa0e13dcd2c66da85f043e56276e41f2918f8bddaff
diff --git a/BUILDING.md b/BUILDING.md
index 63b9b8c..5953bba 100644
--- a/BUILDING.md
+++ b/BUILDING.md
@@ -2,9 +2,9 @@
Use JDK 17+ and the committed `./mvnw` (`mvnw.cmd` on Windows). The wrapper is
Apache Maven Wrapper 3.3.4's unmodified `only-script` distribution; no wrapper
-JAR executes before the download check. It pins Maven 3.9.16 and its SHA-256.
+JAR executes before the download check. It pins Maven 3.10.0 and its SHA-256.
The downloaded Maven ZIP was independently compared to the SHA-512 served at
-[Apache's distribution site](https://downloads.apache.org/maven/maven-3/3.9.16/binaries/).
+[Apache's distribution site](https://downloads.apache.org/maven/maven-3/3.10.0/binaries/).
Wrapper archive SHA-256: `6cb584c2bc907b849a0b931d8266d3ff3214cdd3127115ed4f49fb7176413d36`.
Both bootstrap paths are CI gates, including a deliberately wrong checksum with
an empty wrapper cache. `.mvn/maven.config` requires strict transfer checksums.
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 9eeef1f..fff7cca 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -20,6 +20,10 @@ unchanged. [1.5.0 is available from Central](releases/1.5.0-central-publication.
- Route Jackson plugin-realm pins and Maven wrapper upgrades out of the grouped
Dependabot PR for individual review, and keep Jackson on its 3.1 line until the
Boot parent moves. Security updates remain eligible.
+- Build with Maven 3.10.0 through the committed wrapper and require it for signed
+ releases. Ordinary builds still accept Maven 3.9.16 or later. Library JARs and
+ module POMs are byte-identical to 3.9.16 builds
+ ([validation](releases/maven-3.10.0-validation.md)).
## 1.5.0 — 2026-09-28 UTC
diff --git a/README.md b/README.md
index d44cfff..8ece5e0 100644
--- a/README.md
+++ b/README.md
@@ -134,7 +134,7 @@ container or downstream dependency combination.
## Building and contributing
-Build with JDK 17 and the committed wrapper (Maven 3.9.16); use `mvnw.cmd` on Windows:
+Build with JDK 17 and the committed wrapper (Maven 3.10.0); use `mvnw.cmd` on Windows:
```sh
./mvnw clean verify
diff --git a/RELEASING.md b/RELEASING.md
index 835487f..9e331ac 100644
--- a/RELEASING.md
+++ b/RELEASING.md
@@ -55,7 +55,7 @@ existing Maven version.
## Prepare and verify
1. Use a clean checkout, **Eclipse Temurin 17.0.20.1+1** and the committed
- wrapper (**Maven 3.9.16**). The signing profile enforces vendor, exact runtime
+ wrapper (**Maven 3.10.0**). The signing profile enforces vendor, exact runtime
and Maven versions, and a non-SNAPSHOT project version. Ordinary builds allow
JDK 17+; the release path is stricter because javac output changes on newer
JDKs. Record the full Git commit and operating system/architecture. Build with a fresh Maven local repository so
diff --git a/pom.xml b/pom.xml
index 5c98830..495bbeb 100644
--- a/pom.xml
+++ b/pom.xml
@@ -642,7 +642,7 @@
Eclipse Adoptium
java.runtime.version17\.0\.20\.1\+1
- [3.9.16]
+ [3.10.0]
diff --git a/releases/maven-3.10.0-validation.md b/releases/maven-3.10.0-validation.md
new file mode 100644
index 0000000..c5f5ec4
--- /dev/null
+++ b/releases/maven-3.10.0-validation.md
@@ -0,0 +1,83 @@
+# Maven 3.10.0 wrapper validation — 2026-10-11
+
+This record supports the toolchain decision in the
+[dependency decision record](../.github/DEPENDENCY_DECISIONS.md) and replaces
+Dependabot PR [#235](https://github.com/OWASP/owasp-java-encoder/pull/235).
+The local checks below ran on macOS 27.0.1 (26A434) arm64. Windows wrapper
+bootstrap and the other runtime jobs are checked by required PR CI and are not
+claimed here.
+
+## Distribution
+
+`apache-maven-3.10.0-bin.zip` from `downloads.apache.org` and from Maven Central
+are byte-identical. Its SHA-512 matches the `.sha512` file Apache publishes, and its
+SHA-256 `1f6d9909266510f039f59aa0e13dcd2c66da85f043e56276e41f2918f8bddaff`
+matches `distributionSha256Sum`. The detached signature is a good signature from
+key `88BE34F94BDB2B5357044E2E3A387D43964143E3` (Tamas Cservenak, made
+2026-09-27), checked in an isolated keyring that held only Apache Maven's `KEYS`.
+`KEYS` and the ZIP come from the same publisher, so the signature check does not
+independently establish publisher identity. The wrapper scripts stay Apache Maven
+Wrapper 3.3.4, and `mvnw.cmd` is unchanged and LF in the index.
+
+## Build and wrapper
+
+With 3.10.0, `./mvnw -B -ntp clean verify` passed with Homebrew OpenJDK 17.0.20.1
+and a warm local repository: 2,280 unit tests, seven integration tests,
+Checkstyle, API and Java 8 signature checks, coverage and both packaged JSP
+engines. `python3 scripts/check-wrapper.py` bootstrapped 3.10.0 from an empty
+wrapper cache and rejected an incorrect checksum. All 35 policy tests passed.
+
+Resolver 2 fetches `.meta/prefixes.txt` from every remote repository it consults.
+The legacy `jvnet-nexus-releases` repository declared by `net.java:jvnet-parent:5`
+fails that request on TLS certificate validation. That produces one warning per
+build and no failed resolution, because Central serves every artifact.
+
+## Effective POMs
+
+`help:effective-pom` from 3.9.16 and 3.10.0 was compared per module and per
+plugin, both by default and with `-DperformRelease=true`. The only differences are
+the Super POM's plugin-management entries for the unused assembly and release
+plugins and, in release mode, a redundant `true` on the
+source, Javadoc and deploy plugins. That second difference comes from the Super
+POM `release-profile` that 3.10.0 removes. Plugin versions, executions, goals,
+phases, configuration, properties, dependencies and repositories are unchanged.
+
+## Reproducibility
+
+`scripts/check-reproducible.py` ran with reference Eclipse Temurin 17.0.20.1+1,
+whose archive matched its published SHA-256
+`196d13ba5f10414bef7f6a05a9b3f00edacb18ebacef2b99485db9e2ee18f0e8`. Each run
+used two fresh `git archive` exports with separate empty Maven repositories and
+wrapper caches.
+
+- Implementation commit `07b92e07b2444f6c4b6a08f3af44c720a61defd1` (Maven
+ 3.10.0) produced identical copies of all 13 payloads.
+- Compared with a build of `main` at
+ `ee1e2a88893b43c5b494dcc86cb83be4d0d68660` (Maven 3.9.16), all nine JARs and
+ three module POMs are byte-identical. The parent POM differs only in the
+ release enforcer's `[3.9.16]` → `[3.10.0]` line.
+
+| Payload | SHA-256 (Maven 3.10.0, both builds) |
+| --- | --- |
+| `encoder-1.5.1-SNAPSHOT-javadoc.jar` | `1dea3061f4ed78c79672472889a84793c4fb1c48bde1a5766389314aa0a9c176` |
+| `encoder-1.5.1-SNAPSHOT-sources.jar` | `6eba5c35b89c9d4a8c457f9e774e29560084b2223b1b6799f29eea166bf17f32` |
+| `encoder-1.5.1-SNAPSHOT.jar` | `a10cd22386265d8e631ac7793e8a5eb9645a95ae050ec76d0c916d43b9b6291b` |
+| `encoder-1.5.1-SNAPSHOT.pom` | `19791032b4a05bdfc855bf4c476437cda8251784b6aa1ebaba77e783bbaf0f66` |
+| `encoder-jakarta-jsp-1.5.1-SNAPSHOT-javadoc.jar` | `498f81f2f618452d9b5e180223a17b8128a26c469ad66223e249a568860d02d1` |
+| `encoder-jakarta-jsp-1.5.1-SNAPSHOT-sources.jar` | `69ff116828ee2db0223b4d509a9486b653a3984d78787af4ebac8bd3cc4a1b77` |
+| `encoder-jakarta-jsp-1.5.1-SNAPSHOT.jar` | `463131114b7d34e2f4627918ecba0d18b53ac9b274945062d3fe04c2ab589836` |
+| `encoder-jakarta-jsp-1.5.1-SNAPSHOT.pom` | `ddbfb208fabcc84ceb9cef0ce0e62ea55987eee57d73f6c0de9c192ec154538d` |
+| `encoder-jsp-1.5.1-SNAPSHOT-javadoc.jar` | `77b56a2071429e122967647f4512098010bab6c3043b6b30ccf58b36192a1718` |
+| `encoder-jsp-1.5.1-SNAPSHOT-sources.jar` | `94193083573ef1752c12284f37e5cf59a5901ce5d7cd80032dc915afe7deccb7` |
+| `encoder-jsp-1.5.1-SNAPSHOT.jar` | `fb8d852f656f408697245a9d0aae19035057dbdcf5742d25cf3994c990549c2c` |
+| `encoder-jsp-1.5.1-SNAPSHOT.pom` | `0b35d49c56a8c8a6746c09146ccd79a6be2ed10f6b5cc8da2e024f77396f3575` |
+| `encoder-parent-1.5.1-SNAPSHOT.pom` | `061a61e632b989732a7ba2db1cc9383e75deaedcf182ff42b15ab349e2052060` |
+
+Later documentation-only commits on this change do not alter artifact inputs.
+
+## Not exercised
+
+No signing, Central Portal staging, tag or release operation ran. Maven 3.10.0
+scopes repository credentials to their origins. The first signed `clean deploy`
+under 3.10.0 should confirm that Central staging still authenticates with the
+`central` server entry.
diff --git a/scripts/check-reproducible.py b/scripts/check-reproducible.py
index 0ad03fc..be1edbd 100644
--- a/scripts/check-reproducible.py
+++ b/scripts/check-reproducible.py
@@ -66,7 +66,7 @@ def main():
raise ValueError('Expected thirteen release payload files')
results.append(hashes)
differences = [name for name in results[0] if results[0][name] != results[1][name]]
- report = {'commit': commit, 'java': props, 'maven': '3.9.16',
+ report = {'commit': commit, 'java': props, 'maven': '3.10.0',
'hashes': results, 'differences': differences}
(directory / 'comparison.json').write_text(json.dumps(report, indent=2) + '\n')
require_identical(results[0], results[1])
diff --git a/scripts/check-wrapper.py b/scripts/check-wrapper.py
index ad603ad..61b1884 100644
--- a/scripts/check-wrapper.py
+++ b/scripts/check-wrapper.py
@@ -32,7 +32,7 @@ def bootstrap(directory, bad_hash):
if result.returncode == 0 or 'SHA-256' not in result.stdout:
raise ValueError('Checksum rejection did not work: ' + result.stdout)
else:
- if result.returncode != 0 or 'Apache Maven 3.9.16' not in result.stdout:
+ if result.returncode != 0 or 'Apache Maven 3.10.0' not in result.stdout:
raise ValueError('Wrapper bootstrap failed: ' + result.stdout)
print(('Rejected incorrect checksum' if bad_hash else 'Verified bootstrap'), WRAPPER)