Spring Boot Integration
Use KeyEnv with Spring Boot applications for secure secrets management.
Spring Boot Integration
Integrate KeyEnv with your Spring Boot application to securely manage secrets like database credentials, API keys, and external service configurations.
Features
- Auto-configuration with Spring Boot starter
- PropertySource integration for seamless
@Valueinjection - Environment-aware configuration via
spring.profiles.active - Type-safe
@ConfigurationPropertiesbinding - Works with Spring Cloud Config
- Caching for optimal performance
Installation
Maven
Add the JitPack repository and dependency to your pom.xml:
<repositories>
<repository>
<id>jitpack.io</id>
<url>https://jitpack.io</url>
</repository>
</repositories>
<dependency>
<groupId>com.github.keyenv</groupId>
<artifactId>java-sdk</artifactId>
<version>v1.1.0</version>
</dependency>Gradle
repositories {
maven { url 'https://jitpack.io' }
}
dependencies {
implementation 'com.github.keyenv:java-sdk:v1.1.0'
}Quick Start
The simplest approach loads secrets into system properties at application startup:
// Application.java
import dev.keyenv.KeyEnv;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import jakarta.annotation.PostConstruct;
@SpringBootApplication
public class Application {
@PostConstruct
public void loadSecrets() {
String token = System.getenv("KEYENV_TOKEN");
String projectId = System.getenv("KEYENV_PROJECT");
String environment = System.getenv("KEYENV_ENV");
if (token != null && projectId != null) {
KeyEnv client = KeyEnv.create(token);
int count = client.loadEnv(projectId, environment != null ? environment : "development");
System.out.println("Loaded " + count + " secrets from KeyEnv");
}
}
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}Now use secrets in your configuration:
@Service
public class PaymentService {
@Value("${STRIPE_SECRET_KEY}")
private String stripeSecretKey;
// Use the secret...
}Application Configuration
application.properties
Configure your application using secrets loaded from KeyEnv:
# application.properties
# Database (values come from KeyEnv)
spring.datasource.url=${DATABASE_URL}
spring.datasource.username=${DB_USER}
spring.datasource.password=${DB_PASSWORD}
# Redis
spring.data.redis.url=${REDIS_URL}
# External APIs
app.stripe.secret-key=${STRIPE_SECRET_KEY}
app.sendgrid.api-key=${SENDGRID_API_KEY}application.yml
# application.yml
spring:
datasource:
url: ${DATABASE_URL}
username: ${DB_USER}
password: ${DB_PASSWORD}
data:
redis:
url: ${REDIS_URL}
app:
stripe:
secret-key: ${STRIPE_SECRET_KEY}
sendgrid:
api-key: ${SENDGRID_API_KEY}Environment-Aware Configuration
Mapping Spring Profiles to KeyEnv Environments
Map spring.profiles.active to KeyEnv environments:
import dev.keyenv.KeyEnv;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.core.env.Environment;
import jakarta.annotation.PostConstruct;
@SpringBootApplication
public class Application {
private final Environment springEnv;
public Application(Environment springEnv) {
this.springEnv = springEnv;
}
@PostConstruct
public void loadSecrets() {
String token = System.getenv("KEYENV_TOKEN");
String projectId = System.getenv("KEYENV_PROJECT");
if (token == null || projectId == null) {
return; // Skip in local development
}
// Map Spring profile to KeyEnv environment
String keyenvEnv = mapProfileToEnvironment();
KeyEnv client = KeyEnv.create(token);
client.loadEnv(projectId, keyenvEnv);
}
private String mapProfileToEnvironment() {
String[] profiles = springEnv.getActiveProfiles();
if (profiles.length == 0) {
return "development";
}
String profile = profiles[0];
return switch (profile) {
case "prod", "production" -> "production";
case "staging", "stage" -> "staging";
case "test", "testing" -> "testing";
default -> "development";
};
}
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}Profile-Specific Loading
Load different secrets based on Spring profiles:
@Configuration
@Profile("!local") // Don't load from KeyEnv in local profile
public class KeyEnvConfig {
@Value("${spring.profiles.active:development}")
private String activeProfile;
@PostConstruct
public void loadSecrets() {
String token = System.getenv("KEYENV_TOKEN");
String projectId = System.getenv("KEYENV_PROJECT");
if (token != null && projectId != null) {
KeyEnv client = KeyEnv.create(token);
client.loadEnv(projectId, activeProfile);
}
}
}PropertySource Integration
Custom PropertySource
Create a custom PropertySource to integrate KeyEnv with Spring's property resolution:
import dev.keyenv.KeyEnv;
import org.springframework.core.env.PropertySource;
import java.util.Map;
public class KeyEnvPropertySource extends PropertySource<Map<String, String>> {
public KeyEnvPropertySource(String name, Map<String, String> source) {
super(name, source);
}
@Override
public Object getProperty(String name) {
return source.get(name);
}
public static KeyEnvPropertySource create(KeyEnv client, String projectId, String environment) {
Map<String, String> secrets = client.exportSecretsAsMap(projectId, environment);
return new KeyEnvPropertySource("keyenv", secrets);
}
}Register PropertySource Early
Register the PropertySource before Spring processes configuration:
import dev.keyenv.KeyEnv;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ApplicationContextInitializer;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.core.env.ConfigurableEnvironment;
@SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication app = new SpringApplication(Application.class);
app.addInitializers(new KeyEnvInitializer());
app.run(args);
}
}
class KeyEnvInitializer implements ApplicationContextInitializer<ConfigurableApplicationContext> {
@Override
public void initialize(ConfigurableApplicationContext context) {
String token = System.getenv("KEYENV_TOKEN");
String projectId = System.getenv("KEYENV_PROJECT");
String environment = System.getenv("KEYENV_ENV");
if (token == null || projectId == null) {
return;
}
KeyEnv client = KeyEnv.create(token);
KeyEnvPropertySource propertySource = KeyEnvPropertySource.create(
client,
projectId,
environment != null ? environment : "development"
);
ConfigurableEnvironment env = context.getEnvironment();
env.getPropertySources().addFirst(propertySource);
}
}ConfigurationProperties Binding
Type-Safe Configuration
Use @ConfigurationProperties for type-safe secret binding:
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
@Component
@ConfigurationProperties(prefix = "app")
public class AppProperties {
private Stripe stripe = new Stripe();
private Database database = new Database();
private Redis redis = new Redis();
private SendGrid sendgrid = new SendGrid();
// Getters and setters...
public static class Stripe {
private String secretKey;
private String webhookSecret;
// Getters and setters...
public String getSecretKey() { return secretKey; }
public void setSecretKey(String secretKey) { this.secretKey = secretKey; }
public String getWebhookSecret() { return webhookSecret; }
public void setWebhookSecret(String webhookSecret) { this.webhookSecret = webhookSecret; }
}
public static class Database {
private String url;
private String username;
private String password;
// Getters and setters...
public String getUrl() { return url; }
public void setUrl(String url) { this.url = url; }
public String getUsername() { return username; }
public void setUsername(String username) { this.username = username; }
public String getPassword() { return password; }
public void setPassword(String password) { this.password = password; }
}
public static class Redis {
private String url;
private String password;
// Getters and setters...
public String getUrl() { return url; }
public void setUrl(String url) { this.url = url; }
public String getPassword() { return password; }
public void setPassword(String password) { this.password = password; }
}
public static class SendGrid {
private String apiKey;
public String getApiKey() { return apiKey; }
public void setApiKey(String apiKey) { this.apiKey = apiKey; }
}
// Root getters and setters
public Stripe getStripe() { return stripe; }
public void setStripe(Stripe stripe) { this.stripe = stripe; }
public Database getDatabase() { return database; }
public void setDatabase(Database database) { this.database = database; }
public Redis getRedis() { return redis; }
public void setRedis(Redis redis) { this.redis = redis; }
public SendGrid getSendgrid() { return sendgrid; }
public void setSendgrid(SendGrid sendgrid) { this.sendgrid = sendgrid; }
}Mapping Secrets to Properties
In your application.yml, map KeyEnv secrets to configuration properties:
# application.yml
app:
stripe:
secret-key: ${STRIPE_SECRET_KEY}
webhook-secret: ${STRIPE_WEBHOOK_SECRET}
database:
url: ${DATABASE_URL}
username: ${DB_USER}
password: ${DB_PASSWORD}
redis:
url: ${REDIS_URL}
password: ${REDIS_PASSWORD:}
sendgrid:
api-key: ${SENDGRID_API_KEY}Using Configuration Properties
@Service
public class PaymentService {
private final AppProperties properties;
public PaymentService(AppProperties properties) {
this.properties = properties;
}
public void processPayment() {
Stripe.apiKey = properties.getStripe().getSecretKey();
// Process payment...
}
}Complete Example
Here's a complete Spring Boot application with KeyEnv integration:
Project Structure
src/main/java/com/example/
├── Application.java
├── config/
│ ├── KeyEnvConfig.java
│ ├── KeyEnvPropertySource.java
│ └── AppProperties.java
├── service/
│ └── UserService.java
└── controller/
└── UserController.javaApplication.java
package com.example;
import com.example.config.KeyEnvPropertySource;
import dev.keyenv.KeyEnv;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.ApplicationContextInitializer;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.core.env.ConfigurableEnvironment;
@SpringBootApplication
@EnableConfigurationProperties
public class Application {
public static void main(String[] args) {
SpringApplication app = new SpringApplication(Application.class);
app.addInitializers(new KeyEnvInitializer());
app.run(args);
}
static class KeyEnvInitializer implements ApplicationContextInitializer<ConfigurableApplicationContext> {
@Override
public void initialize(ConfigurableApplicationContext context) {
String token = System.getenv("KEYENV_TOKEN");
String projectId = System.getenv("KEYENV_PROJECT");
if (token == null || projectId == null) {
System.out.println("KeyEnv not configured, using local environment");
return;
}
// Determine environment from Spring profile or KEYENV_ENV
ConfigurableEnvironment env = context.getEnvironment();
String keyenvEnv = System.getenv("KEYENV_ENV");
if (keyenvEnv == null) {
String[] profiles = env.getActiveProfiles();
keyenvEnv = profiles.length > 0 ? mapProfile(profiles[0]) : "development";
}
try {
KeyEnv client = KeyEnv.create(token);
var secrets = client.exportSecretsAsMap(projectId, keyenvEnv);
env.getPropertySources().addFirst(new KeyEnvPropertySource("keyenv", secrets));
System.out.println("Loaded " + secrets.size() + " secrets from KeyEnv (" + keyenvEnv + ")");
} catch (Exception e) {
System.err.println("Failed to load secrets from KeyEnv: " + e.getMessage());
}
}
private String mapProfile(String profile) {
return switch (profile) {
case "prod", "production" -> "production";
case "staging", "stage" -> "staging";
case "test" -> "testing";
default -> "development";
};
}
}
}application.yml
# application.yml
spring:
profiles:
active: ${SPRING_PROFILES_ACTIVE:development}
# Database
datasource:
url: ${DATABASE_URL:jdbc:postgresql://localhost:5432/myapp}
username: ${DB_USER:postgres}
password: ${DB_PASSWORD:}
driver-class-name: org.postgresql.Driver
hikari:
maximum-pool-size: 10
minimum-idle: 5
# JPA
jpa:
hibernate:
ddl-auto: validate
show-sql: false
# Redis
data:
redis:
url: ${REDIS_URL:redis://localhost:6379}
# Application secrets
app:
stripe:
secret-key: ${STRIPE_SECRET_KEY:}
webhook-secret: ${STRIPE_WEBHOOK_SECRET:}
publishable-key: ${STRIPE_PUBLISHABLE_KEY:}
sendgrid:
api-key: ${SENDGRID_API_KEY:}
jwt:
secret: ${JWT_SECRET:dev-secret-change-in-production}
expiration: 86400000UserService.java
package com.example.service;
import com.example.config.AppProperties;
import org.springframework.stereotype.Service;
@Service
public class UserService {
private final AppProperties properties;
public UserService(AppProperties properties) {
this.properties = properties;
}
public void sendWelcomeEmail(String email) {
// Use SendGrid API key from KeyEnv
String apiKey = properties.getSendgrid().getApiKey();
// Send email...
}
}Setting Up Your Deployment
Required Environment Variables
Set these in your deployment platform (not in KeyEnv):
| Variable | Description |
|---|---|
KEYENV_TOKEN | Your KeyEnv service token |
KEYENV_PROJECT | Your KeyEnv project ID |
KEYENV_ENV | KeyEnv environment (optional, derived from Spring profile) |
SPRING_PROFILES_ACTIVE | Spring profile (production, staging, etc.) |
Secrets to Store in KeyEnv
Store these secrets in KeyEnv for each environment:
| Secret | Description | Example |
|---|---|---|
DATABASE_URL | JDBC connection URL | jdbc:postgresql://host:5432/db |
DB_USER | Database username | myapp |
DB_PASSWORD | Database password | secure-password |
REDIS_URL | Redis connection URL | redis://localhost:6379 |
JWT_SECRET | JWT signing secret | your-256-bit-secret |
STRIPE_SECRET_KEY | Stripe API key | sk_live_... |
STRIPE_WEBHOOK_SECRET | Stripe webhook secret | whsec_... |
SENDGRID_API_KEY | SendGrid API key | SG.xxx |
Docker Deployment
Dockerfile
FROM eclipse-temurin:21-jre-alpine
WORKDIR /app
COPY target/*.jar app.jar
ENTRYPOINT ["java", "-jar", "app.jar"]docker-compose.yml
version: '3.8'
services:
app:
build: .
environment:
- KEYENV_TOKEN=${KEYENV_TOKEN}
- KEYENV_PROJECT=${KEYENV_PROJECT}
- SPRING_PROFILES_ACTIVE=production
ports:
- "8080:8080"Local Development
Skip KeyEnv in Development
For local development, you can skip KeyEnv entirely:
@PostConstruct
public void loadSecrets() {
String token = System.getenv("KEYENV_TOKEN");
if (token == null) {
System.out.println("KEYENV_TOKEN not set, using local configuration");
return;
}
// Load from KeyEnv...
}Use .env File Locally
Generate a .env file for local development:
keyenv pull --env development --output .envLoad it with a library like dotenv-java:
import io.github.cdimascio.dotenv.Dotenv;
@SpringBootApplication
public class Application {
public static void main(String[] args) {
// Load .env in development
if (System.getenv("KEYENV_TOKEN") == null) {
Dotenv dotenv = Dotenv.configure().ignoreIfMissing().load();
dotenv.entries().forEach(e ->
System.setProperty(e.getKey(), e.getValue())
);
}
SpringApplication.run(Application.class, args);
}
}Testing
Unit Tests
Mock the configuration properties in tests:
@SpringBootTest
@TestPropertySource(properties = {
"app.stripe.secret-key=sk_test_xxx",
"app.jwt.secret=test-secret"
})
class PaymentServiceTest {
@Autowired
private PaymentService paymentService;
@Test
void shouldProcessPayment() {
// Test with mocked properties
}
}Integration Tests
Use test-specific configuration:
# application-test.yml
app:
stripe:
secret-key: sk_test_xxx
database:
url: jdbc:h2:mem:testdb@SpringBootTest
@ActiveProfiles("test")
class IntegrationTest {
// Tests run with test configuration, not KeyEnv
}Troubleshooting
"KEYENV_TOKEN not set"
In production, ensure KEYENV_TOKEN is set in your deployment platform's environment variables.
"Project not found"
Verify KEYENV_PROJECT matches your project ID in the KeyEnv dashboard.
"Environment not found"
Check that the environment name (development, staging, production) exists in your KeyEnv project.
Secrets not available in @Value
- Ensure secrets are loaded before Spring processes
@Valueannotations (useApplicationContextInitializer) - Check that the secret key matches exactly (case-sensitive)
- Verify the service token has read access to the environment
DataSource fails to initialize
Database properties must be available before Spring creates the DataSource. Use the ApplicationContextInitializer approach to load secrets early enough.
Performance
Secrets are loaded once at startup and cached. There's no runtime API calls after initialization. For optimal performance in containerized environments, consider enabling caching:
KeyEnv client = KeyEnv.builder()
.token(token)
.cacheTtl(Duration.ofMinutes(5))
.build();