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
35 changes: 35 additions & 0 deletions docs/migration-4.x.md
Original file line number Diff line number Diff line change
Expand Up @@ -199,6 +199,41 @@ code change to `SymphonyBdkBuilder` usage required. Before switching, be aware o
`symphony-bdk-http-jdk` is currently `@API(status = API.Status.EXPERIMENTAL)`. If you need an `@API(STABLE)`-only
dependency tree, stay on `symphony-bdk-http-jersey` for now.

### 4.2: Spring modules complete the default-to-JDK migration

Through 4.0 and 4.1, `symphony-bdk-core-spring-boot-starter` was the one exception to the above: `BdkCoreConfig`
still depended on `symphony-bdk-http-jersey` at `implementation` scope and hardcoded
`new ApiClientBuilderProviderJersey2()`, so Spring Boot consumers kept getting the Jersey transport by default
regardless of the rest of the BDK's direction. BDK 4.2 closes that last gap: the starter now depends on
`symphony-bdk-http-jdk` at `implementation` scope instead, and `BdkCoreConfig` exposes a
`@Bean @ConditionalOnMissingBean ApiClientBuilderProvider` returning `ApiClientBuilderProviderJdk`, wired into the
`ApiClientFactory` bean — mirroring plain `symphony-bdk-core`'s behaviour.

**For most Spring Boot applications, this is transparent.** If your application only consumes the starter's
auto-configured beans (`agentApiClient`, `podApiClient`, `loginApiClient`, and the services built on top of them) and
does not declare its own `ApiClientBuilderProvider` or `ApiClientFactory` bean, no code change is required — Spring
Boot's dependency management pulls in `symphony-bdk-http-jdk` transitively and the transport switches automatically.

**Possible breaking changes to check before upgrading:**

- **The Jersey-vs-JDK behavioral differences described above now reach Spring consumers too.** The read-timeout
(total timeout vs. read-only timeout) and filter-support (`Function<HttpRequest.Builder, HttpRequest.Builder>`-only
vs. JAX-RS `ClientRequestFilter`/`ClientResponseFilter`) differences apply to every Spring Boot application using
the default starter configuration, not just direct `symphony-bdk-core` users. Check any custom `ApiClientBuilder`
filter or read-timeout-dependent long-running request after upgrading.
- **`BdkCoreConfig` subclasses overriding `apiClientFactory(SymphonyBdkCoreProperties)` lose bean registration.** That
single-argument method is no longer annotated `@Bean` — it is kept only as a source-compatible overload delegating
to the new two-argument, `@Bean`-annotated `apiClientFactory(properties, apiClientBuilderProvider)`. A custom
configuration that subclassed `BdkCoreConfig` and overrode the single-argument method expecting Spring to invoke it
as the factory bean will now see that override silently ignored; override the two-argument method instead, or
declare your own `ApiClientBuilderProvider` bean.
- **`symphony-bdk-http-jersey` no longer rides in transitively through the starter.** It is retained at
`testImplementation` scope inside the starter module itself (for internal `MockApiClient` test fixture
compatibility only), not exposed to consumer applications. If your application relied on Jersey/JAX-RS classes
being present purely because the starter pulled them in, add `symphony-bdk-http-jersey` (or
`symphony-bdk-http-webclient`) explicitly and declare your own `ApiClientBuilderProvider` bean to keep the previous
transport.

## Support window

BDK 3.x will receive critical security fixes for **6 months** following the BDK 4.0.0 release, where Symphony is
Expand Down
6 changes: 3 additions & 3 deletions docs/migration.md
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,7 @@ Simply update BDK dependency version to `3.0.0`
</dependency>
<dependency>
<groupId>org.finos.symphony.bdk</groupId>
<artifactId>symphony-bdk-http-jersey</artifactId> <!-- or symphony-bdk-http-webclient -->
<artifactId>symphony-bdk-http-jdk</artifactId> <!-- or the deprecated symphony-bdk-http-jersey / symphony-bdk-http-webclient -->
<scope>runtime</scope>
</dependency>
<dependency>
Expand All @@ -138,7 +138,7 @@ This guide provides information about how to migrate from Symphony SDK 1.0 to BD

## Dependencies
In Java SDK 1.0, the bot had dependencies on `symphony-api-client-java` in addition to the application framework (SpringBoot for e.g). With BDK 2.0, we can replace both of them with `symphony-bdk-core-spring-boot-starter`.
If your project is not framework based, dependencies such as *jersey* and *freemarker* should be added as well.
If your project is not framework based, dependencies such as *http-jdk* and *freemarker* should be added as well.
### Spring Boot based project

#### Java SDK 1.0
Expand Down Expand Up @@ -219,7 +219,7 @@ If your project is not framework based, dependencies such as *jersey* and *freem
</dependency>
<dependency>
<groupId>org.finos.symphony.bdk</groupId>
<artifactId>symphony-bdk-http-jersey</artifactId> <!-- or symphony-bdk-http-webclient -->
<artifactId>symphony-bdk-http-jdk</artifactId> <!-- or the deprecated symphony-bdk-http-jersey / symphony-bdk-http-webclient -->
<scope>runtime</scope>
</dependency>
<dependency>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
schema: spec-driven
created: 2026-10-02
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
## Context

See `proposal.md` for background and motivation. `symphony-bdk-http-jdk` provides a complete implementation of `com.symphony.bdk.http.api.ApiClient` using the standard JDK 11+ `java.net.http.HttpClient`. However, `symphony-bdk-core-spring-boot-starter` still depended on `symphony-bdk-http-jersey` at `implementation` scope and hardcoded `new ApiClientBuilderProviderJersey2()` in `BdkCoreConfig`, preventing users from benefiting from the zero-dependency transport out-of-the-box.

## Goals / Non-Goals

**Goals:**
- Make `symphony-bdk-http-jdk` the default HTTP client for `symphony-bdk-core-spring-boot-starter`, `symphony-bdk-cli`, and example modules.
- Make the HTTP transport pluggable in Spring Boot by exposing an `ApiClientBuilderProvider` bean.
- Maintain full backward compatibility for Spring Boot configurations and `ApiClientFactory` calls.
- Maintain isolated test compatibility for test fixtures that depend on `MockApiClient`.

**Non-Goals:**
- Removing or altering the behavior of `symphony-bdk-http-jersey` or `symphony-bdk-http-webclient` (they remain supported alternatives).
- Rewriting `MockApiClient` in `symphony-bdk-core`'s internal test harness.

## Decisions

### D1: Expose `ApiClientBuilderProvider` as an injectable Spring bean
In `BdkCoreConfig`:
```java
@Bean
@ConditionalOnMissingBean
public ApiClientBuilderProvider apiClientBuilderProvider() {
return new ApiClientBuilderProviderJdk();
}

@Bean
@ConditionalOnMissingBean
public ApiClientFactory apiClientFactory(SymphonyBdkCoreProperties properties,
ApiClientBuilderProvider apiClientBuilderProvider) {
return new ApiClientFactory(properties, apiClientBuilderProvider);
}
```
**Rationale**: This enables consumers to easily plug in an alternative transport (e.g. `symphony-bdk-http-jersey` or `symphony-bdk-http-webclient`) simply by declaring an `ApiClientBuilderProvider` bean, without needing to replace or re-implement the entire `ApiClientFactory` bean.

### D2: Retain single-parameter `apiClientFactory` overload for source compatibility
Provide:
```java
public ApiClientFactory apiClientFactory(SymphonyBdkCoreProperties properties) {
return apiClientFactory(properties, apiClientBuilderProvider());
}
```
**Rationale**: Existing Java unit tests or custom configurations invoking `config.apiClientFactory(properties)` directly continue to compile and function without modification.

### D3: Scope `symphony-bdk-http-jersey` strictly to `testImplementation` in starter
In `symphony-bdk-core-spring-boot-starter/build.gradle`:
- `implementation project(':symphony-bdk-http:symphony-bdk-http-jdk')`
- `testImplementation project(':symphony-bdk-http:symphony-bdk-http-jersey')`

And in `SymphonyBdkMockedConfiguration$ApiClientFactoryMock`:
```java
public ApiClientFactoryMock(BdkConfig config) {
super(config, new ApiClientBuilderProviderJdk());
}
```
**Rationale**: `MockApiClient` (inherited from `symphony-bdk-core` test output) relies on Jersey's `ApiClientJersey2` and `RuntimeDelegate` at test time. Retaining Jersey in `testImplementation` allows tests to run without leaking Jersey into consumer runtime classpaths. Explicitly passing `ApiClientBuilderProviderJdk` to `super(...)` prevents `ServiceLookup.lookupSingleService` from encountering multiple SPI implementations during tests.

### D4: Update CLI distribution and examples to runtimeOnly JDK
CLI distribution (`symphony-bdk-cli`) and examples (`bdk-ai-agent-example`, `bdk-core-examples`, `bdk-group-example`, `bdk-multi-instances-example`) bundle or reference `symphony-bdk-http-jdk` at `runtimeOnly` (or `implementation`) scope, eliminating obsolete Jersey dependencies.

## Risks / Trade-offs

- **[Risk] Behavioral difference between JDK HttpClient and Jersey client (e.g. read timeout semantics)** → Documented in `docs/migration-4.x.md`; consumers requiring Jersey can supply an `ApiClientBuilderProvider` bean or dependency override.
- **[Risk] Multiple SPI providers on classpath if consumer adds both jars** → `ServiceLookup` fails fast with an explicit error message prompting the user to remove one of the conflicting dependencies.
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
## Why

In Symphony BDK 4.x, `symphony-bdk-http-jdk` was introduced as the zero-third-party-dependency HTTP client built on Java 11+ `java.net.http.HttpClient` and documented as the recommended default transport for BDK 4.x. However, `symphony-bdk-core-spring-boot-starter` still pulled in `symphony-bdk-http-jersey` as a direct `implementation` dependency and hardcoded `new ApiClientBuilderProviderJersey2()` in `BdkCoreConfig`.

This forced all Spring Boot starter consumers to transitively pull in the heavy Jersey 3 stack, JAX-RS APIs, and Apache HttpClient 5, exposing applications to unnecessary CVE attack surface and preventing easy pluggability. Furthermore, the CLI distribution and example projects still bundled or implemented `symphony-bdk-http-jersey`. Switching `symphony-bdk-spring` and the rest of the workspace to default to `symphony-bdk-http-jdk` fulfills the BDK 4.x architecture vision of a lightweight, zero-third-party default transport.

## What Changes

- **Spring Boot Starter default dependency**: In `symphony-bdk-core-spring-boot-starter`, replace the `implementation` dependency on `symphony-bdk-http-jersey` with `symphony-bdk-http-jdk`.
- **Spring Boot Starter transport pluggability**: In `BdkCoreConfig`, expose a `@ConditionalOnMissingBean` bean of type `ApiClientBuilderProvider` returning `ApiClientBuilderProviderJdk`, and inject it into `apiClientFactory`. Retain a backwards-compatible overload `apiClientFactory(properties)`.
- **Spring Boot Starter test isolation**: Retain `testImplementation project(':symphony-bdk-http:symphony-bdk-http-jersey')` in `symphony-bdk-core-spring-boot-starter` for internal `MockApiClient` compatibility, and pass `ApiClientBuilderProviderJdk` explicitly in `ApiClientFactoryMock` to prevent classpath SPI lookup conflicts during testing.
- **CLI distribution runtime**: Switch `symphony-bdk-cli` distribution runtime dependency from `symphony-bdk-http-jersey` to `symphony-bdk-http-jdk`.
- **Example projects**: Switch `bdk-ai-agent-example`, `bdk-core-examples`, `bdk-group-example`, and `bdk-multi-instances-example` to use `symphony-bdk-http-jdk` as their runtime/default HTTP transport, removing unused Jersey dependencies from `bdk-core-examples`.
- **Documentation**: Update `docs/migration.md` to reference `symphony-bdk-http-jdk` as the default runtime HTTP dependency.

## Capabilities

### Modified Capabilities
- `jdk-http-transport`: Adds requirements for Spring Boot starter default transport wiring, transport pluggability via Spring bean override, and CLI distribution runtime default.

## Impact

- **Dependencies**: Spring Boot applications using `symphony-bdk-core-spring-boot-starter` will now transitively receive `symphony-bdk-http-jdk` instead of `symphony-bdk-http-jersey`. Applications desiring Jersey or WebClient can declare their respective dependency and provide an `ApiClientBuilderProvider` bean or `ApiClientFactory` bean.
- **Transitive CVE surface**: Eliminates Jersey, JAX-RS, and Apache HttpClient from the default starter and CLI dependency trees.
- **Compatibility**: Fully backward compatible for Spring Boot applications using standard BDK starter configurations.
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
## ADDED Requirements

### Requirement: Spring Boot Starter Default Transport
The `symphony-bdk-core-spring-boot-starter` module SHALL declare `symphony-bdk-http-jdk` as its default `implementation` HTTP client dependency, and instantiate `ApiClientBuilderProviderJdk` by default.

#### Scenario: Spring Boot application startup with default starter
- **WHEN** a Spring Boot application starts with `symphony-bdk-core-spring-boot-starter` and no custom HTTP client beans
- **THEN** the application context creates an `ApiClientFactory` backed by `ApiClientBuilderProviderJdk` and connects using Java's built-in `HttpClient`

#### Scenario: No Jersey runtime required
- **WHEN** a consumer includes only `symphony-bdk-core-spring-boot-starter` without `symphony-bdk-http-jersey`
- **THEN** application compilation and runtime execution succeed without Jersey or Apache HttpClient classes on the classpath

### Requirement: Spring Boot Starter Transport Pluggability
The `BdkCoreConfig` configuration class SHALL expose an `ApiClientBuilderProvider` bean annotated with `@ConditionalOnMissingBean` to allow overriding the HTTP transport via standard Spring bean definition.

#### Scenario: Custom ApiClientBuilderProvider bean provided
- **WHEN** a Spring Boot application defines a custom bean implementing `ApiClientBuilderProvider`
- **THEN** the custom provider is injected into `ApiClientFactory` instead of `ApiClientBuilderProviderJdk`

### Requirement: CLI Distribution Default Transport
The `symphony-bdk-cli` application module SHALL package `symphony-bdk-http-jdk` as its runtime HTTP implementation for the standalone CLI distribution.

#### Scenario: Running CLI command
- **WHEN** the standalone `bdk` CLI distribution binary executes an API command against Symphony
- **THEN** the command uses `ApiClientJdk` without external third-party HTTP client libraries
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
## 1. Spring Boot Starter HTTP Transport

- [x] 1.1 In `symphony-bdk-spring/symphony-bdk-core-spring-boot-starter/build.gradle`, replace `implementation project(':symphony-bdk-http:symphony-bdk-http-jersey')` with `implementation project(':symphony-bdk-http:symphony-bdk-http-jdk')` and retain `testImplementation project(':symphony-bdk-http:symphony-bdk-http-jersey')`.
- [x] 1.2 In `BdkCoreConfig`, expose `@Bean @ConditionalOnMissingBean ApiClientBuilderProvider` returning `ApiClientBuilderProviderJdk`.
- [x] 1.3 In `BdkCoreConfig`, inject `ApiClientBuilderProvider` into `apiClientFactory`, and add single-argument `apiClientFactory(properties)` delegation overload for source compatibility.
- [x] 1.4 In `SymphonyBdkMockedConfiguration$ApiClientFactoryMock`, explicitly pass `new ApiClientBuilderProviderJdk()` to `super(...)` to prevent classpath `ServiceLookup` conflicts.
- [x] 1.5 In `BdkCoreConfigTest`, add unit tests verifying `apiClientBuilderProvider()` produces `ApiClientBuilderProviderJdk` by default and custom provider injection works with `apiClientFactory`.
- [x] 1.6 Verify `symphony-bdk-core-spring-boot-starter` and `symphony-bdk-app-spring-boot-starter` build and tests pass via `./gradlew :symphony-bdk-spring:symphony-bdk-core-spring-boot-starter:check :symphony-bdk-spring:symphony-bdk-app-spring-boot-starter:check`.

## 2. CLI and Example Modules

- [x] 2.1 In `symphony-bdk-cli/build.gradle`, switch bundled runtime dependency from `symphony-bdk-http-jersey` to `symphony-bdk-http-jdk`.
- [x] 2.2 In `symphony-bdk-examples/bdk-ai-agent-example/build.gradle`, switch `symphony-bdk-http-jersey` to `symphony-bdk-http-jdk`.
- [x] 2.3 In `symphony-bdk-examples/bdk-core-examples/build.gradle`, remove unused Jersey client/multipart dependencies and switch to `runtimeOnly project(':symphony-bdk-http:symphony-bdk-http-jdk')`.
- [x] 2.4 In `symphony-bdk-examples/bdk-group-example/build.gradle`, switch `symphony-bdk-http-jersey` to `symphony-bdk-http-jdk`.
- [x] 2.5 In `symphony-bdk-examples/bdk-multi-instances-example/build.gradle`, add `runtimeOnly project(':symphony-bdk-http:symphony-bdk-http-jdk')`.
- [x] 2.6 Verify CLI and all examples build and pass tests via `./gradlew :symphony-bdk-cli:check :symphony-bdk-examples:bdk-core-examples:check :symphony-bdk-examples:bdk-spring-boot-example:check :symphony-bdk-examples:bdk-app-spring-boot-example:check :symphony-bdk-examples:bdk-ai-agent-example:check :symphony-bdk-examples:bdk-group-example:check :symphony-bdk-examples:bdk-multi-instances-example:check`.

## 3. Documentation

- [x] 3.1 In `docs/migration.md`, update Maven dependency snippets and prose references from `symphony-bdk-http-jersey` to `symphony-bdk-http-jdk`.

## 4. End-to-End Validation

- [x] 4.1 Run full project verification via `./gradlew check` and confirm all tasks succeed.
25 changes: 25 additions & 0 deletions openspec/specs/jdk-http-transport/spec.md
Original file line number Diff line number Diff line change
Expand Up @@ -101,3 +101,28 @@ When the expected return type is `File`, `ApiClientJdk` SHALL stream the respons
- **WHEN** a request sent through `ApiClientJdk` receives a response
- **THEN** a DEBUG-level log entry is written to `com.symphony.bdk.requests.outgoing` containing the response status code, the request URL, and the elapsed time

### Requirement: Spring Boot Starter Default Transport
The `symphony-bdk-core-spring-boot-starter` module SHALL declare `symphony-bdk-http-jdk` as its default `implementation` HTTP client dependency, and instantiate `ApiClientBuilderProviderJdk` by default.

#### Scenario: Spring Boot application startup with default starter
- **WHEN** a Spring Boot application starts with `symphony-bdk-core-spring-boot-starter` and no custom HTTP client beans
- **THEN** the application context creates an `ApiClientFactory` backed by `ApiClientBuilderProviderJdk` and connects using Java's built-in `HttpClient`

#### Scenario: No Jersey runtime required
- **WHEN** a consumer includes only `symphony-bdk-core-spring-boot-starter` without `symphony-bdk-http-jersey`
- **THEN** application compilation and runtime execution succeed without Jersey or Apache HttpClient classes on the classpath

### Requirement: Spring Boot Starter Transport Pluggability
The `BdkCoreConfig` configuration class SHALL expose an `ApiClientBuilderProvider` bean annotated with `@ConditionalOnMissingBean` to allow overriding the HTTP transport via standard Spring bean definition.

#### Scenario: Custom ApiClientBuilderProvider bean provided
- **WHEN** a Spring Boot application defines a custom bean implementing `ApiClientBuilderProvider`
- **THEN** the custom provider is injected into `ApiClientFactory` instead of `ApiClientBuilderProviderJdk`

### Requirement: CLI Distribution Default Transport
The `symphony-bdk-cli` application module SHALL package `symphony-bdk-http-jdk` as its runtime HTTP implementation for the standalone CLI distribution.

#### Scenario: Running CLI command
- **WHEN** the standalone `bdk` CLI distribution binary executes an API command against Symphony
- **THEN** the command uses `ApiClientJdk` without external third-party HTTP client libraries

2 changes: 1 addition & 1 deletion symphony-bdk-cli/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ dependencies {
implementation project(':symphony-bdk-core')

// self-contained distribution: bundle an HTTP implementation and a logging backend
runtimeOnly project(':symphony-bdk-http:symphony-bdk-http-jersey')
runtimeOnly project(':symphony-bdk-http:symphony-bdk-http-jdk')
runtimeOnly project(':symphony-bdk-template:symphony-bdk-template-freemarker')

// command-line parsing
Expand Down
2 changes: 1 addition & 1 deletion symphony-bdk-examples/bdk-ai-agent-example/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ dependencies {
implementation project(':symphony-bdk-core')

runtimeOnly project(':symphony-bdk-template:symphony-bdk-template-freemarker')
implementation project(':symphony-bdk-http:symphony-bdk-http-jersey')
implementation project(':symphony-bdk-http:symphony-bdk-http-jdk')

implementation platform("dev.langchain4j:langchain4j-bom:${langchain4jVersion}")
implementation 'dev.langchain4j:langchain4j'
Expand Down
Loading
Loading