Last Updated: December 13, 2025
Migration Status: Complete (Ruby → Go)
- Overview
- Directory Structure
- Component Types
- Buildpack Lifecycle
- Key Architectural Patterns
- Component Interface
- Configuration System
- Dependency Management
- Cloud Foundry Integration
The Cloud Foundry Java Buildpack is implemented in Go and follows Cloud Foundry's V3 buildpack API. The buildpack is responsible for:
- Detecting if an application is a Java application
- Supplying dependencies (JRE, frameworks, libraries) during staging
- Finalizing runtime configuration and generating the launch command
- Modularity: Components are independent and composable
- Convention over Configuration: Sensible defaults with override capability
- Declarative Configuration: YAML-based configuration system
- Lifecycle Separation: Clear separation between staging and runtime phases
java-buildpack/
├── bin/
│ ├── compile # Legacy V2 API entrypoint
│ ├── detect # Detection phase entrypoint
│ ├── release # Release phase entrypoint
│ ├── finalize # Finalize phase entrypoint (V3)
│ └── supply # Supply phase entrypoint (V3)
│
├── src/integration/ # Integraion tests
├── src/java/ # Go source code
| ├── common/ # Common libbuildpack integrations
│ ├── containers/ # Container implementations
│ ├── frameworks/ # Framework implementations
│ ├── jres/ # JRE implementations
| ├── resources/ # Resource configuration files
│ ├── supply/ # Supply phase orchestration
│ │ └── cli/ # Supply CLI entrypoint
│ └── finalize/ # Finalize phase orchestration
│ └── cli/ # Finalize CLI entrypoint
│
├── docs/ # Documentation
├── ci/ # CI scripts
└── scripts/ # Build and test scripts
The buildpack uses three main component types:
Purpose: Define how the application will be executed
Responsibilities:
- Detect application type (Spring Boot, Tomcat, Groovy, etc.)
- Download and configure the container/runtime
- Generate the launch command
Examples:
spring_boot.go- Spring Boot embedded server detectiontomcat.go- Traditional WAR file deploymentjava_main.go- Simple Java main class executiongroovy.go- Groovy script execution
Location: src/java/containers/
Selection: Only ONE container can be selected per application
Purpose: Add additional capabilities and transformations
Responsibilities:
- Detect required frameworks (via service bindings, files, etc.)
- Download and install agents, libraries, transformers
- Configure Java options, environment variables
- Generate profile.d scripts for runtime setup
Examples:
new_relic.go- New Relic APM agentjava_cf_env.go- Cloud Foundry environment integrationpostgresql_jdbc.go- PostgreSQL JDBC driver injectionjmx.go- JMX remote access configuration
Location: src/java/frameworks/
Selection: MULTIPLE frameworks can be active simultaneously
Purpose: Provide the Java runtime for the application
Responsibilities:
- Detect required JRE version
- Download and install the JRE
- Configure memory settings (via memory calculator)
- Install JVM utilities (jvmkill agent)
Examples:
openjdk.go- OpenJDK JRE (default)zulu.go- Azul Zulu JREgraalvm.go- GraalVMsapmachine.go- SAP Machine JRE
Location: src/java/jres/
Selection: Only ONE JRE can be selected per application
The buildpack follows Cloud Foundry's V3 lifecycle with four phases:
Purpose: Determine if this buildpack can run the application
Entry Point: bin/detect
Flow:
1. Check for Java application indicators:
- .jar files
- .war files
- Main-Class in MANIFEST.MF
- Spring Boot markers
- Groovy scripts
- etc.
2. If Java app detected → Exit 0 (success)
3. If not → Exit 1 (failure)
Output: Tags printed to stdout (e.g., open-jdk-jre=17.0.1)
Purpose: Download and install all dependencies
Entry Point: bin/supply → src/java/supply/cli/main.go
Flow:
1. Load component registries for containers, jres and frameworks
2. For each component type (JRE, Frameworks):
a. Run Detect() method
b. If detected, run Supply() method
3. Supply() responsibilities:
- Download dependencies from repositories
- Extract/install to deps directory
- Copy resources
- NO runtime configuration yet
4. Output dependencies to:
<deps_dir>/<deps_idx>/
Key Characteristics:
- Can be run multiple times (multi-buildpack)
- Modifies staging environment only
- Downloads from internet/repositories
- No profile.d generation here
Purpose: Configure runtime environment and generate launch command
Entry Point: bin/finalize → src/java/finalize/cli/main.go
Flow:
1. Load all detected components
2. Select ONE container
3. For each component (Container, Frameworks, JRE):
a. Run Finalize() method
4. Finalize() responsibilities:
- Write profile.d scripts
- Set environment variables
- Configure JAVA_OPTS
- Container generates launch command
5. Output:
- profile.d/*.sh scripts
- launch command (returned by container)
Key Characteristics:
- Runs once (last buildpack only)
- No internet access
- Generates runtime configuration
- Profile.d scripts run before app launch
Purpose: Assemble the start command that the Cloud Controller wil use to start the running java process of the application.
Entry Point: bin/release
Flow:
Output the launch command written previouly in the finalize phase to $BUILD_DIR/tmp/java-buildpack-release-step.yml
Note that the java-buildpack-release-step.yml follows strict predefined structure which is expected from CC.
If anything except this yaml appears in release output, the staged application will fail to start.
Every component receives a Context struct containing:
type Context struct {
Stager *Stager // Build directory, deps directory access
Manifest *Manifest // Dependency version resolution
Installer *Installer // Dependency download/install
Log *Logger // Structured logging
// ... other utilities
}Usage:
func (f *MyFramework) Supply() error {
// Access build directory
buildDir := f.context.Stager.BuildDir()
// Get dependency version
dep, err := f.context.Manifest.DefaultVersion("my-framework")
// Install dependency
targetDir := filepath.Join(f.context.Stager.DepDir(), "my_framework")
err = f.context.Installer.InstallDependency(dep, targetDir)
return nil
}All components implement a consistent interface:
type Component interface {
// Detect returns non-empty string if component applies
Detect() (string, error)
// Supply installs dependencies (staging phase)
Supply() error
// Finalize configures runtime (finalize phase)
Finalize() error
}Runtime configuration is done via profile.d scripts:
func (f *MyFramework) Finalize() error {
script := `#!/bin/bash
export MY_VAR="value"
export JAVA_OPTS="${JAVA_OPTS} -Dmy.property=value"
`
return f.context.Stager.WriteProfileD("my_framework.sh", script)
}Scripts execute: Before app launch, in lexicographic order
Many frameworks detect via service bindings:
func (f *MyFramework) findService() (map[string]interface{}, error) {
vcapServices := os.Getenv("VCAP_SERVICES")
var services map[string][]map[string]interface{}
json.Unmarshal([]byte(vcapServices), &services)
// Search for service by name/label/tag
for _, serviceList := range services {
for _, service := range serviceList {
if matchesPattern(service) {
return service, nil
}
}
}
return nil, errors.New("service not found")
}Dependencies are resolved via buildpack manifest:
// Get default version from manifest
dep, err := f.context.Manifest.DefaultVersion("tomcat")
// Returns: Dependency{Name: "tomcat", Version: "9.0.54", URI: "https://..."}
// Install to target directory
err = f.context.Installer.InstallDependency(dep, targetDir)Signature: Detect() (string, error)
Purpose: Determine if component should be included
Return Values:
- Non-empty string: Component detected (string is used as tag)
- Empty string: Component not applicable
- Error: Detection failed
Example:
func (f *NewRelicFramework) Detect() (string, error) {
// Check for bound New Relic service
service, err := f.findNewRelicService()
if err != nil {
return "", nil // Not detected, not an error
}
// Get version
dep, _ := f.context.Manifest.DefaultVersion("new-relic")
return fmt.Sprintf("new-relic-agent=%s", dep.Version), nil
}Signature: Supply() error
Purpose: Download and install dependencies
Responsibilities:
- Download from internet/repositories
- Extract archives
- Copy files to deps directory
- Prepare for finalize phase
Constraints:
- Must be idempotent
- No runtime configuration
- No profile.d scripts
Example:
func (f *NewRelicFramework) Supply() error {
f.context.Log.BeginStep("Installing New Relic Agent")
// Get dependency from manifest
dep, err := f.context.Manifest.DefaultVersion("new-relic")
if err != nil {
return fmt.Errorf("unable to determine version: %w", err)
}
// Install to deps directory
targetDir := filepath.Join(f.context.Stager.DepDir(), "new_relic")
if err := f.context.Installer.InstallDependency(dep, targetDir); err != nil {
return fmt.Errorf("failed to install: %w", err)
}
f.context.Log.Info("Installed New Relic Agent %s", dep.Version)
return nil
}Signature: Finalize() error
Purpose: Configure runtime environment
Responsibilities:
- Write profile.d scripts
- Set environment variables
- Configure JAVA_OPTS
- Generate launch command (containers only)
Constraints:
- No internet access
- Uses files from supply phase
- Must be fast (impacts staging time)
Example:
func (f *NewRelicFramework) Finalize() error {
// Find installed agent JAR
agentDir := filepath.Join(f.context.Stager.DepDir(), "new_relic")
agentJar := filepath.Join(agentDir, "newrelic.jar")
// Get license key from service binding
service, _ := f.findNewRelicService()
creds := service["credentials"].(map[string]interface{})
licenseKey := creds["license_key"].(string)
// Create profile.d script
script := fmt.Sprintf(`#!/bin/bash
export JAVA_OPTS="${JAVA_OPTS} -javaagent:%s"
export JAVA_OPTS="${JAVA_OPTS} -Dnewrelic.config.license_key=%s"
`, agentJar, licenseKey)
return f.context.Stager.WriteProfileD("new_relic.sh", script)
}Users can override configuration via environment variables:
Operator-level (foundation-wide):
JBP_DEFAULT_OPEN_JDK_JRE='{ jre: { version: 17.+ } }'Application-level:
cf set-env my-app JBP_CONFIG_OPEN_JDK_JRE '{ jre: { version: 11.+ } }'File: manifest.yml (generated during packaging)
Purpose: Declare available dependencies and their locations
Structure:
dependencies:
- name: openjdk
version: 17.0.5
uri: https://github.com/adoptium/temurin17-binaries/.../OpenJDK17.tar.gz
sha256: abc123...
stacks:
- cflinuxfs4- Component requests dependency:
Manifest.DefaultVersion("openjdk") - Manifest finds matching version (version ranges supported)
- Returns
Dependencystruct with URI and metadata - Installer downloads and verifies (checksum)
- Installer extracts to target directory
Supports semantic versioning with wildcards:
17.+- Latest 17.x version11.0.+- Latest 11.0.x version8.+- Latest 8.x version
The buildpack uses CF environment variables:
VCAP_SERVICES- Service binding informationVCAP_APPLICATION- Application metadataCF_STACK- Stack name (cflinuxfs4, etc.)BP_*- Buildpack configuration variables
Frameworks detect services via VCAP_SERVICES:
{
"postgresql": [{
"name": "my-db",
"credentials": {
"uri": "postgres://...",
"username": "user",
"password": "pass"
}
}]
}Scripts in <app>/.profile.d/ run before app launch:
# Execution order
1. System profile.d scripts
2. Buildpack profile.d scripts (alphabetical)
3. Application launch command- JREs - Install Java runtime first
- Frameworks - Process in
registry.RegisterStandardFrameworks()order
- JRE - Configure Java runtime
- Frameworks - Process in
registry.RegisterStandardFrameworks()order - Container - Generate launch command (last)
Important: JavaOpts framework must be last to allow user overrides
See DEVELOPING.md for detailed development instructions.
Quick Start:
# Build
./scripts/build.sh
# Run tests
./scripts/unit.sh
./scripts/integration.sh
# Package
./scripts/package.sh- DEVELOPING.md - Development setup and workflow
- IMPLEMENTING_FRAMEWORKS.md - Framework implementation guide
- IMPLEMENTING_CONTAINERS.md - Container implementation guide
- IMPLEMENTING_JRES.md - JRE implementation guide
- TESTING.md - Testing guide
- design.md - High-level design overview
This buildpack was migrated from Ruby to Go in 2025. Key differences:
| Aspect | Ruby Buildpack | Go Buildpack |
|---|---|---|
| Language | Ruby | Go |
| API Version | V2 (compile/release) | V3 (supply/finalize) |
| Base Classes | BaseComponent, ModularComponent | Interface-based |
| Configuration | Ruby DSL | YAML + env vars |
| Lifecycle | detect→compile→release | detect→supply→finalize |
| Multi-buildpack | Via framework | Native CF support |
Questions or issues? See CONTRIBUTING.md for how to get help.