diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 79b7c8b3b..000a814bd 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -23,9 +23,11 @@ The SDK's primary artifacts are: - Does not have its own logic - `openai-java-bedrock` - Adds optional Amazon Bedrock authentication and credential-provider integration -- `openai-java-spring-boot-starter` - - Provides the legacy Spring Boot 2 integration - - Is retained to verify the final 4.45.0 release, but is OpenAI EOL and no longer published + +The retired Spring Boot 2 starter is not part of the active build. Its final source remains available +in the [`v4.45.0` tag](https://github.com/openai/openai-java/tree/v4.45.0/openai-java-spring-boot-starter), +and its lifecycle history and consumer migration path are documented in the +[Spring Boot 2 EOL decision](docs/spring-boot-2-eol.md). `openai-java-runtime-compatibility` is a non-published fixture that exercises representative behavior for each supported artifact on an isolated runtime classpath. Its JVM matrix and each diff --git a/README.md b/README.md index 7d26dd4d5..92a2c5cad 100644 --- a/README.md +++ b/README.md @@ -1455,79 +1455,15 @@ GraalVM should automatically detect and use the published metadata, but [manual > [!WARNING] > `openai-java-spring-boot-starter` targets Spring Boot 2.7 and is OpenAI EOL as of 2026-07-27. > Version 4.45.0 is the final supported, tested, and published release. The artifact remains -> downloadable but receives no fixes, testing, or compatibility support. See the +> downloadable but receives no fixes, testing, or compatibility support, and its source is no +> longer part of the active build. See the > [Spring Boot 2 EOL decision and migration path](docs/spring-boot-2-eol.md). New Spring > applications should depend on `openai-java` directly and provide an `OpenAIClient` bean until a > supported, generation-specific integration is available. -Existing Spring Boot 2 applications can use the legacy starter to simplify configuration. - -### Installation - -#### Gradle - -```kotlin -implementation("com.openai:openai-java-spring-boot-starter:4.45.0") -``` - -#### Maven - -```xml - - com.openai - openai-java-spring-boot-starter - 4.45.0 - -``` - -### Configuration - -The [client's environment variable options](#client-configuration) can be configured in [`application.properties` or `application.yml`](https://docs.spring.io/spring-boot/how-to/properties-and-configuration.html). - -#### `application.properties` - -```properties -openai.base-url=https://api.openai.com/v1 -openai.api-key=My API Key -openai.admin-key=My Admin API Key -openai.org-id=My Organization -openai.project-id=My Project -openai.webhook-secret=My Webhook Secret -``` - -#### `application.yml` - -```yaml -openai: - base-url: https://api.openai.com/v1 - api-key: My API Key - admin-key: My Admin API Key - org-id: My Organization - project-id: My Project - webhook-secret: My Webhook Secret -``` - -#### Other configuration - -Configure any other client option by providing one or more instances of [`OpenAIClientCustomizer`](openai-java-spring-boot-starter/src/main/kotlin/com/openai/springboot/OpenAIClientCustomizer.kt). For example, here's how you'd set [`maxRetries`](#retries): - -```java -import com.openai.springboot.OpenAIClientCustomizer; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; - -@Configuration -public class OpenAIConfig { - @Bean - public OpenAIClientCustomizer customizer() { - return builder -> builder.maxRetries(3); - } -} -``` - -### Usage - -[Inject](https://docs.spring.io/spring-framework/reference/core/beans/dependencies/factory-collaborators.html) [`OpenAIClient`](openai-java-core/src/main/kotlin/com/openai/client/OpenAIClient.kt) anywhere and start using it! +Existing Spring Boot 2 users can refer to the +[`v4.45.0` Spring Boot documentation](https://github.com/openai/openai-java/blob/v4.45.0/README.md#spring-boot) +for the final legacy configuration and usage instructions. ## Jackson diff --git a/docs/spring-boot-2-eol.md b/docs/spring-boot-2-eol.md index 6b3325a6b..e319bb73c 100644 --- a/docs/spring-boot-2-eol.md +++ b/docs/spring-boot-2-eol.md @@ -20,6 +20,27 @@ floor, or changing the same coordinate to a newer Spring generation would requir A future supported Spring integration must use generation-specific coordinates and independently pass the policy's 12-month admission gate. +## History and source removal + +The starter was introduced in SDK 2.18.0 after +[an external request](https://github.com/openai/openai-java/issues/480) for Spring Boot +`application.properties` and `application.yml` support. It was a customer-facing convenience +artifact, not a test fixture: it bound `openai.*` configuration, created an injectable +`OpenAIClient` bean, and allowed applications to customize the client builder. + +The starter's source was removed from the active build after its final release. Keeping that source +on the default branch did not verify 4.45.0: the module depended on the current checkout's +`openai-java` project and therefore compiled and tested an unpublished, changing combination of the +current SDK with Spring Boot 2.7.18. It also kept the EOL Spring dependency graph active for +repository security scanning. + +Removing the active source does not withdraw the released artifact. Version 4.45.0 remains on Maven +Central, and its exact implementation and tests remain available in the +[`v4.45.0` source tag](https://github.com/openai/openai-java/tree/v4.45.0/openai-java-spring-boot-starter). +The tagged +[`v4.45.0` README](https://github.com/openai/openai-java/blob/v4.45.0/README.md#spring-boot) +preserves its final installation and configuration instructions. + ## Consumer migration Applications that cannot leave Boot 2 may pin the starter to 4.45.0, but do so without OpenAI or diff --git a/docs/version-support-policy.md b/docs/version-support-policy.md index fc17644f1..1640df974 100644 --- a/docs/version-support-policy.md +++ b/docs/version-support-policy.md @@ -66,7 +66,7 @@ that apply this policy. | Surface | Decision | Next action | | --- | --- | --- | | Framework-neutral artifacts | Keep Java 8 through SDK v4. | Raise the floor only in an SDK major; first prefer replacing or isolating a constraining dependency. | -| Existing Spring Boot 2 starter | OpenAI EOL as of 2026-07-27; 4.45.0 is the final supported and published release. | Follow the [Spring Boot 2 EOL decision](spring-boot-2-eol.md); publish no later versions. | +| Retired Spring Boot 2 starter | OpenAI EOL as of 2026-07-27; 4.45.0 is the final supported and published release, and its source is no longer in the active build. | Keep the final artifact and tagged source available; follow the [Spring Boot 2 EOL decision](spring-boot-2-eol.md); publish no later versions. | | Potential Spring Boot 4 integration | Candidate Java 17 integration using a new generation-specific artifact. | Pass the admission gate and Spring ADR before implementation. | Do not publish an OpenAI Java BOM until independently versioned artifacts or demonstrated consumer diff --git a/gradle/version-support.properties b/gradle/version-support.properties index 4c55d7516..ce6cec66c 100644 --- a/gradle/version-support.properties +++ b/gradle/version-support.properties @@ -15,5 +15,3 @@ artifact.openai-java-client-okhttp.runtime=8 artifact.openai-java-client-okhttp.lifecycle=active artifact.openai-java-core.runtime=8 artifact.openai-java-core.lifecycle=active -artifact.openai-java-spring-boot-starter.runtime=8 -artifact.openai-java-spring-boot-starter.lifecycle=eol diff --git a/openai-java-spring-boot-starter/build.gradle.kts b/openai-java-spring-boot-starter/build.gradle.kts deleted file mode 100644 index 6b68e7ecb..000000000 --- a/openai-java-spring-boot-starter/build.gradle.kts +++ /dev/null @@ -1,40 +0,0 @@ -plugins { - id("openai.kotlin") -} - -repositories { - mavenCentral() -} - -dependencies { - constraints { - testImplementation("org.xmlunit:xmlunit-core:2.11.0") { - because("2.11.0 includes the fix for CVE-2024-31573 in this test-only dependency") - } - testImplementation("com.jayway.jsonpath:json-path:2.10.0") { - because("Spring Boot's transitive JSONPath version has a stack-overflow vulnerability") - } - testImplementation("org.slf4j:slf4j-api") { - version { strictly("1.7.36") } - because("Spring Boot 2.7 requires SLF4J 1.7 at test runtime") - } - } - - api(project(":openai-java")) - implementation("org.springframework.boot:spring-boot-autoconfigure:2.7.18") - annotationProcessor("org.springframework.boot:spring-boot-configuration-processor:2.7.18") - - testImplementation("org.springframework.boot:spring-boot-starter-test:2.7.18") { - exclude(group = "org.springframework.boot", module = "spring-boot-starter-logging") - } - testRuntimeOnly("org.slf4j:slf4j-nop:1.7.36") { - because("keep Boot 2.7 tests on SLF4J 1.7 without adding vulnerable Logback") - } - testImplementation("org.assertj:assertj-core:3.27.7") - - constraints { - testImplementation("org.yaml:snakeyaml:2.6") { - because("avoid known vulnerabilities in Spring Boot's test dependency") - } - } -} diff --git a/openai-java-spring-boot-starter/src/main/kotlin/com/openai/springboot/OpenAIClientAutoConfiguration.kt b/openai-java-spring-boot-starter/src/main/kotlin/com/openai/springboot/OpenAIClientAutoConfiguration.kt deleted file mode 100644 index dd6c55af9..000000000 --- a/openai-java-spring-boot-starter/src/main/kotlin/com/openai/springboot/OpenAIClientAutoConfiguration.kt +++ /dev/null @@ -1,36 +0,0 @@ -// File generated from our OpenAPI spec by Stainless. - -package com.openai.springboot - -import com.openai.client.OpenAIClient -import com.openai.client.okhttp.OpenAIOkHttpClient -import org.springframework.beans.factory.ObjectProvider -import org.springframework.boot.autoconfigure.AutoConfiguration -import org.springframework.boot.autoconfigure.condition.ConditionalOnClass -import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean -import org.springframework.boot.context.properties.EnableConfigurationProperties -import org.springframework.context.annotation.Bean - -@AutoConfiguration -@ConditionalOnClass(OpenAIClient::class) -@EnableConfigurationProperties(OpenAIClientProperties::class) -class OpenAIClientAutoConfiguration { - - @Bean - @ConditionalOnMissingBean - internal fun client( - properties: OpenAIClientProperties, - customizers: ObjectProvider, - ): OpenAIClient = - OpenAIOkHttpClient.builder() - .apply { - properties.baseUrl?.let { baseUrl(it) } - properties.apiKey?.let { apiKey(it) } - properties.adminApiKey?.let { adminApiKey(it) } - properties.organization?.let { organization(it) } - properties.project?.let { project(it) } - properties.webhookSecret?.let { webhookSecret(it) } - customizers.orderedStream().forEach { it.customize(this) } - } - .build() -} diff --git a/openai-java-spring-boot-starter/src/main/kotlin/com/openai/springboot/OpenAIClientCustomizer.kt b/openai-java-spring-boot-starter/src/main/kotlin/com/openai/springboot/OpenAIClientCustomizer.kt deleted file mode 100644 index b86c4b7d7..000000000 --- a/openai-java-spring-boot-starter/src/main/kotlin/com/openai/springboot/OpenAIClientCustomizer.kt +++ /dev/null @@ -1,10 +0,0 @@ -// File generated from our OpenAPI spec by Stainless. - -package com.openai.springboot - -import com.openai.client.okhttp.OpenAIOkHttpClient - -fun interface OpenAIClientCustomizer { - - fun customize(builder: OpenAIOkHttpClient.Builder) -} diff --git a/openai-java-spring-boot-starter/src/main/kotlin/com/openai/springboot/OpenAIClientProperties.kt b/openai-java-spring-boot-starter/src/main/kotlin/com/openai/springboot/OpenAIClientProperties.kt deleted file mode 100644 index 443b8a8bc..000000000 --- a/openai-java-spring-boot-starter/src/main/kotlin/com/openai/springboot/OpenAIClientProperties.kt +++ /dev/null @@ -1,19 +0,0 @@ -// File generated from our OpenAPI spec by Stainless. - -package com.openai.springboot - -import org.springframework.boot.context.properties.ConfigurationProperties -import org.springframework.boot.context.properties.ConstructorBinding -import org.springframework.boot.context.properties.bind.Name - -@ConfigurationProperties(prefix = "openai") -internal data class OpenAIClientProperties -@ConstructorBinding -constructor( - @Name("base-url") val baseUrl: String? = null, - @Name("api-key") val apiKey: String? = null, - @Name("admin-key") val adminApiKey: String? = null, - @Name("org-id") val organization: String? = null, - @Name("project-id") val project: String? = null, - @Name("webhook-secret") val webhookSecret: String? = null, -) diff --git a/openai-java-spring-boot-starter/src/main/resources/META-INF/additional-spring-configuration-metadata.json b/openai-java-spring-boot-starter/src/main/resources/META-INF/additional-spring-configuration-metadata.json deleted file mode 100644 index 7875f417f..000000000 --- a/openai-java-spring-boot-starter/src/main/resources/META-INF/additional-spring-configuration-metadata.json +++ /dev/null @@ -1,35 +0,0 @@ -{ - "properties": [ - { - "name": "openai.base-url", - "type": "java.lang.String", - "defaultValue": "https://api.openai.com/v1", - "sourceType": "com.openai.springboot.OpenAIClientProperties" - }, - { - "name": "openai.api-key", - "type": "java.lang.String", - "sourceType": "com.openai.springboot.OpenAIClientProperties" - }, - { - "name": "openai.admin-key", - "type": "java.lang.String", - "sourceType": "com.openai.springboot.OpenAIClientProperties" - }, - { - "name": "openai.org-id", - "type": "java.lang.String", - "sourceType": "com.openai.springboot.OpenAIClientProperties" - }, - { - "name": "openai.project-id", - "type": "java.lang.String", - "sourceType": "com.openai.springboot.OpenAIClientProperties" - }, - { - "name": "openai.webhook-secret", - "type": "java.lang.String", - "sourceType": "com.openai.springboot.OpenAIClientProperties" - } - ] -} \ No newline at end of file diff --git a/openai-java-spring-boot-starter/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports b/openai-java-spring-boot-starter/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports deleted file mode 100644 index d1d2e241e..000000000 --- a/openai-java-spring-boot-starter/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports +++ /dev/null @@ -1 +0,0 @@ -com.openai.springboot.OpenAIClientAutoConfiguration \ No newline at end of file diff --git a/openai-java-spring-boot-starter/src/test/kotlin/com/openai/springboot/OpenAIClientAutoConfigurationTest.kt b/openai-java-spring-boot-starter/src/test/kotlin/com/openai/springboot/OpenAIClientAutoConfigurationTest.kt deleted file mode 100644 index c9b386667..000000000 --- a/openai-java-spring-boot-starter/src/test/kotlin/com/openai/springboot/OpenAIClientAutoConfigurationTest.kt +++ /dev/null @@ -1,108 +0,0 @@ -// File generated from our OpenAPI spec by Stainless. - -package com.openai.springboot - -import com.openai.client.OpenAIClient -import java.nio.charset.StandardCharsets -import org.assertj.core.api.Assertions.assertThat -import org.junit.jupiter.api.Test -import org.springframework.beans.factory.getBean -import org.springframework.boot.autoconfigure.AutoConfigurations -import org.springframework.boot.env.YamlPropertySourceLoader -import org.springframework.boot.test.context.runner.ApplicationContextRunner -import org.springframework.core.io.ByteArrayResource - -internal class OpenAIClientAutoConfigurationTest { - - private val contextRunner = - ApplicationContextRunner() - .withConfiguration(AutoConfigurations.of(OpenAIClientAutoConfiguration::class.java)) - - @Test - fun properties() { - contextRunner - .withPropertyValues( - "openai.base-url=https://api.openai.com/v1", - "openai.api-key=My API Key", - "openai.admin-key=My Admin API Key", - "openai.org-id=My Organization", - "openai.project-id=My Project", - "openai.webhook-secret=My Webhook Secret", - ) - .run { context -> - val properties = context.getBean() - assertThat(properties.baseUrl).isEqualTo("https://api.openai.com/v1") - assertThat(properties.apiKey).isEqualTo("My API Key") - assertThat(properties.adminApiKey).isEqualTo("My Admin API Key") - assertThat(properties.organization).isEqualTo("My Organization") - assertThat(properties.project).isEqualTo("My Project") - assertThat(properties.webhookSecret).isEqualTo("My Webhook Secret") - } - } - - @Test - fun yamlProperties() { - val resource = - ByteArrayResource( - """ - openai: - base-url: https://api.openai.com/v1 - api-key: YAML API Key - org-id: YAML Organization - """ - .trimIndent() - .toByteArray(StandardCharsets.UTF_8) - ) - val propertySources = YamlPropertySourceLoader().load("test", resource) - - contextRunner - .withInitializer { context -> - propertySources.reversed().forEach { - context.environment.propertySources.addFirst(it) - } - } - .run { context -> - val properties = context.getBean() - assertThat(properties.baseUrl).isEqualTo("https://api.openai.com/v1") - assertThat(properties.apiKey).isEqualTo("YAML API Key") - assertThat(properties.organization).isEqualTo("YAML Organization") - context.getBean() - } - } - - @Test - fun client() { - contextRunner - .withPropertyValues( - "openai.base-url=https://api.openai.com/v1", - "openai.api-key=My API Key", - "openai.admin-key=My Admin API Key", - "openai.org-id=My Organization", - "openai.project-id=My Project", - "openai.webhook-secret=My Webhook Secret", - ) - .run { it.getBean() } - } - - @Test - fun clientWithCustomizers() { - var customized = false - contextRunner - .withPropertyValues( - "openai.base-url=https://api.openai.com/v1", - "openai.api-key=My API Key", - "openai.admin-key=My Admin API Key", - "openai.org-id=My Organization", - "openai.project-id=My Project", - "openai.webhook-secret=My Webhook Secret", - ) - .withBean( - OpenAIClientCustomizer::class.java, - { OpenAIClientCustomizer { customized = true } }, - ) - .run { - it.getBean() - assertThat(customized).isTrue() - } - } -} diff --git a/openai-java-spring-boot-starter/src/test/kotlin/com/openai/springboot/SpringBootLoggingCompatibilityTest.kt b/openai-java-spring-boot-starter/src/test/kotlin/com/openai/springboot/SpringBootLoggingCompatibilityTest.kt deleted file mode 100644 index 5f69aa5e8..000000000 --- a/openai-java-spring-boot-starter/src/test/kotlin/com/openai/springboot/SpringBootLoggingCompatibilityTest.kt +++ /dev/null @@ -1,29 +0,0 @@ -// File generated from our OpenAPI spec by Stainless. - -package com.openai.springboot - -import com.openai.client.OpenAIClient -import org.assertj.core.api.Assertions.assertThat -import org.junit.jupiter.api.Test -import org.springframework.boot.SpringBootConfiguration -import org.springframework.boot.WebApplicationType -import org.springframework.boot.autoconfigure.EnableAutoConfiguration -import org.springframework.boot.builder.SpringApplicationBuilder - -internal class SpringBootLoggingCompatibilityTest { - - @Test - fun springApplicationStartsWithoutLogback() { - val classLoader = Thread.currentThread().contextClassLoader ?: javaClass.classLoader - assertThat(classLoader.getResource("ch/qos/logback/core/Context.class")).isNull() - assertThat(classLoader.getResource("ch/qos/logback/classic/Logger.class")).isNull() - - SpringApplicationBuilder(TestApplication::class.java) - .web(WebApplicationType.NONE) - .properties("openai.api-key=test") - .run() - .use { context -> assertThat(context.getBean(OpenAIClient::class.java)).isNotNull() } - } - - @SpringBootConfiguration @EnableAutoConfiguration internal open class TestApplication -}