diff --git a/.github/workflows/java-publish-maven.yml b/.github/workflows/java-publish-maven.yml
index 944a0ee38..e293d9127 100644
--- a/.github/workflows/java-publish-maven.yml
+++ b/.github/workflows/java-publish-maven.yml
@@ -199,21 +199,9 @@ jobs:
working-directory: ./java
run: |
VERSION="${{ steps.versions.outputs.release_version }}"
-
- # Update release version in README.md (supports any version qualifier like -java.N, -java-preview.N, -beta-java.N)
- sed -i "s|[0-9][0-9]*\.[0-9][0-9]*\.[0-9][0-9]*\(-[a-z][a-z0-9-]*\.[0-9][0-9]*\)*|${VERSION}|g" README.md
- sed -i "s|copilot-sdk-java:[0-9][0-9]*\.[0-9][0-9]*\.[0-9][0-9]*\(-[a-z][a-z0-9-]*\.[0-9][0-9]*\)*|copilot-sdk-java:${VERSION}|g" README.md
-
- # Update snapshot versions in README.md (must run AFTER release version seds
- # because the release copilot-sdk-java: pattern partially matches inside snapshot
- # strings — the snapshot-specific seds override with the correct DEV_VERSION)
DEV_VERSION="${{ steps.versions.outputs.dev_version }}"
- sed -i "s|[0-9][0-9]*\.[0-9][0-9]*\.[0-9][0-9]*\(-[a-z][a-z0-9-]*\.[0-9][0-9]*\)*-SNAPSHOT|${DEV_VERSION}|g" README.md
- sed -i "s|copilot-sdk-java:[0-9][0-9]*\.[0-9][0-9]*\.[0-9][0-9]*\(-[a-z][a-z0-9-]*\.[0-9][0-9]*\)*-SNAPSHOT|copilot-sdk-java:${DEV_VERSION}|g" README.md
-
- # Update version in jbang-example.java
- sed -i "s|copilot-sdk-java:[0-9][0-9]*\.[0-9][0-9]*\.[0-9][0-9]*\(-[a-z][a-z0-9-]*\.[0-9][0-9]*\)*|copilot-sdk-java:${VERSION}|g" jbang-example.java
- sed -i 's|copilot-sdk-java:${project\.version}|copilot-sdk-java:'"${VERSION}"'|g' jbang-example.java
+ ./scripts/test-update-documentation-versions.sh
+ ./scripts/update-documentation-versions.sh "$VERSION" "$DEV_VERSION" README.md jbang-example.java
# Commit the documentation changes before release:prepare (requires clean working directory)
git add README.md jbang-example.java
diff --git a/.github/workflows/java-sdk-tests.yml b/.github/workflows/java-sdk-tests.yml
index c92b5291a..20083749e 100644
--- a/.github/workflows/java-sdk-tests.yml
+++ b/.github/workflows/java-sdk-tests.yml
@@ -45,6 +45,10 @@ jobs:
with:
node-version: 22
+ - name: Test documentation version updater
+ if: matrix.test-jdk == '25'
+ run: ./scripts/test-update-documentation-versions.sh
+
- name: Build SDK and set up test harness
run: mvn test-compile jar:jar
diff --git a/java/README.md b/java/README.md
index 87a0ef7c5..c3428eee0 100644
--- a/java/README.md
+++ b/java/README.md
@@ -32,14 +32,14 @@ Replace `${copilot.sdk.version}` with the latest release from Maven Central.
com.github
copilot-sdk-java
- 1.0.5-01
+ 1.0.9-preview.3
```
### Gradle
```groovy
-implementation 'com.github:copilot-sdk-java:1.0.9-preview.3-01'
+implementation 'com.github:copilot-sdk-java:1.0.9-preview.3'
```
#### Snapshot Builds
diff --git a/java/jbang-example.java b/java/jbang-example.java
index 59e625724..9abc39edd 100644
--- a/java/jbang-example.java
+++ b/java/jbang-example.java
@@ -1,5 +1,5 @@
///usr/bin/env jbang "$0" "$@" ; exit $?
-//DEPS com.github:copilot-sdk-java:1.0.9-preview.3-01
+//DEPS com.github:copilot-sdk-java:1.0.9-preview.3
import com.github.copilot.CopilotClient;
import com.github.copilot.generated.AssistantMessageEvent;
import com.github.copilot.generated.SessionUsageInfoEvent;
diff --git a/java/scripts/test-update-documentation-versions.sh b/java/scripts/test-update-documentation-versions.sh
new file mode 100755
index 000000000..213bda6c4
--- /dev/null
+++ b/java/scripts/test-update-documentation-versions.sh
@@ -0,0 +1,53 @@
+#!/usr/bin/env bash
+
+set -euo pipefail
+
+SCRIPT_DIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)
+UPDATER="${SCRIPT_DIR}/update-documentation-versions.sh"
+TEMP_DIR=$(mktemp -d)
+trap 'rm -rf "$TEMP_DIR"' EXIT
+
+run_case() {
+ local name=$1
+ local old_version=$2
+ local old_dev_version=$3
+ local version=$4
+ local dev_version=$5
+ local old_jbang_version=$6
+ local case_dir="${TEMP_DIR}/${name}"
+
+ mkdir "$case_dir"
+ printf '%s\n' \
+ '' \
+ ' copilot-sdk-java' \
+ " ${old_version}" \
+ '' \
+ "implementation 'com.github:copilot-sdk-java:${old_version}'" \
+ '' \
+ ' copilot-sdk-java' \
+ " ${old_dev_version}" \
+ '' \
+ "implementation 'com.github:copilot-sdk-java:${old_dev_version}'" \
+ > "${case_dir}/README.md"
+ printf '%s\n' \
+ "///usr/bin/env jbang \"\$0\" \"\$@\" ; exit \$?" \
+ "//DEPS com.github:copilot-sdk-java:${old_jbang_version}" \
+ > "${case_dir}/jbang-example.java"
+
+ "$UPDATER" "$version" "$dev_version" "${case_dir}/README.md" "${case_dir}/jbang-example.java"
+
+ grep -Fqx " ${version}" "${case_dir}/README.md"
+ grep -Fqx "implementation 'com.github:copilot-sdk-java:${version}'" "${case_dir}/README.md"
+ grep -Fqx " ${dev_version}" "${case_dir}/README.md"
+ grep -Fqx "implementation 'com.github:copilot-sdk-java:${dev_version}'" "${case_dir}/README.md"
+ grep -Fqx "//DEPS com.github:copilot-sdk-java:${version}" "${case_dir}/jbang-example.java"
+
+ if grep -Fq "$old_version" "${case_dir}/README.md" "${case_dir}/jbang-example.java" ||
+ grep -Fq "$old_dev_version" "${case_dir}/README.md"; then
+ echo "Stale version remained in ${name} test output" >&2
+ exit 1
+ fi
+}
+
+run_case stable 1.0.8 1.0.9-SNAPSHOT 1.0.9 1.0.10-SNAPSHOT "\${project.version}"
+run_case preview 1.0.9-preview.2-01 1.0.10-preview.2-SNAPSHOT 1.0.10-preview.2 1.0.11-preview.2-SNAPSHOT 1.0.9-preview.2-01
diff --git a/java/scripts/update-documentation-versions.sh b/java/scripts/update-documentation-versions.sh
new file mode 100755
index 000000000..d2d23c4ff
--- /dev/null
+++ b/java/scripts/update-documentation-versions.sh
@@ -0,0 +1,80 @@
+#!/usr/bin/env bash
+
+set -euo pipefail
+
+if [[ $# -ne 4 ]]; then
+ echo "Usage: $0 " >&2
+ exit 2
+fi
+
+VERSION=$1
+DEV_VERSION=$2
+README=$3
+JBANG_EXAMPLE=$4
+VERSION_FORMAT='[0-9]+\.[0-9]+\.[0-9]+(-(preview|(beta-)?java(-preview)?)\.[0-9]+)?'
+
+if [[ ! "$VERSION" =~ ^${VERSION_FORMAT}$ ]]; then
+ echo "Invalid release version: $VERSION" >&2
+ exit 2
+fi
+if [[ ! "$DEV_VERSION" =~ ^${VERSION_FORMAT}-SNAPSHOT$ ]]; then
+ echo "Invalid development version: $DEV_VERSION" >&2
+ exit 2
+fi
+if [[ ! -f "$README" || ! -f "$JBANG_EXAMPLE" ]]; then
+ echo "README and JBang example files must exist" >&2
+ exit 2
+fi
+
+export VERSION DEV_VERSION
+
+perl -0 - "$README" <<'PERL'
+use strict;
+use warnings;
+
+my ($path) = @ARGV;
+open my $input, '<', $path or die "Cannot read $path: $!\n";
+my $content = do { local $/; <$input> };
+close $input or die "Cannot close $path: $!\n";
+
+# Match accepted release versions plus numeric suffixes left by the former broken updater.
+my $version = qr/[0-9]+\.[0-9]+\.[0-9]+(?:-(?:preview|(?:beta-)?java(?:-preview)?)\.[0-9]+)?(?:-[0-9]+)*/;
+my $snapshot_xml = ($content =~ s{$version-SNAPSHOT}{$ENV{DEV_VERSION}}g);
+my $snapshot_gradle = ($content =~ s{(copilot-sdk-java:)$version-SNAPSHOT(?![-A-Za-z0-9.])}{$1 . $ENV{DEV_VERSION}}ge);
+my $release_xml = ($content =~ s{$version}{$ENV{VERSION}}g);
+my $release_gradle = ($content =~ s{(copilot-sdk-java:)$version(?![-A-Za-z0-9.])}{$1 . $ENV{VERSION}}ge);
+
+die "Expected one release and one snapshot example for both Maven and Gradle in $path\n"
+ unless $snapshot_xml == 1 && $snapshot_gradle == 1 && $release_xml == 1 && $release_gradle == 1;
+
+open my $output, '>', $path or die "Cannot write $path: $!\n";
+print {$output} $content;
+close $output or die "Cannot close $path: $!\n";
+PERL
+
+perl -0 - "$JBANG_EXAMPLE" <<'PERL'
+use strict;
+use warnings;
+
+my ($path) = @ARGV;
+open my $input, '<', $path or die "Cannot read $path: $!\n";
+my $content = do { local $/; <$input> };
+close $input or die "Cannot close $path: $!\n";
+
+my $version = qr/[0-9]+\.[0-9]+\.[0-9]+(?:-(?:preview|(?:beta-)?java(?:-preview)?)\.[0-9]+)?(?:-[0-9]+)*/;
+my $version_count = ($content =~ s{(copilot-sdk-java:)$version(?![-A-Za-z0-9.])}{$1 . $ENV{VERSION}}ge);
+my $placeholder_count = ($content =~ s{copilot-sdk-java:\$\{project\.version\}}{copilot-sdk-java:$ENV{VERSION}}g);
+
+die "Expected exactly one Copilot SDK dependency in $path\n"
+ unless $version_count + $placeholder_count == 1;
+
+open my $output, '>', $path or die "Cannot write $path: $!\n";
+print {$output} $content;
+close $output or die "Cannot close $path: $!\n";
+PERL
+
+grep -Fqx " ${VERSION}" "$README"
+grep -Fqx "implementation 'com.github:copilot-sdk-java:${VERSION}'" "$README"
+grep -Fqx " ${DEV_VERSION}" "$README"
+grep -Fqx "implementation 'com.github:copilot-sdk-java:${DEV_VERSION}'" "$README"
+grep -Fqx "//DEPS com.github:copilot-sdk-java:${VERSION}" "$JBANG_EXAMPLE"