diff --git a/docs/container-dist_zip.md b/docs/container-dist_zip.md index aadd126a96..25814fba0c 100644 --- a/docs/container-dist_zip.md +++ b/docs/container-dist_zip.md @@ -16,7 +16,7 @@ The Dist Zip Container allows applications packaged in [`distZip`-style][] to be Tags are printed to standard output by the buildpack detect script -If the application uses Spring, [Spring profiles][] can be specified by setting the [`SPRING_PROFILES_ACTIVE`][] environment variable. This is automatically detected and used by Spring. The Spring Auto-reconfiguration Framework will specify the `cloud` profile in addition to any others. +If the application uses Spring, [Spring profiles][] can be specified by setting the [`SPRING_PROFILES_ACTIVE`][] environment variable. This is automatically detected and used by Spring. The [Java CfEnv](framework-java-cfenv.md) framework — the replacement for the deprecated Spring Auto-reconfiguration — activates the `cloud` profile at runtime; you can also add it explicitly with `SPRING_PROFILES_INCLUDE=cloud`. ## Configuration For general information on configuring the buildpack, including how to specify configuration values through environment variables, refer to [Configuration and Extension][]. diff --git a/docs/container-java_main.md b/docs/container-java_main.md index fa0a943461..2061c3d03a 100644 --- a/docs/container-java_main.md +++ b/docs/container-java_main.md @@ -19,7 +19,7 @@ Command line arguments may optionally be configured. Tags are printed to standard output by the buildpack detect script -If the application uses Spring, [Spring profiles][] can be specified by setting the [`SPRING_PROFILES_ACTIVE`][] environment variable. This is automatically detected and used by Spring. The Spring Auto-reconfiguration Framework will specify the `cloud` profile in addition to any others. +If the application uses Spring, [Spring profiles][] can be specified by setting the [`SPRING_PROFILES_ACTIVE`][] environment variable. This is automatically detected and used by Spring. The [Java CfEnv](framework-java-cfenv.md) framework — the replacement for the deprecated Spring Auto-reconfiguration — activates the `cloud` profile at runtime; you can also add it explicitly with `SPRING_PROFILES_INCLUDE=cloud`. ## Spring Boot diff --git a/docs/container-spring_boot.md b/docs/container-spring_boot.md index 17c8833acf..2a303b92d9 100644 --- a/docs/container-spring_boot.md +++ b/docs/container-spring_boot.md @@ -15,7 +15,7 @@ Tags are printed to standard output by the buildpack detect script The container expects to run the application creating by running [`gradle distZip`][d] in an application built with the Spring Boot Gradle plugin. -If the application uses Spring, [Spring profiles][] can be specified by setting the [`SPRING_PROFILES_ACTIVE`][] environment variable. This is automatically detected and used by Spring. The Spring Auto-reconfiguration Framework will specify the `cloud` profile in addition to any others. +If the application uses Spring, [Spring profiles][] can be specified by setting the [`SPRING_PROFILES_ACTIVE`][] environment variable. This is automatically detected and used by Spring. The [Java CfEnv](framework-java-cfenv.md) framework — the replacement for the deprecated Spring Auto-reconfiguration — activates the `cloud` profile at runtime; you can also add it explicitly with `SPRING_PROFILES_INCLUDE=cloud`. ## CF Tasks diff --git a/docs/container-spring_boot_cli.md b/docs/container-spring_boot_cli.md index 93199f21ca..4f56c6b665 100644 --- a/docs/container-spring_boot_cli.md +++ b/docs/container-spring_boot_cli.md @@ -19,7 +19,7 @@ The Spring Boot CLI Container runs one or more Groovy (i.e. `*.groovy`) files us Tags are printed to standard output by the buildpack detect script. -If the application uses Spring, [Spring profiles][] can be specified by setting the [`SPRING_PROFILES_ACTIVE`][] environment variable. This is automatically detected and used by Spring. The Spring Auto-reconfiguration Framework will specify the `cloud` profile in addition to any others. +If the application uses Spring, [Spring profiles][] can be specified by setting the [`SPRING_PROFILES_ACTIVE`][] environment variable. This is automatically detected and used by Spring. The [Java CfEnv](framework-java-cfenv.md) framework — the replacement for the deprecated Spring Auto-reconfiguration — activates the `cloud` profile at runtime; you can also add it explicitly with `SPRING_PROFILES_INCLUDE=cloud`. ## Configuration For general information on configuring the buildpack, including how to specify configuration values through environment variables, refer to [Configuration and Extension][]. diff --git a/docs/framework-java-cfenv.md b/docs/framework-java-cfenv.md index e9bbb31c71..0a94b27e2f 100644 --- a/docs/framework-java-cfenv.md +++ b/docs/framework-java-cfenv.md @@ -1,15 +1,22 @@ # Java CfEnv Framework -The Java CfEnv Framework provides the `java-cfenv` library for Spring Boot 3+ applications. This library sets various Spring Boot properties by parsing CloudFoundry variables such as `VCAP_SERVICES`, allowing Spring Boot's autoconfiguration to kick in. +The Java CfEnv Framework provides the `java-cfenv` library for Spring Boot 3.x and 4.x applications. This library sets various Spring Boot properties by parsing Cloud Foundry variables such as `VCAP_SERVICES`, allowing Spring Boot's autoconfiguration to kick in. -This is the recommended replacement for Spring AutoReconfiguration library which is deprecated. See the `java-cfenv` repostitory for more detail. +This is the recommended replacement for Spring AutoReconfiguration library which is deprecated. See the `java-cfenv` repository for more details. -It also sets the 'cloud' profile for Spring Boot applications, as the Spring AutoReconfiguration framework did. +The `cloud` Spring profile is activated at runtime by java-cfenv's `CloudProfileApplicationListener`, which ships in the `java-cfenv-all` module. To ensure the profile is active — or to activate it independently of java-cfenv — set it explicitly. Use `SPRING_PROFILES_INCLUDE=cloud` to add `cloud` alongside any other active profiles, or `SPRING_PROFILES_ACTIVE=cloud` to set it as the sole active profile (this replaces any others). The buildpack itself does not set any Spring profile. + +The buildpack selects the appropriate `java-cfenv` version based on the detected Spring Boot major version: + +| Spring Boot | java-cfenv | +|-------------|------------| +| 3.x | 3.x (latest) | +| 4.x | 4.x (latest) | - - + + @@ -17,3 +24,54 @@ It also sets the 'cloud' profile for Spring Boot applications, as the Spring Aut
Detection CriterionExistence of a `Spring-Boot-Version: 3.*` manifest entryNo existing `java-cfenv` library foundExistence of a spring-boot-3.*.jar or spring-boot-4.*.jar in BOOT-INF/lib, WEB-INF/lib, or lib/; or a Spring-Boot-Version: 3.* / Spring-Boot-Version: 4.* entry in META-INF/MANIFEST.MFNo existing java-cfenv library found in the application
Tags
Tags are printed to standard output by the buildpack detect script + +## How it works + +The framework is implemented in `src/java/frameworks/java_cf_env.go`: + +1. **Detect** — activates only when all of these hold: the framework is enabled (see [Configuration](#configuration)); a Spring Boot 3.x or 4.x marker is found (a `spring-boot-{3,4}.*.jar` under `BOOT-INF/lib`, `WEB-INF/lib`, or `lib/`, or a `Spring-Boot-Version: 3.*` / `4.*` entry in `META-INF/MANIFEST.MF`); and the application does not already bundle a `java-cfenv*.jar` (if it does, the buildpack backs off and uses the application's own copy). +2. **Supply** — selects the java-cfenv version from the detected Spring Boot major (Spring Boot 3 → the manifest's `3.x` line, Spring Boot 4 → the `4.x` line) and installs the `java-cfenv-all` jar into the dependency directory. +3. **Finalize** — appends the installed jar to `CLASSPATH` via a `.profile.d/java_cf_env.sh` script, so it is on the application's runtime classpath. +4. **Runtime** — Spring Boot reads the jar's `META-INF/spring.factories`: the `EnvironmentPostProcessor`s map `VCAP_SERVICES` to Spring properties, and `CloudProfileApplicationListener` (in the `java-cfenv-all` module) activates the `cloud` profile when running in Cloud Foundry. + +## Configuration + +The framework can be disabled via the `JBP_CONFIG_JAVA_CF_ENV` environment variable: + +```bash +cf set-env JBP_CONFIG_JAVA_CF_ENV '{enabled: false}' +cf restage +``` + +The buildpack only re-reads this variable during staging, so a `cf restage` is required for the change to take effect. + +To re-enable, either set it back to `{enabled: true}` or remove the variable entirely: + +```bash +cf unset-env JBP_CONFIG_JAVA_CF_ENV +cf restage +``` + +| Variable | Default | Description | +|----------|---------|-------------| +| `JBP_CONFIG_JAVA_CF_ENV` | `{enabled: true}` | Enable or disable the framework | + +Note: if `java-cfenv*.jar` is already present in the application, the buildpack skips injection automatically — no need to disable explicitly for that case. + +Disable when: +- The application handles `VCAP_SERVICES` manually with custom binding logic +- The automatic `cloud` profile activation is unwanted +- Another service binding library conflicts with `java-cfenv` + +`{enabled: false}` disables the **whole** framework — both the `VCAP_SERVICES` → Spring property mapping and the `cloud` profile activation. There is no option to disable only the `cloud` profile. + +For finer control, bundle a java-cfenv artifact in the application yourself. Because the buildpack backs off whenever a `java-cfenv*.jar` is already present, the app's choice wins: + +| App bundles | Property mapping | `cloud` profile | +|-------------|------------------|-----------------| +| _(nothing — buildpack injects `java-cfenv-all`)_ | yes | yes | +| `java-cfenv-all` | yes | yes (app pins the version) | +| `java-cfenv-boot` | yes | **no** (no `CloudProfileApplicationListener`) | +| `java-cfenv` (core) | **no** (API only) | **no** | + +So an app can include `java-cfenv-boot` to keep property mapping without the `cloud` profile, or the bare `java-cfenv` core to opt out of all automatic behaviour and use the `CfEnv` API directly. diff --git a/docs/spring-auto-reconfiguration-migration.md b/docs/spring-auto-reconfiguration-migration.md index 734fccd4ec..52e7f4e541 100644 --- a/docs/spring-auto-reconfiguration-migration.md +++ b/docs/spring-auto-reconfiguration-migration.md @@ -2,6 +2,45 @@ This guide provides step-by-step instructions for migrating from the deprecated **Spring Auto-reconfiguration** framework to **java-cfenv**. +> **Note — the `cloud` Spring profile** +> +> Spring Auto-reconfiguration activated a Spring profile named `cloud`. java-cfenv only +> activates that profile if the `java-cfenv-all` module is on the classpath (it contains +> `CloudProfileApplicationListener`); the `java-cfenv-boot` module does **not**. If your +> application relies on the `cloud` profile — for example `application-cloud.yml` / +> `application-cloud.properties` or `@Profile("cloud")` beans — either use `java-cfenv-all`, +> or activate it explicitly. If the application already sets other active profiles, use +> `SPRING_PROFILES_INCLUDE` to add `cloud` alongside them: +> +> ```bash +> cf set-env SPRING_PROFILES_INCLUDE cloud # adds 'cloud' to any existing profiles +> cf restage +> ``` +> +> Use `SPRING_PROFILES_ACTIVE=cloud` only if `cloud` should be the sole active profile (it +> replaces any others). + +> **Note — controlling the automatic behaviour** +> +> When the application does not bundle java-cfenv itself, the buildpack injects `java-cfenv-all` +> (property mapping **and** `cloud` profile). To scope this: +> - `cf set-env JBP_CONFIG_JAVA_CF_ENV '{enabled: false}'` (+ `cf restage`) disables the +> **whole** framework — both property mapping and the `cloud` profile. There is no +> `cloud`-profile-only toggle. +> - Because the buildpack backs off when the app already bundles a `java-cfenv*.jar`, bundling your +> own artifact wins: `java-cfenv-boot` = property mapping without the `cloud` profile; +> `java-cfenv` (core) = neither, use the `CfEnv` API directly. +> +> See [Java CfEnv Framework](framework-java-cfenv.md) for details. + +> **Note — backwards compatible with buildpack 4.x (java-buildpack 5.0.6+)** +> +> As of java-buildpack **5.0.6**, the buildpack injects `java-cfenv-all` by default, so the `cloud` +> profile activation and the `VCAP_SERVICES` → Spring property mapping behave the same as under +> java-buildpack 4.x. Apps that relied on either under 4.x keep working after upgrading — no +> application change is required for the `cloud`/VCAP behaviour. (Buildpack 5.0.0–5.0.5 shipped the +> bare `java-cfenv` core module, which activated neither; see #1349.) + --- ## Table of Contents @@ -52,8 +91,9 @@ This guide provides step-by-step instructions for migrating from the deprecated io.pivotal.cfenv - java-cfenv-boot - 3.1.4 + java-cfenv-all + + 3.5.1 ``` @@ -62,6 +102,7 @@ This guide provides step-by-step instructions for migrating from the deprecated - Library reads `VCAP_SERVICES` and sets Spring Boot properties - Spring Boot autoconfiguration uses these properties - More transparent and Spring Boot native +- `java-cfenv-all` bundles the property post-processors **and** the `cloud` profile listener (`CloudProfileApplicationListener`); use the lighter `java-cfenv-boot` instead only if you do not rely on the `cloud` profile --- @@ -91,11 +132,11 @@ Check your `pom.xml` or `build.gradle`: ```xml - + io.pivotal.cfenv - java-cfenv-boot - 3.1.4 + java-cfenv-all + 3.5.1 ``` @@ -104,7 +145,7 @@ Check your `pom.xml` or `build.gradle`: ```groovy dependencies { - implementation 'io.pivotal.cfenv:java-cfenv-boot:3.1.4' + implementation 'io.pivotal.cfenv:java-cfenv-all:3.5.1' // 4.0.0 for Spring Boot 4 } ``` @@ -227,7 +268,7 @@ You should see: ``` Java Buildpack v1.x.x | https://github.com/cloudfoundry/java-buildpack -----> Supplying frameworks... - java-cf-env=3.1.4 + java-cf-env=3.5.1 ``` --- @@ -453,16 +494,23 @@ public class CustomConfig { ### Issue: "cloud" profile not active -**Cause**: java-cfenv only activates "cloud" profile on Cloud Foundry - -**Solution**: This is expected. Locally, the "cloud" profile won't be active. +**Cause**: The `cloud` profile is activated by `CloudProfileApplicationListener`, which ships +only in the `java-cfenv-all` module. If you migrated with the lighter `java-cfenv-boot` module +instead, it does **not** carry that listener, so the `cloud` profile you had under Spring +Auto-reconfiguration will not be active. -To test cloud profile locally: +**Solution**: Depend on `java-cfenv-all` (as shown in the steps above), or activate the profile +explicitly. If the application already sets other active profiles, add `cloud` alongside them with +`SPRING_PROFILES_INCLUDE`: ```bash -java -jar myapp.jar --spring.profiles.active=cloud +cf set-env SPRING_PROFILES_INCLUDE cloud # adds 'cloud' to any existing profiles +cf restage ``` +Use `SPRING_PROFILES_ACTIVE=cloud` only if `cloud` should be the sole active profile (it replaces +any others). To activate it locally: `java -jar myapp.jar --spring.profiles.active=cloud`. + --- ## Rollback Plan @@ -502,7 +550,7 @@ If you encounter migration issues: ## Summary Checklist - [ ] Verify Spring Boot version (2.1+ required, 3.x recommended) -- [ ] Add `java-cfenv-boot` dependency to `pom.xml` or `build.gradle` +- [ ] Add `java-cfenv-all` dependency to `pom.xml` or `build.gradle` (match your Spring Boot major) - [ ] Remove Spring Cloud Connectors dependencies (if present) - [ ] Review and simplify custom service configurations - [ ] Remove `JBP_CONFIG_SPRING_AUTO_RECONFIGURATION` environment variable diff --git a/src/java/frameworks/java_cf_env.go b/src/java/frameworks/java_cf_env.go index 48bf3f91f2..7a70e04cec 100644 --- a/src/java/frameworks/java_cf_env.go +++ b/src/java/frameworks/java_cf_env.go @@ -71,7 +71,6 @@ func (j *JavaCfEnvFramework) Supply() error { } else { j.context.Log.Debug("Resolved Java CF Env version pattern '%s' to %s", versionPattern, resolvedVersion) } - // Install java-cfenv JAR javaCfEnvDir := filepath.Join(j.context.Stager.DepDir(), "java_cf_env") if err := j.context.Installer.InstallDependency(dep, javaCfEnvDir); err != nil { diff --git a/src/java/frameworks/java_cf_env_test.go b/src/java/frameworks/java_cf_env_test.go index b307be606f..b10f305699 100644 --- a/src/java/frameworks/java_cf_env_test.go +++ b/src/java/frameworks/java_cf_env_test.go @@ -68,10 +68,23 @@ var _ = Describe("Java CF Env", func() { }) }) - Context("with Spring Boot 3.x JAR in lib/", func() { + Context("with Spring Boot 4.1.x JAR in BOOT-INF/lib", func() { + BeforeEach(func() { + Expect(os.MkdirAll(filepath.Join(buildDir, "BOOT-INF", "lib"), 0755)).To(Succeed()) + Expect(os.WriteFile(filepath.Join(buildDir, "BOOT-INF", "lib", "spring-boot-4.1.0.jar"), []byte("fake"), 0644)).To(Succeed()) + }) + + It("returns 'Java CF Env'", func() { + name, err := fw.Detect() + Expect(err).NotTo(HaveOccurred()) + Expect(name).To(Equal("Java CF Env")) + }) + }) + + Context("with Spring Boot 4.1.x JAR in lib/", func() { BeforeEach(func() { Expect(os.MkdirAll(filepath.Join(buildDir, "lib"), 0755)).To(Succeed()) - Expect(os.WriteFile(filepath.Join(buildDir, "lib", "spring-boot-3.1.5.jar"), []byte("fake"), 0644)).To(Succeed()) + Expect(os.WriteFile(filepath.Join(buildDir, "lib", "spring-boot-4.1.0.jar"), []byte("fake"), 0644)).To(Succeed()) }) It("returns 'Java CF Env'", func() { @@ -81,10 +94,10 @@ var _ = Describe("Java CF Env", func() { }) }) - Context("with Spring Boot 3.x JAR in WEB-INF/lib", func() { + Context("with Spring Boot 4.1.x JAR in WEB-INF/lib", func() { BeforeEach(func() { Expect(os.MkdirAll(filepath.Join(buildDir, "WEB-INF", "lib"), 0755)).To(Succeed()) - Expect(os.WriteFile(filepath.Join(buildDir, "WEB-INF", "lib", "spring-boot-3.0.0.jar"), []byte("fake"), 0644)).To(Succeed()) + Expect(os.WriteFile(filepath.Join(buildDir, "WEB-INF", "lib", "spring-boot-4.1.0.jar"), []byte("fake"), 0644)).To(Succeed()) }) It("returns 'Java CF Env'", func() { @@ -94,12 +107,12 @@ var _ = Describe("Java CF Env", func() { }) }) - Context("with Spring-Boot-Version: 3.x in META-INF/MANIFEST.MF", func() { + Context("with Spring-Boot-Version: 4.1.x in META-INF/MANIFEST.MF", func() { BeforeEach(func() { Expect(os.MkdirAll(filepath.Join(buildDir, "META-INF"), 0755)).To(Succeed()) Expect(os.WriteFile( filepath.Join(buildDir, "META-INF", "MANIFEST.MF"), - []byte("Manifest-Version: 1.0\nSpring-Boot-Version: 3.2.0\nMain-Class: org.springframework.boot.loader.JarLauncher\n"), + []byte("Manifest-Version: 1.0\nSpring-Boot-Version: 4.1.0\nMain-Class: org.springframework.boot.loader.JarLauncher\n"), 0644, )).To(Succeed()) }) @@ -206,6 +219,12 @@ var _ = Describe("Java CF Env", func() { }) }) + Describe("DependencyIdentifier", func() { + It("returns java-cfenv so supply.go can look up the manifest default version", func() { + Expect(fw.DependencyIdentifier()).To(Equal("java-cfenv")) + }) + }) + Describe("Finalize", func() { Context("when the JAR is present", func() { BeforeEach(func() { diff --git a/src/java/supply/supply.go b/src/java/supply/supply.go index afcd8d0943..4fa238a6fe 100644 --- a/src/java/supply/supply.go +++ b/src/java/supply/supply.go @@ -181,5 +181,7 @@ func (s *Supplier) frameworkVersionSuffix(framework frameworks.Framework) string return "" } - return fmt.Sprintf(" (%s)", dependency.Version) + // "manifest default" — the default version listed in manifest.yml, not necessarily what + // gets installed (e.g. java-cfenv resolves to a Spring-Boot-major-specific version). + return fmt.Sprintf(" (manifest default: %s)", dependency.Version) }