diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 47bcad9b5..a1637e754 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -18,7 +18,7 @@ jobs:
lint:
timeout-minutes: 15
name: lint
- runs-on: 'ubuntu-latest'
+ runs-on: ${{ github.repository == 'anthropics/anthropic-sdk-java' && 'ubuntu-latest-16core' || 'ubuntu-latest-16-core' }}
if: (github.event_name == 'push' || github.event.pull_request.head.repo.fork)
steps:
@@ -31,21 +31,25 @@ jobs:
java-version: |
8
21
- cache: gradle
- name: Set up Gradle
uses: gradle/actions/setup-gradle@ed408507eac070d1f99cc633dbcf757c94c7933a # v4.4.3
+ with:
+ # Required for configuration-cache data to be saved/restored across CI runs.
+ cache-encryption-key: ${{ secrets.GRADLE_ENCRYPTION_KEY }}
- name: Run lints
run: ./scripts/lint
build:
- timeout-minutes: 15
+ # The first run on a fresh cache compiles everything and generates all documentation, which
+ # does not fit in 15 minutes; cached runs are much faster.
+ timeout-minutes: 30
name: build
permissions:
contents: read
id-token: write
- runs-on: 'ubuntu-latest'
+ runs-on: ${{ github.repository == 'anthropics/anthropic-sdk-java' && 'ubuntu-latest-16core' || 'ubuntu-latest-16-core' }}
if: (github.event_name == 'push' || github.event.pull_request.head.repo.fork)
steps:
@@ -58,10 +62,12 @@ jobs:
java-version: |
8
21
- cache: gradle
- name: Set up Gradle
uses: gradle/actions/setup-gradle@ed408507eac070d1f99cc633dbcf757c94c7933a # v4.4.3
+ with:
+ # Required for configuration-cache data to be saved/restored across CI runs.
+ cache-encryption-key: ${{ secrets.GRADLE_ENCRYPTION_KEY }}
- name: Build SDK
run: ./scripts/build
@@ -94,7 +100,7 @@ jobs:
test:
timeout-minutes: 30
name: test
- runs-on: 'ubuntu-latest'
+ runs-on: ${{ github.repository == 'anthropics/anthropic-sdk-java' && 'ubuntu-latest-16core' || 'ubuntu-latest-16-core' }}
if: github.event_name == 'push' || github.event.pull_request.head.repo.fork
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
@@ -106,10 +112,12 @@ jobs:
java-version: |
8
21
- cache: gradle
- name: Set up Gradle
- uses: gradle/gradle-build-action@a8f75513eafdebd8141bd1cd4e30fcd194af8dfa # v2.12.0
+ uses: gradle/actions/setup-gradle@ed408507eac070d1f99cc633dbcf757c94c7933a # v4.4.3
+ with:
+ # Required for configuration-cache data to be saved/restored across CI runs.
+ cache-encryption-key: ${{ secrets.GRADLE_ENCRYPTION_KEY }}
- name: Run tests
run: ./scripts/test
@@ -117,7 +125,7 @@ jobs:
detect_breaking_changes_vs_main:
timeout-minutes: 15
name: detect-breaking-changes-vs-main
- runs-on: ${{ github.repository == 'stainless-sdks/anthropic-java' && 'depot-ubuntu-24.04' || 'ubuntu-latest' }}
+ runs-on: ${{ github.repository == 'anthropics/anthropic-sdk-java' && 'ubuntu-latest-16core' || 'ubuntu-latest-16-core' }}
if: |-
(github.event_name == 'push' &&
!startsWith(github.ref, 'refs/heads/release-please--')) ||
@@ -140,10 +148,12 @@ jobs:
java-version: |
8
21
- cache: gradle
- name: Set up Gradle
uses: gradle/actions/setup-gradle@ed408507eac070d1f99cc633dbcf757c94c7933a # v4.4.3
+ with:
+ # Required for configuration-cache data to be saved/restored across CI runs.
+ cache-encryption-key: ${{ secrets.GRADLE_ENCRYPTION_KEY }}
- name: Determine base SHA
run: |
diff --git a/.github/workflows/create-releases.yml b/.github/workflows/create-releases.yml
index 935219780..0cfc1d2dd 100644
--- a/.github/workflows/create-releases.yml
+++ b/.github/workflows/create-releases.yml
@@ -10,7 +10,7 @@ jobs:
release:
name: release
if: github.ref == 'refs/heads/main' && github.repository == 'anthropics/anthropic-sdk-java'
- runs-on: ubuntu-latest
+ runs-on: ubuntu-latest-16-core
environment: production-release
steps:
@@ -30,11 +30,10 @@ jobs:
java-version: |
8
21
- cache: gradle
- name: Set up Gradle
if: ${{ steps.release.outputs.releases_created }}
- uses: gradle/gradle-build-action@a8f75513eafdebd8141bd1cd4e30fcd194af8dfa # v2.12.0
+ uses: gradle/actions/setup-gradle@ed408507eac070d1f99cc633dbcf757c94c7933a # v4.4.3
- name: Publish to Sonatype
if: ${{ steps.release.outputs.releases_created }}
diff --git a/.github/workflows/publish-sonatype.yml b/.github/workflows/publish-sonatype.yml
index 565e4fd0f..85d7b88cb 100644
--- a/.github/workflows/publish-sonatype.yml
+++ b/.github/workflows/publish-sonatype.yml
@@ -7,7 +7,7 @@ on:
jobs:
publish:
name: publish
- runs-on: ubuntu-latest
+ runs-on: ubuntu-latest-16-core
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
@@ -19,10 +19,9 @@ jobs:
java-version: |
8
21
- cache: gradle
- name: Set up Gradle
- uses: gradle/gradle-build-action@a8f75513eafdebd8141bd1cd4e30fcd194af8dfa # v2.12.0
+ uses: gradle/actions/setup-gradle@ed408507eac070d1f99cc633dbcf757c94c7933a # v4.4.3
- name: Publish to Sonatype
run: |-
@@ -34,4 +33,4 @@ jobs:
SONATYPE_USERNAME: ${{ secrets.ANTHROPIC_SONATYPE_USERNAME || secrets.SONATYPE_USERNAME }}
SONATYPE_PASSWORD: ${{ secrets.ANTHROPIC_SONATYPE_PASSWORD || secrets.SONATYPE_PASSWORD }}
GPG_SIGNING_KEY: ${{ secrets.ANTHROPIC_SONATYPE_GPG_SIGNING_KEY || secrets.GPG_SIGNING_KEY }}
- GPG_SIGNING_PASSWORD: ${{ secrets.ANTHROPIC_SONATYPE_GPG_SIGNING_PASSWORD || secrets.GPG_SIGNING_PASSWORD }}
\ No newline at end of file
+ GPG_SIGNING_PASSWORD: ${{ secrets.ANTHROPIC_SONATYPE_GPG_SIGNING_PASSWORD || secrets.GPG_SIGNING_PASSWORD }}
diff --git a/.release-please-manifest.json b/.release-please-manifest.json
index 8d572e9c9..29ad411ba 100644
--- a/.release-please-manifest.json
+++ b/.release-please-manifest.json
@@ -1,3 +1,3 @@
{
- ".": "2.40.1"
+ ".": "2.41.0"
}
\ No newline at end of file
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 1005cef25..414acc07e 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,14 @@
# Changelog
+## 2.41.0 (2026-06-11)
+
+Full Changelog: [v2.40.1...v2.41.0](https://github.com/anthropics/anthropic-sdk-java/compare/v2.40.1...v2.41.0)
+
+### Build System
+
+* fix runners ([#66](https://github.com/anthropics/anthropic-sdk-java/issues/66)) ([19882bb](https://github.com/anthropics/anthropic-sdk-java/commit/19882bbada3e1ccbd1cb4fc10b011801cd0865fa))
+* speed up local and CI builds ([#62](https://github.com/anthropics/anthropic-sdk-java/issues/62)) ([78de617](https://github.com/anthropics/anthropic-sdk-java/commit/78de617c06dc8fb31327ec4682649a613860b348))
+
## 2.40.1 (2026-06-09)
Full Changelog: [v2.40.0...v2.40.1](https://github.com/anthropics/anthropic-sdk-java/compare/v2.40.0...v2.40.1)
diff --git a/README.md b/README.md
index 1d1082a75..30d58e81f 100644
--- a/README.md
+++ b/README.md
@@ -15,7 +15,7 @@ Full documentation is available at **[platform.claude.com/docs/en/api/sdks/java]
### Gradle
```kotlin
-implementation("com.anthropic:anthropic-java:2.40.1")
+implementation("com.anthropic:anthropic-java:2.41.0")
```
### Maven
@@ -24,7 +24,7 @@ implementation("com.anthropic:anthropic-java:2.40.1")
com.anthropic
anthropic-java
- 2.40.1
+ 2.41.0
```
diff --git a/anthropic-java-proguard-test/build.gradle.kts b/anthropic-java-proguard-test/build.gradle.kts
index 1038c2368..7ec112802 100644
--- a/anthropic-java-proguard-test/build.gradle.kts
+++ b/anthropic-java-proguard-test/build.gradle.kts
@@ -31,9 +31,10 @@ val proguardJarPath = "${layout.buildDirectory.get()}/libs/${project.name}-${pro
val proguardJar by tasks.registering(proguard.gradle.ProGuardTask::class) {
group = "verification"
dependsOn(tasks.shadowJar)
- notCompatibleWithConfigurationCache("ProGuard")
- injars(tasks.shadowJar)
+ // Pass the archive path rather than the task itself: `Task` objects cannot
+ // be serialized to the configuration cache.
+ injars(tasks.shadowJar.get().archiveFile.get().asFile.absolutePath)
outjars(proguardJarPath)
printmapping("${layout.buildDirectory.get()}/proguard-mapping.txt")
@@ -57,40 +58,56 @@ val proguardJar by tasks.registering(proguard.gradle.ProGuardTask::class) {
val testProGuard by tasks.registering(JavaExec::class) {
group = "verification"
dependsOn(proguardJar)
- notCompatibleWithConfigurationCache("ProGuard")
mainClass.set("com.anthropic.proguard.ProGuardCompatibilityTest")
classpath = files(proguardJarPath)
+
+ // This is a verification task with no file outputs, so rerun it only when
+ // the JAR changes.
+ outputs.upToDateWhen { true }
}
val r8JarPath = "${layout.buildDirectory.get()}/libs/${project.name}-${project.version}-r8.jar"
val r8Jar by tasks.registering(JavaExec::class) {
group = "verification"
dependsOn(tasks.shadowJar)
- notCompatibleWithConfigurationCache("R8")
mainClass.set("com.android.tools.r8.R8")
classpath = buildscript.configurations["classpath"]
+ val proguardConfigs = listOf(
+ "./test.pro",
+ "../anthropic-java-core/src/main/resources/META-INF/proguard/anthropic-java-core.pro",
+ )
+
args = listOf(
"--release",
"--classfile",
"--output", r8JarPath,
"--lib", System.getProperty("java.home"),
- "--pg-conf", "./test.pro",
- "--pg-conf", "../anthropic-java-core/src/main/resources/META-INF/proguard/anthropic-java-core.pro",
+ "--pg-conf", proguardConfigs[0],
+ "--pg-conf", proguardConfigs[1],
"--pg-map-output", "${layout.buildDirectory.get()}/r8-mapping.txt",
tasks.shadowJar.get().archiveFile.get().asFile.absolutePath,
)
+
+ // `args` are not tracked as task inputs, so declare them explicitly for
+ // up-to-date checking.
+ inputs.files(tasks.shadowJar.map { it.archiveFile })
+ inputs.files(proguardConfigs)
+ outputs.file(r8JarPath)
}
val testR8 by tasks.registering(JavaExec::class) {
group = "verification"
dependsOn(r8Jar)
- notCompatibleWithConfigurationCache("R8")
mainClass.set("com.anthropic.proguard.ProGuardCompatibilityTest")
classpath = files(r8JarPath)
+
+ // This is a verification task with no file outputs, so rerun it only when
+ // the JAR changes.
+ outputs.upToDateWhen { true }
}
tasks.test {
diff --git a/anthropic-java/build.gradle.kts b/anthropic-java/build.gradle.kts
index 62c8f1d88..0c96e0772 100644
--- a/anthropic-java/build.gradle.kts
+++ b/anthropic-java/build.gradle.kts
@@ -7,23 +7,37 @@ dependencies {
api(project(":anthropic-java-client-okhttp"))
}
-// Redefine `dokkaJavadoc` to:
-// - Depend on the root project's task for merging the docs of all the projects
-// - Forward that task's output to this task's output
-tasks.named("dokkaJavadoc").configure {
- actions.clear()
+// This module's javadoc JAR must document the API of every module it
+// re-exports, so add each module's main sources to this module's `dokkaJavadoc`
+// task as extra source sets.
+tasks.named("dokkaJavadoc").configure {
+ // Run after every other module's `dokkaJavadoc`: this task's documentation generation is by
+ // far the largest, and Dokka generates in-process, so running it concurrently with another
+ // large generation can exhaust the Gradle daemon's heap.
+ rootProject.subprojects
+ .filter { it.name != project.name }
+ .forEach { subproject -> mustRunAfter(subproject.tasks.matching { it.name == "dokkaJavadoc" }) }
- val dokkaJavadocCollector = rootProject.tasks["dokkaJavadocCollector"]
- dependsOn(dokkaJavadocCollector)
-
- val outputDirectory = project.layout.buildDirectory.dir("dokka/javadoc")
- doLast {
- copy {
- from(dokkaJavadocCollector.outputs.files)
- into(outputDirectory)
- duplicatesStrategy = DuplicatesStrategy.INCLUDE
- }
+ dokkaSourceSets {
+ rootProject.subprojects
+ .filter { it.file("src/main/kotlin").exists() }
+ .sortedBy { it.name }
+ .forEach { subproject ->
+ register(subproject.name) {
+ sourceRoots.from(
+ listOf("src/main/kotlin", "src/main/java")
+ .map(subproject::file)
+ .filter { it.exists() }
+ )
+ // Resolve lazily: sibling projects may not be configured
+ // yet when this runs.
+ classpath.from(
+ project.provider {
+ subproject.configurations.getByName("compileClasspath")
+ }
+ )
+ jdkVersion.set(8)
+ }
+ }
}
-
- outputs.dir(outputDirectory)
}
diff --git a/build.gradle.kts b/build.gradle.kts
index 07b458e32..55a823e49 100644
--- a/build.gradle.kts
+++ b/build.gradle.kts
@@ -8,7 +8,7 @@ repositories {
allprojects {
group = "com.anthropic"
- version = "2.40.1" // x-release-please-version
+ version = "2.41.0" // x-release-please-version
}
subprojects {
@@ -26,10 +26,3 @@ subprojects {
subprojects {
apply(plugin = "org.jetbrains.dokka")
}
-
-// Avoid race conditions between `dokkaJavadocCollector` and `dokkaJavadocJar` tasks
-tasks.named("dokkaJavadocCollector").configure {
- subprojects.flatMap { it.tasks }
- .filter { it.project.name != "anthropic-java" && it.name == "dokkaJavadocJar" }
- .forEach { mustRunAfter(it) }
-}
diff --git a/buildSrc/src/main/kotlin/anthropic.java.gradle.kts b/buildSrc/src/main/kotlin/anthropic.java.gradle.kts
index 0239e2013..76f8009a7 100644
--- a/buildSrc/src/main/kotlin/anthropic.java.gradle.kts
+++ b/buildSrc/src/main/kotlin/anthropic.java.gradle.kts
@@ -36,7 +36,6 @@ tasks.withType().configureEach {
// Run tests in parallel to some degree.
maxParallelForks = (Runtime.getRuntime().availableProcessors() / 2).coerceAtLeast(1)
- forkEvery = 100
// Mockito's ByteBuddy agent emits a JVM warning when loaded dynamically. Tests that capture
// stderr (e.g. LoggingHttpClientTest) see this warning interleaved with their expected output
@@ -49,6 +48,15 @@ tasks.withType().configureEach {
}
}
+dependencies {
+ // SLF4J lazily initializes on the first `LoggerFactory.getLogger` call in
+ // the JVM and, without a provider, prints a warning to stderr. That warning
+ // corrupts tests that capture and assert on exact stderr contents when
+ // another test races the initialization. Binding a no-op provider keeps
+ // SLF4J silent.
+ "testRuntimeOnly"("org.slf4j:slf4j-nop:2.0.16")
+}
+
val palantir by configurations.creating
dependencies {
palantir("com.palantir.javaformat:palantir-java-format:2.89.0")
diff --git a/gradle.properties b/gradle.properties
index 6680f9ce9..f4c160ba6 100644
--- a/gradle.properties
+++ b/gradle.properties
@@ -1,18 +1,26 @@
org.gradle.caching=true
org.gradle.configuration-cache=true
org.gradle.parallel=true
-org.gradle.daemon=false
-# These options improve our compilation and test performance. They are inherited by the Kotlin daemon.
+org.gradle.daemon=true
+# JVM flags for the Gradle daemon, chosen from measurements:
+# - Heaps are capped so that the Gradle daemon, Kotlin daemon, and test workers
+# fit together in a 16 GB GitHub Actions runner; exceeding the runner's memory
+# gets the job killed mid-run. The Gradle daemon needs the most headroom
+# because Dokka generates documentation in-process, and a cold build can run
+# two large generations concurrently (a 3 GB heap OOMs).
+# - ParallelGC gives a measured ~2% faster clean build than the default G1.
+# - Both daemons peak at ~200 MB of JIT-compiled code, which overflows the
+# default 240 MB reserved code cache once it is split into segments, so
+# reserve more (reserved != committed, so this costs nothing up front).
+# - The metaspace cap (default: unlimited) is a guard against runaway memory on
+# CI, not a speedup; the daemons use 100-260 MB.
org.gradle.jvmargs=\
- -Xms2g \
- -Xmx8g \
+ -Xmx6g \
-XX:+UseParallelGC \
- -XX:InitialCodeCacheSize=256m \
- -XX:ReservedCodeCacheSize=1G \
- -XX:MetaspaceSize=512m \
- -XX:MaxMetaspaceSize=2G \
- -XX:TieredStopAtLevel=1 \
- -XX:GCTimeRatio=4 \
- -XX:CICompilerCount=4 \
- -XX:+OptimizeStringConcat \
- -XX:+UseStringDeduplication
+ -XX:ReservedCodeCacheSize=512m \
+ -XX:MaxMetaspaceSize=1G
+kotlin.daemon.jvmargs=\
+ -Xmx4g \
+ -XX:+UseParallelGC \
+ -XX:ReservedCodeCacheSize=512m \
+ -XX:MaxMetaspaceSize=1G