KeyEnvKeyEnv
SDKs & Integrations

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 @Value injection
  • Environment-aware configuration via spring.profiles.active
  • Type-safe @ConfigurationProperties binding
  • 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.java

Application.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: 86400000

UserService.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):

VariableDescription
KEYENV_TOKENYour KeyEnv service token
KEYENV_PROJECTYour KeyEnv project ID
KEYENV_ENVKeyEnv environment (optional, derived from Spring profile)
SPRING_PROFILES_ACTIVESpring profile (production, staging, etc.)

Secrets to Store in KeyEnv

Store these secrets in KeyEnv for each environment:

SecretDescriptionExample
DATABASE_URLJDBC connection URLjdbc:postgresql://host:5432/db
DB_USERDatabase usernamemyapp
DB_PASSWORDDatabase passwordsecure-password
REDIS_URLRedis connection URLredis://localhost:6379
JWT_SECRETJWT signing secretyour-256-bit-secret
STRIPE_SECRET_KEYStripe API keysk_live_...
STRIPE_WEBHOOK_SECRETStripe webhook secretwhsec_...
SENDGRID_API_KEYSendGrid API keySG.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 .env

Load 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

  1. Ensure secrets are loaded before Spring processes @Value annotations (use ApplicationContextInitializer)
  2. Check that the secret key matches exactly (case-sensitive)
  3. 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();

On this page