diff --git a/.github/workflows/links.yml b/.github/workflows/links.yml
new file mode 100644
index 00000000..5e3255ba
--- /dev/null
+++ b/.github/workflows/links.yml
@@ -0,0 +1,32 @@
+name: Links
+
+on:
+ push:
+ pull_request:
+ schedule:
+ - cron: "0 13 * * 1" # weekly, to catch external link rot without a commit
+ workflow_dispatch:
+
+permissions:
+ contents: read
+
+jobs:
+ linkChecker:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
+ with:
+ persist-credentials: false
+
+ - name: Setup mise
+ uses: jdx/mise-action@6d1e696aa24c1aa1bcc1adea0212707c71ab78a8 # v3.6.1
+ with:
+ install: false
+
+ # Install only lychee (not the repo's full toolchain) and run the check.
+ - name: Check links
+ env:
+ MISE_AUTO_INSTALL: "false"
+ run: |
+ mise install lychee
+ mise run check-links
diff --git a/.gitignore b/.gitignore
index dd0e2411..effe3689 100644
--- a/.gitignore
+++ b/.gitignore
@@ -9,6 +9,7 @@
.claude
.gh-pages
.idea
+.lycheecache
docs/.hugo_build.lock
docs/public/
.pmd
diff --git a/README.dev.md b/README.dev.md
index 1cda7587..4de2a532 100644
--- a/README.dev.md
+++ b/README.dev.md
@@ -19,9 +19,9 @@
To get this access, first create a Central Portal account. You will then need
access to our namespace, but we have not added anyone since switching to
-Central Portal. Previously you would need to make an account on the [Sonatype
-JIRA issue tracker](https://issues.sonatype.org/) and make an issue asking for
-access [like so](https://issues.sonatype.org/browse/OSSRH-34414).
+Central Portal. Previously you would need to make an account on the Sonatype
+JIRA issue tracker and make an issue asking for access. That tracker has since
+been [retired](https://central.sonatype.org/faq/what-happened-to-issues-sonatype-org/).
Ensure you inform MaxMind operations about your new access.
@@ -52,7 +52,7 @@ and enter `trust` and choose ultimate.
Make sure the key shows up in `gpg --list-secret-keys`.
Make sure you publish it to a keyserver. See
-[here](http://central.sonatype.org/pages/working-with-pgp-signatures.html)
+[here](https://central.sonatype.org/publish/requirements/gpg/)
for more info about that and the process in general.
### gpg "inappropriate ioctl" errors
diff --git a/README.md b/README.md
index e6fab87a..23d47296 100644
--- a/README.md
+++ b/README.md
@@ -327,7 +327,7 @@ Please report all issues with this code using the
If you are having an issue with the minFraud service that is not specific
to the client API, please see
-[our support page](https://www.maxmind.com/en/support).
+[our support page](https://support.maxmind.com/knowledge-base).
## Requirements ##
diff --git a/lychee.toml b/lychee.toml
new file mode 100644
index 00000000..f51c4df8
--- /dev/null
+++ b/lychee.toml
@@ -0,0 +1,67 @@
+# Lychee link checker configuration
+# https://lychee.cli.rs/#/usage/config
+#
+# Run locally with:
+# lychee './**/*.md' './src/**/*.java' './pom.xml'
+
+# Include URL fragments in checks
+include_fragments = true
+
+# Don't allow any redirects, so links that have moved are surfaced and updated
+# to their canonical destination.
+max_redirects = 0
+
+# Accept these HTTP status codes
+# 100-103: Informational responses
+# 200-299: Success responses
+# 403: Forbidden (some sites use this for rate limiting)
+# 429: Too Many Requests
+# 500-599: Server errors (temporary issues shouldn't fail CI)
+# 999: LinkedIn's custom status code
+accept = ["100..=103", "200..=299", "403", "429", "500..=599", "999"]
+
+# Exclude URL patterns from checking (treated as regular expressions)
+exclude = [
+ '^file://',
+ # Live / auth-gated endpoints that appear as string literals or require login
+ '^https://geoip\.maxmind\.com',
+ '^https://geolite\.info',
+ '^https://minfraud\.maxmind\.com',
+ '^https://sandbox\.maxmind\.com',
+ '^https://updates\.maxmind\.com',
+ '^https://www\.maxmind\.com/en/accounts/',
+ 'https://www\.maxmind\.com/en/account/login',
+ # XML namespace identifiers in pom.xml (not real links)
+ '^http://www\.w3\.org/',
+ '^http://maven\.apache\.org/',
+ '^https://maven\.apache\.org/xsd/',
+ '^http://java\.sun\.com/',
+ '^http://schemas\.',
+ # Maven property placeholder in a build-time download URL (not a real link)
+ 'japicmp\.baselineVersion',
+ # dev.maxmind.com docs render anchors client-side, so lychee cannot verify
+ # the #schema-... fragment even though the page (a clean 200) contains it.
+ '^https://dev\.maxmind\.com/minfraud/api-documentation/responses/#',
+ # Placeholders / local
+ '^https?://example\.(com|org|net)',
+ '^http://localhost',
+ '127\.0\.0\.1',
+]
+
+# Exclude file paths from getting checked (treated as regular expressions)
+exclude_path = [
+ '(^|/)node_modules/',
+ '(^|/)target/',
+ '(^|/)\.git/',
+ # Test code and fixtures contain example URLs (e.g. www.example.com), not ours
+ '(^|/)src/test/',
+ # Changelog: historical entries are preserved as-is, not rewritten
+ '(^|/)CHANGELOG\.md$',
+]
+
+# Cache results for 1 day to speed up repeated checks
+cache = true
+max_cache_age = "1d"
+
+# Skip missing input files instead of erroring
+skip_missing = true
diff --git a/mise.lock b/mise.lock
index 9b150a1f..a1cae619 100644
--- a/mise.lock
+++ b/mise.lock
@@ -36,6 +36,34 @@ url = "https://github.com/gohugoio/hugo/releases/download/v0.161.1/hugo_0.161.1_
version = "26.0.1"
backend = "core:java"
+[[tools.lychee]]
+version = "0.23.0"
+backend = "aqua:lycheeverse/lychee"
+
+[tools.lychee."platforms.linux-arm64"]
+checksum = "sha256:97eb93b02a7d78a752fc33e5b0983439ccaadbf3db952b68a0a4401acd92e6e0"
+url = "https://github.com/lycheeverse/lychee/releases/download/lychee-v0.23.0/lychee-aarch64-unknown-linux-gnu.tar.gz"
+
+[tools.lychee."platforms.linux-arm64-musl"]
+checksum = "sha256:97eb93b02a7d78a752fc33e5b0983439ccaadbf3db952b68a0a4401acd92e6e0"
+url = "https://github.com/lycheeverse/lychee/releases/download/lychee-v0.23.0/lychee-aarch64-unknown-linux-gnu.tar.gz"
+
+[tools.lychee."platforms.linux-x64"]
+checksum = "sha256:5538440d2c69a45a0a09983271e5dee0c2fe7137d8035d25b2632e10a66a090a"
+url = "https://github.com/lycheeverse/lychee/releases/download/lychee-v0.23.0/lychee-x86_64-unknown-linux-musl.tar.gz"
+
+[tools.lychee."platforms.linux-x64-musl"]
+checksum = "sha256:5538440d2c69a45a0a09983271e5dee0c2fe7137d8035d25b2632e10a66a090a"
+url = "https://github.com/lycheeverse/lychee/releases/download/lychee-v0.23.0/lychee-x86_64-unknown-linux-musl.tar.gz"
+
+[tools.lychee."platforms.macos-arm64"]
+checksum = "sha256:4c8034900e11083b68ac6f6582c377ff1f704e268991999e09d717973e493e7f"
+url = "https://github.com/lycheeverse/lychee/releases/download/lychee-v0.23.0/lychee-arm64-macos.dmg"
+
+[tools.lychee."platforms.windows-x64"]
+checksum = "sha256:0fda7ff0a60c0250939fc25361c2d4e6e7853c31c996733fdd5a1dd760bcb824"
+url = "https://github.com/lycheeverse/lychee/releases/download/lychee-v0.23.0/lychee-x86_64-windows.exe"
+
[[tools.maven]]
version = "3.9.15"
backend = "aqua:apache/maven"
diff --git a/mise.toml b/mise.toml
index 4a11f83e..9ac550c4 100644
--- a/mise.toml
+++ b/mise.toml
@@ -9,6 +9,7 @@ disable_backends = [
[tools]
hugo = "latest"
java = "latest"
+lychee = "latest"
maven = "latest"
[tasks.build-docs]
@@ -19,6 +20,10 @@ run = "hugo --source docs --minify"
description = "Serve the docs site locally with Hugo dev server"
run = "hugo server --source docs"
+[tasks.check-links]
+description = "Check links with lychee"
+run = "lychee --no-progress './**/*.md' './src/**/*.java' './pom.xml'"
+
[hooks]
enter = "mise install --quiet --locked"
diff --git a/pom.xml b/pom.xml
index 91d321e4..027bf243 100644
--- a/pom.xml
+++ b/pom.xml
@@ -7,17 +7,17 @@
4.3.0
MaxMind minFraud API
MaxMind minFraud Score, Insights, Factors and Report Transaction API
- http://dev.maxmind.com/minfraud
+ https://dev.maxmind.com/minfraud/
Apache License, Version 2.0
- http://www.apache.org/licenses/LICENSE-2.0.html
+ https://www.apache.org/licenses/LICENSE-2.0.html
repo
MaxMind, Inc.
- http://www.maxmind.com/
+ https://www.maxmind.com/en/home
https://github.com/maxmind/minfraud-api-java
diff --git a/src/main/java/com/maxmind/minfraud/request/CustomInputs.java b/src/main/java/com/maxmind/minfraud/request/CustomInputs.java
index 4d72ae19..eee9ddd6 100644
--- a/src/main/java/com/maxmind/minfraud/request/CustomInputs.java
+++ b/src/main/java/com/maxmind/minfraud/request/CustomInputs.java
@@ -8,7 +8,8 @@
/**
* Custom inputs to be used in
- * Custom Rules.
+ * Custom
+ * Rules.
* In order to use custom inputs, you must set them up from your account portal.
*/
public final class CustomInputs extends AbstractModel {
diff --git a/src/main/java/com/maxmind/minfraud/response/Device.java b/src/main/java/com/maxmind/minfraud/response/Device.java
index 4a78f3a0..e52bb632 100644
--- a/src/main/java/com/maxmind/minfraud/response/Device.java
+++ b/src/main/java/com/maxmind/minfraud/response/Device.java
@@ -21,7 +21,7 @@
* date-time.
* @param localTime The date and time of the transaction at the UTC offset associated with the
* device. This is an RFC 3339 date-time.
- * @see Device Tracking Add-on
+ * @see Device Tracking Add-on
*/
public record Device(
@JsonProperty("confidence")
diff --git a/src/main/java/com/maxmind/minfraud/response/Warning.java b/src/main/java/com/maxmind/minfraud/response/Warning.java
index 4aaa0fb3..5fb077b2 100644
--- a/src/main/java/com/maxmind/minfraud/response/Warning.java
+++ b/src/main/java/com/maxmind/minfraud/response/Warning.java
@@ -35,8 +35,8 @@
* @param inputPointer This is a JSON Pointer to the input that the warning is associated with. For
* instance, if the warning was about the billing city, the value would be
* "/billing/city". See
- * RFC 6901 for the JSON
- * Pointer spec.
+ * RFC 6901 for the
+ * JSON Pointer spec.
*/
public record Warning(
@JsonProperty("code")
@@ -99,7 +99,8 @@ public String getWarning() {
* @return This is a JSON Pointer to the input that the warning is associated with. For
* instance, if the warning was about the billing city, the value would be "/billing/city".
* See
- * RFC 6901 for the JSON Pointer spec.
+ * RFC 6901 for the JSON Pointer
+ * spec.
* @deprecated Use {@link #inputPointer()} instead. This method will be removed in 5.0.0.
*/
@Deprecated(since = "4.0.0", forRemoval = true)