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
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,9 @@ See the README.md file in each main sample directory for cut/paste Gradle comman

- [**Worker Versioning**](/core/src/main/java/io/temporal/samples/workerversioning): Demonstrates how to use worker versioning to manage workflow code changes.

- [**Environment Configuration**](/core/src/main/java/io/temporal/samples/envconfig):
Load client configuration from TOML files with programmatic overrides.

#### API demonstrations

- [**Async Untyped Child Workflow**](/core/src/main/java/io/temporal/samples/asyncuntypedchild): Demonstrates how to invoke an untyped child workflow async, that can complete after parent workflow is already completed.
Expand Down
3 changes: 3 additions & 0 deletions core/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@ dependencies {
implementation "io.temporal:temporal-opentracing:$javaSDKVersion"
testImplementation("io.temporal:temporal-testing:$javaSDKVersion")

// Environment configuration
implementation "io.temporal:temporal-envconfig:$javaSDKVersion"

// Needed for SDK related functionality
implementation(platform("com.fasterxml.jackson:jackson-bom:2.17.2"))
implementation "com.fasterxml.jackson.core:jackson-databind"
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
package io.temporal.samples.envconfig;

import io.temporal.client.WorkflowClient;
import io.temporal.client.WorkflowClientOptions;
import io.temporal.envconfig.ClientConfigProfile;
import io.temporal.envconfig.LoadClientConfigProfileOptions;
import io.temporal.serviceclient.WorkflowServiceStubs;
import io.temporal.serviceclient.WorkflowServiceStubsOptions;
import java.nio.file.Paths;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
* This sample demonstrates loading the default environment configuration profile from a TOML file.
*/
public class LoadFromFile {

private static final Logger logger = LoggerFactory.getLogger(LoadFromFile.class);

public static void main(String[] args) {
try {
// For this sample to be self-contained, we explicitly provide the path to
// the config.toml file included in this directory.
// By default though, the config.toml file will be loaded from
// ~/.config/temporal/temporal.toml (or the equivalent standard config directory on your OS).
String configFilePath =
Paths.get(LoadFromFile.class.getResource("/config.toml").toURI()).toString();

logger.info("--- Loading 'default' profile from {} ---", configFilePath);

// Load client profile from file. By default, this loads the "default" profile
// and applies any environment variable overrides.
ClientConfigProfile profile =
ClientConfigProfile.load(
LoadClientConfigProfileOptions.newBuilder()
.setConfigFilePath(configFilePath)
.build());

// Convert profile to client options (equivalent to Python's load_client_connect_config)
WorkflowServiceStubsOptions serviceStubsOptions = profile.toWorkflowServiceStubsOptions();
WorkflowClientOptions clientOptions = profile.toWorkflowClientOptions();

logger.info("Loaded 'default' profile from {}", configFilePath);
logger.info(" Address: {}", serviceStubsOptions.getTarget());
logger.info(" Namespace: {}", clientOptions.getNamespace());
if (serviceStubsOptions.getHeaders() != null
&& !serviceStubsOptions.getHeaders().keys().isEmpty()) {
logger.info(" gRPC Metadata keys: {}", serviceStubsOptions.getHeaders().keys());
}

logger.info("\nAttempting to connect to client...");

try {
// Create the workflow client using the loaded configuration
WorkflowClient client =
WorkflowClient.newInstance(
WorkflowServiceStubs.newServiceStubs(serviceStubsOptions), clientOptions);

// Test the connection by getting system info
var systemInfo =
client
.getWorkflowServiceStubs()
.blockingStub()
.getSystemInfo(
io.temporal.api.workflowservice.v1.GetSystemInfoRequest.getDefaultInstance());

logger.info("✅ Client connected successfully!");
logger.info(" Server version: {}", systemInfo.getServerVersion());

} catch (Exception e) {
logger.error("❌ Failed to connect: {}", e.getMessage());
}

} catch (Exception e) {
logger.error("Failed to load configuration: {}", e.getMessage(), e);
System.exit(1);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
package io.temporal.samples.envconfig;

import io.temporal.client.WorkflowClient;
import io.temporal.client.WorkflowClientOptions;
import io.temporal.envconfig.ClientConfigProfile;
import io.temporal.envconfig.LoadClientConfigProfileOptions;
import io.temporal.serviceclient.WorkflowServiceStubs;
import io.temporal.serviceclient.WorkflowServiceStubsOptions;
import java.nio.file.Paths;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
* This sample demonstrates loading a specific profile from a TOML configuration file with
* programmatic overrides.
*/
public class LoadProfile {

private static final Logger logger = LoggerFactory.getLogger(LoadProfile.class);

public static void main(String[] args) {
String profileName = "staging";

try {
// For this sample to be self-contained, we explicitly provide the path to
// the config.toml file included in this directory.
String configFilePath =
Paths.get(LoadProfile.class.getResource("/config.toml").toURI()).toString();

logger.info("--- Loading '{}' profile from {} ---", profileName, configFilePath);

// Load specific profile from file with environment variable overrides
ClientConfigProfile profile =
ClientConfigProfile.load(
LoadClientConfigProfileOptions.newBuilder()
.setConfigFilePath(configFilePath)
.setConfigFileProfile(profileName)
.build());

// Demonstrate programmatic override - fix the incorrect address from staging profile
logger.info("\n--- Applying programmatic override ---");
ClientConfigProfile.Builder profileBuilder = profile.toBuilder();
profileBuilder.setAddress("localhost:7233"); // Override the incorrect address
profile = profileBuilder.build();
logger.info(" Overridden address to: {}", profile.getAddress());

// Convert profile to client options (equivalent to Python's load_client_connect_config)
WorkflowServiceStubsOptions serviceStubsOptions = profile.toWorkflowServiceStubsOptions();
WorkflowClientOptions clientOptions = profile.toWorkflowClientOptions();

logger.info("Loaded '{}' profile from {}", profileName, configFilePath);
logger.info(" Address: {}", serviceStubsOptions.getTarget());
logger.info(" Namespace: {}", clientOptions.getNamespace());
if (serviceStubsOptions.getHeaders() != null
&& !serviceStubsOptions.getHeaders().keys().isEmpty()) {
logger.info(" gRPC Metadata keys: {}", serviceStubsOptions.getHeaders().keys());
}

logger.info("\nAttempting to connect to client...");

try {
// Create the workflow client using the loaded configuration
WorkflowClient client =
WorkflowClient.newInstance(
WorkflowServiceStubs.newServiceStubs(serviceStubsOptions), clientOptions);

// Test the connection by getting system info
var systemInfo =
client
.getWorkflowServiceStubs()
.blockingStub()
.getSystemInfo(
io.temporal.api.workflowservice.v1.GetSystemInfoRequest.getDefaultInstance());

logger.info("✅ Client connected successfully!");
logger.info(" Server version: {}", systemInfo.getServerVersion());

} catch (Exception e) {
logger.error("❌ Failed to connect: {}", e.getMessage());
}

} catch (Exception e) {
logger.error("Failed to load configuration: {}", e.getMessage(), e);
System.exit(1);
}
}
}
18 changes: 18 additions & 0 deletions core/src/main/java/io/temporal/samples/envconfig/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# Environment Configuration Sample

This sample demonstrates how to configure a Temporal client using TOML configuration files. This allows you to manage connection settings across different environments without hardcoding them.

The `config.toml` file defines three profiles:
- `[profile.default]`: Local development configuration
- `[profile.staging]`: Configuration with incorrect address to demonstrate overrides
- `[profile.prod]`: Example production configuration (not runnable)

**Load from file (default profile):**
```bash
./gradlew -q execute -PmainClass=io.temporal.samples.envconfig.LoadFromFile
```

**Load specific profile with overrides:**
```bash
./gradlew -q execute -PmainClass=io.temporal.samples.envconfig.LoadProfile
```
40 changes: 40 additions & 0 deletions core/src/main/resources/config.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
# This is a sample configuration file for demonstrating Temporal's environment
# configuration feature. It defines multiple profiles for different environments,
# such as local development, production, and staging.

# Default profile for local development
[profile.default]
address = "localhost:7233"
namespace = "default"

# Optional: Add custom gRPC headers
[profile.default.grpc_meta]
my-custom-header = "development-value"
trace-id = "dev-trace-123"

# Staging profile with inline certificate data
[profile.staging]
address = "localhost:9999"
namespace = "staging"

# An example production profile for Temporal Cloud
[profile.prod]
address = "your-namespace.a1b2c.tmprl.cloud:7233"
namespace = "your-namespace"
# Replace with your actual Temporal Cloud API key
api_key = "your-api-key-here"

# TLS configuration for production
[profile.prod.tls]
# TLS is auto-enabled when an API key is present, but you can configure it
# explicitly.
# disabled = false

# Use certificate files for mTLS. Replace with actual paths.
client_cert_path = "/etc/temporal/certs/client.pem"
client_key_path = "/etc/temporal/certs/client.key"

# Custom headers for production
[profile.prod.grpc_meta]
environment = "production"
service-version = "v1.2.3"
Loading