Skip to content

java-cfenv JDBC auto-config broken on Spring Boot 3+/4.x — spring.datasource.url never set #1406

Description

@stokpop

Summary

Buildpack-injected java-cfenv silently fails to set spring.datasource.url (and username/password) from VCAP_SERVICES on Spring Boot 3.x and 4.x applications. Cloud profile activation works (since #1352); JDBC mapping does not.

Migration impact (v4 → v5)

Apps without java-cfenv in their pom.xml migrating from Ruby v4 to Go v5:

Feature Ruby v4 + SB2 Ruby v4 + SB3+ Go v5 + SB3+/SB4
Cloud profile ✅ auto-reconfiguration ❌ core jar, no spring.factories ✅ fat jar (since #1352)
JDBC auto-config ✅ auto-reconfiguration ❌ core jar, no spring.factories ❌ fat jar bugs

SB2 apps migrating to v5: auto-reconfiguration is disabled by default in Go v5. java-cfenv does not detect SB2. Cloud profile and JDBC auto-config are silently lost — no error, app falls back to whatever is in application.yml. Recovery: set JBP_CONFIG_SPRING_AUTO_RECONFIGURATION='{enabled: true}', but these apps should upgrade to SB4.

SB3+/SB4 apps: cloud profile now works on v5 (gained via #1352). JDBC mapping was never functional on any buildpack version — see root cause below.

History

For SB2 apps, auto-reconfiguration (Spring Cloud Connectors) handled both cloud profile and JDBC auto-config. This worked because SB2's classloader had no classpath isolation.

When java-cfenv was introduced as the SB3+ replacement, it was assumed to provide the same functionality. However:

  • Ruby v4 shipped java-cfenv core jar (19KB, io.pivotal.cfenv:java-cfenv) — not java-cfenv-all. The core jar has no spring.factories at all — neither cloud profile nor JDBC mapping ever worked on v4 + SB3+. Additionally, java-cfenv's presence in components.yml (ordered before SpringAutoReconfiguration) blocks auto-reconfiguration from installing on SB3+ apps (return if java_cf_env_framework?), so the working SB2 mechanism is explicitly disabled.

  • Go v5 early shipped the same core jar — same problem.

  • Go v5 after fix(java-cfenv): add the mirrored java-cfenv-all jar to the classpath #1352 (2026-07-09) switched to java-cfenv-all (fat jar) — cloud profile fixed (CloudProfileApplicationListener present in spring.factories), but JDBC mapping still broken due to fat jar bugs described below.

  • Go v5 also changed the jar injection mechanism from v4's staging-time symlinks into BOOT-INF/lib/ (additional_libraries.link_to) to CLASSPATH env var + runtime symlink (zzz_classpath_symlinks.sh). This places jars on the system classpath, outside LaunchedClassLoader's classpath isolation.

In short: JDBC auto-config via java-cfenv has never worked on SB3+, on any buildpack version.

Root cause

The java-cfenv-all fat jar has two bugs:

  1. Broken spring.factories — Gradle Shadow drops EnvironmentPostProcessor entries during merge. Only ApplicationListener=CloudProfileApplicationListener survives. CfDataSourceEnvironmentPostProcessor is never discovered by Spring Boot.

  2. Bundles Spring Boot as compile scope — the 8MB fat jar includes all of Spring Boot. When loaded by LaunchedClassLoader, the same classes exist in two classloaders (system CL from fat jar + LaunchedClassLoader from BOOT-INF/lib/spring-boot-*.jar). Even if spring.factories is fixed, isAssignableFrom returns falseIllegalArgumentException: Class [CfDataSourceEnvironmentPostProcessor] is not assignable to factory type [EnvironmentPostProcessor].

Additionally, Go v5 places the jar on the system classpath via CLASSPATH env var. For SB3+, framework jars must be in BOOT-INF/lib/ to be loaded by LaunchedClassLoader, which resolves Spring Boot interfaces from its own classpath.

The java-cfenv thin jars

The java-cfenv-all fat jar bundles these three modules (plus transitive deps). Each has a distinct role:

  • java-cfenv (core, 19KB) — parses VCAP_SERVICES JSON into CfService objects. No Spring dependency. Foundation for the other modules.

  • java-cfenv-boot (29KB) — Spring Boot integration. Contains CloudProfileApplicationListener (activates cloud profile) and CfEnvironmentPostProcessor (maps service credentials to Spring properties). Has correct spring.factories registering both EnvironmentPostProcessor and ApplicationListener entries. Pulls in java-cfenv transitively.

  • java-cfenv-jdbc (17KB) — JDBC-specific mapping. Contains CfDataSourceEnvironmentPostProcessor which detects database URIs (postgres://, mysql://, etc.) and maps them to spring.datasource.url, spring.datasource.username, spring.datasource.password. Registered via META-INF/services/ SPI file.

The thin jars have correct spring.factories/SPI, no bundled Spring Boot (~1.5MB total vs 8MB fat jar), and work when placed in BOOT-INF/lib/.

Proven via 5 experiments

# What Result
1 Remove classpath.idx Still 404
2 Copy fat jar to BOOT-INF/lib/ + classpath.idx Jar loads (visible in /loaded-jars), still 404
3 Write correct spring.factories to BOOT-INF/classes/META-INF/ Crash: not assignable to EnvironmentPostProcessor
4 All three combined Same crash
5 Thin jars in BOOT-INF/lib/ spring.datasource.url: jdbc:postgresql://host:5432/dbname

Fix plan

  1. Fix java-cfenv-all upstream (pivotal-cf/java-cfenv): mark spring-boot as provided scope, fix spring.factories merge in Gradle Shadow build. See java-cfenv-all fat jar: broken spring.factories + Spring Boot leaking into uber jar pivotal-cf/java-cfenv#470.
  2. Change java_cf_env.go to copy jar into BOOT-INF/lib/ + classpath.idx at staging time — required even with fixed fat jar, because LaunchedClassLoader cannot resolve Spring Boot interfaces from the system classpath

Workaround: apps can add java-cfenv-boot and java-cfenv-jdbc as Maven/Gradle dependencies (4.0.1 for SB4, 3.5.2 for SB3). java-cfenv-boot pulls in java-cfenv transitively. The buildpack's hasJavaCfEnv() detects java-cfenv*.jar in BOOT-INF/lib/ and skips injection.

Affects

  • java-cfenv-all 3.5.2 and 4.0.1
  • All SB3+/SB4 exploded jars using buildpack java-cfenv injection
  • Integration test spring_boot_test.go VCAP mapping assertions timeout waiting for spring.datasource.url value that is never set

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions