Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .release-please-manifest.json
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
{
".": "2.28.0",
".": "2.29.0",
"anthropic-java-aws": "0.2.0"
}
23 changes: 23 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,28 @@
# Changelog

## 2.29.0 (2026-05-05)

Full Changelog: [v2.28.0...v2.29.0](https://github.com/anthropics/anthropic-sdk-java/compare/v2.28.0...v2.29.0)

### Features

* **client:** allow targeting a workspace for OIDC federation token exchange ([578003a](https://github.com/anthropics/anthropic-sdk-java/commit/578003afed53205ed23789845fc7ed0153ed1100))


### Performance Improvements

* **client:** create one json mapper ([556ef49](https://github.com/anthropics/anthropic-sdk-java/commit/556ef492e5a668f1964147b41808ca47a26bb906))


### Chores

* remove duplicated dokka setup ([d6b94f4](https://github.com/anthropics/anthropic-sdk-java/commit/d6b94f49fd06f0ceaf6293880e03e7ba95d95d41))


### Documentation

* remove bad semicolon ([ffb078b](https://github.com/anthropics/anthropic-sdk-java/commit/ffb078b8285c3a52c45502dabac5ed1890460db8))

## 2.28.0 (2026-05-04)

Full Changelog: [v2.27.0...v2.28.0](https://github.com/anthropics/anthropic-sdk-java/compare/v2.27.0...v2.28.0)
Expand Down
97 changes: 2 additions & 95 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.28.0")
implementation("com.anthropic:anthropic-java:2.29.0")
```

### Maven
Expand All @@ -24,7 +24,7 @@ implementation("com.anthropic:anthropic-java:2.28.0")
<dependency>
<groupId>com.anthropic</groupId>
<artifactId>anthropic-java</artifactId>
<version>2.28.0</version>
<version>2.29.0</version>
</dependency>
```

Expand All @@ -46,99 +46,6 @@ MessageCreateParams params = MessageCreateParams.builder()
.maxTokens(1024L)
.addUserMessage("Hello, Claude")
.model(Model.CLAUDE_OPUS_4_6)
.maxTokens(JsonMissing.of())
.build();
```

### Response properties

To access undocumented response properties, call the `_additionalProperties()` method:

```java
import com.anthropic.core.JsonValue;
import java.util.Map;

Map<String, JsonValue> additionalProperties = client.messages().create(params)._additionalProperties();
JsonValue secretPropertyValue = additionalProperties.get("secretProperty");

String result = secretPropertyValue.accept(new JsonValue.Visitor<>() {
@Override
public String visitNull() {
return "It's null!";
}

@Override
public String visitBoolean(boolean value) {
return "It's a boolean!";
}

@Override
public String visitNumber(Number value) {
return "It's a number!";
}

// Other methods include `visitMissing`, `visitString`, `visitArray`, and `visitObject`
// The default implementation of each unimplemented method delegates to `visitDefault`, which throws by default, but can also be overridden
});
```

To access a property's raw JSON value, which may be undocumented, call its `_` prefixed method:

```java
import com.anthropic.core.JsonField;
import java.util.Optional;

JsonField<Long> maxTokens = client.messages().create(params)._maxTokens();

if (maxTokens.isMissing()) {
// The property is absent from the JSON response
} else if (maxTokens.isNull()) {
// The property was set to literal null
} else {
// Check if value was provided as a string
// Other methods include `asNumber()`, `asBoolean()`, etc.
Optional<String> jsonString = maxTokens.asString();

// Try to deserialize into a custom type
MyClass myObject = maxTokens.asUnknown().orElseThrow().convert(MyClass.class);
}
```

### Response validation

In rare cases, the API may return a response that doesn't match the expected type. For example, the SDK may expect a property to contain a `String`, but the API could return something else.

By default, the SDK will not throw an exception in this case. It will throw [`AnthropicInvalidDataException`](anthropic-java-core/src/main/kotlin/com/anthropic/errors/AnthropicInvalidDataException.kt) only if you directly access the property.

Validating the response is _not_ forwards compatible with new types from the API for existing fields.

If you would still prefer to check that the response is completely well-typed upfront, then either call `validate()`:

```java
import com.anthropic.models.messages.Message;

Message message = client.messages().create(params).validate();
```

Or configure the method call to validate the response using the `responseValidation` method:

```java
import com.anthropic.models.messages.Message;

Message message = client.messages().create(
params, RequestOptions.builder().responseValidation(true).build()
);
```

Or configure the default for all method calls at the client level:

```java
import com.anthropic.client.AnthropicClient;
import com.anthropic.client.okhttp.AnthropicOkHttpClient;

AnthropicClient client = AnthropicOkHttpClient.builder()
.fromEnv()
.responseValidation(true)
.build();
Message message = client.messages().create(params);
```
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ private constructor(
val federationRuleId: String,
val organizationId: String,
val serviceAccountId: String?,
val workspaceId: String?,
)

fun applyCredentials(httpClient: HttpClient, clientOptionsBuilder: ClientOptions.Builder) {
Expand All @@ -75,6 +76,7 @@ private constructor(
config.federationRuleId,
config.organizationId,
config.serviceAccountId,
config.workspaceId,
httpClient,
jsonMapper,
)
Expand Down Expand Up @@ -203,6 +205,22 @@ private constructor(
federationRuleId: String,
organizationId: String,
serviceAccountId: String?,
) =
federationTokenProvider(
identityTokenProvider,
federationRuleId,
organizationId,
serviceAccountId,
null,
)

@JvmSynthetic
internal fun federationTokenProvider(
identityTokenProvider: IdentityTokenProvider,
federationRuleId: String,
organizationId: String,
serviceAccountId: String?,
workspaceId: String?,
) = apply {
clearCredentials()
federationConfig =
Expand All @@ -211,6 +229,7 @@ private constructor(
federationRuleId,
organizationId,
serviceAccountId,
workspaceId,
)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ private constructor(
private val envIdentityTokenFile: String? = null,
private val envIdentityToken: String? = null,
private val envServiceAccountId: String? = null,
private val envWorkspaceId: String? = null,
) : ProfileConfigProvider {

init {
Expand Down Expand Up @@ -53,6 +54,7 @@ private constructor(
envIdentityTokenFile,
envIdentityToken,
envServiceAccountId,
envWorkspaceId,
)

cachedConfig = config
Expand All @@ -77,6 +79,7 @@ private constructor(
private var envIdentityTokenFile: String? = null
private var envIdentityToken: String? = null
private var envServiceAccountId: String? = null
private var envWorkspaceId: String? = null

fun profile(profile: String) = apply { this.profile = profile }

Expand All @@ -94,12 +97,15 @@ private constructor(

fun envServiceAccountId(value: String?) = apply { this.envServiceAccountId = value }

fun envWorkspaceId(value: String?) = apply { this.envWorkspaceId = value }

fun fromEnv() = apply {
envFederationRuleId(System.getenv("ANTHROPIC_FEDERATION_RULE_ID"))
envOrganizationId(System.getenv("ANTHROPIC_ORGANIZATION_ID"))
envIdentityTokenFile(System.getenv("ANTHROPIC_IDENTITY_TOKEN_FILE"))
envIdentityToken(System.getenv("ANTHROPIC_IDENTITY_TOKEN"))
envServiceAccountId(System.getenv("ANTHROPIC_SERVICE_ACCOUNT_ID"))
envWorkspaceId(System.getenv("ANTHROPIC_WORKSPACE_ID"))
}

fun build(): ConfigurationFileProvider {
Expand All @@ -112,6 +118,7 @@ private constructor(
envIdentityTokenFile,
envIdentityToken,
envServiceAccountId,
envWorkspaceId,
)
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -194,6 +194,7 @@ private constructor(
envIdentityTokenFile: String?,
envIdentityToken: String?,
envServiceAccountId: String?,
envWorkspaceId: String?,
): ProfileConfig {
val auth = authentication ?: return this
val authIdentityToken = auth.identityToken().orElse(null)
Expand Down Expand Up @@ -222,6 +223,7 @@ private constructor(
return toBuilder()
.authentication(filledAuth)
.organizationId(organizationId ?: envOrganizationId)
.workspaceId(workspaceId ?: envWorkspaceId)
.build()
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,9 @@ import java.time.ZoneId
import java.time.format.DateTimeFormatter
import java.time.temporal.ChronoField

fun jsonMapper(): JsonMapper =
fun jsonMapper(): JsonMapper = JSON_MAPPER

private val JSON_MAPPER: JsonMapper =
JsonMapper.builder()
.addModule(kotlinModule())
.addModule(Jdk8Module())
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ private constructor(
private val envIdentityTokenFile: String?,
private val envIdentityToken: String?,
private val envServiceAccountId: String?,
private val envWorkspaceId: String?,
private val configDir: Path?,
private val httpClient: HttpClient?,
private val jsonMapper: JsonMapper,
Expand Down Expand Up @@ -220,6 +221,7 @@ private constructor(
federationRuleId,
organizationId,
auth.serviceAccountId().orElse(null),
config.workspaceId().orElse(null),
client,
jsonMapper,
)
Expand All @@ -240,7 +242,10 @@ private constructor(
CredentialResult(
cachingProvider,
config.baseUrl().orElse(null),
config.workspaceId().orElse(null),
// For federation profiles workspace_id is sent in the jwt-bearer exchange body,
// not as a request header (the minted token is already workspace-scoped, so the
// header would be ignored).
null,
)
}
AuthenticationType.USER_OAUTH -> {
Expand Down Expand Up @@ -361,6 +366,7 @@ private constructor(
.envIdentityTokenFile(envIdentityTokenFile)
.envIdentityToken(envIdentityToken)
.envServiceAccountId(envServiceAccountId)
.envWorkspaceId(envWorkspaceId)
.build()

val stepSources = mutableListOf<CredentialSource>()
Expand Down Expand Up @@ -437,6 +443,7 @@ private constructor(
federationRuleId,
organizationId,
envServiceAccountId,
envWorkspaceId,
client,
jsonMapper,
)
Expand Down Expand Up @@ -482,6 +489,7 @@ private constructor(
.envIdentityTokenFile(envIdentityTokenFile)
.envIdentityToken(envIdentityToken)
.envServiceAccountId(envServiceAccountId)
.envWorkspaceId(envWorkspaceId)
.build()

return resolveFromConfigurationProvider(
Expand Down Expand Up @@ -539,6 +547,12 @@ private constructor(
.envIdentityTokenFile(System.getenv("ANTHROPIC_IDENTITY_TOKEN_FILE"))
.envIdentityToken(System.getenv("ANTHROPIC_IDENTITY_TOKEN"))
.envServiceAccountId(System.getenv("ANTHROPIC_SERVICE_ACCOUNT_ID"))
// Coerce empty string to null so a defaulted-but-empty CI variable doesn't put
// "workspace_id": "" on the wire. The builder setter applies the same coercion so
// resolvers built directly (e.g. in tests) behave identically.
.envWorkspaceId(
System.getenv("ANTHROPIC_WORKSPACE_ID")?.takeUnless { it.isEmpty() }
)
.configDir(ConfigDir.resolve()?.let { Paths.get(it) })
.httpClient(httpClient)
.build()
Expand All @@ -554,6 +568,7 @@ private constructor(
private var envIdentityTokenFile: String? = null
private var envIdentityToken: String? = null
private var envServiceAccountId: String? = null
private var envWorkspaceId: String? = null
private var configDir: Path? = null
private var httpClient: HttpClient? = null
private var jsonMapper: JsonMapper = jsonMapper()
Expand Down Expand Up @@ -589,6 +604,12 @@ private constructor(
this.envServiceAccountId = envServiceAccountId
}

fun envWorkspaceId(envWorkspaceId: String?) = apply {
// Coerce empty string to null so a defaulted-but-empty CI variable doesn't put
// "workspace_id": "" on the wire.
this.envWorkspaceId = envWorkspaceId?.takeUnless { it.isEmpty() }
}

fun configDir(configDir: Path?) = apply { this.configDir = configDir }

fun httpClient(httpClient: HttpClient?) = apply { this.httpClient = httpClient }
Expand All @@ -605,6 +626,7 @@ private constructor(
envIdentityTokenFile,
envIdentityToken,
envServiceAccountId,
envWorkspaceId,
configDir,
httpClient,
jsonMapper,
Expand Down
Loading
Loading