Article
In modern application development, managing configuration complexity across multiple environments, services, and teams is a significant challenge. CUE (Configure, Unify, Execute) is a powerful language for defining, validating, and managing configuration data. When combined with Java applications, CUE provides type-safe configuration management, validation, and template generation capabilities that go far beyond traditional configuration formats. This article explores how to integrate CUE with Java applications for robust configuration management.
What is CUE Language?
CUE is an open-source data validation language and inference engine with roots in Go. It combines:
- Schema Definition: Define types and constraints for your data
- Data Validation: Validate configuration against schemas
- Template Generation: Generate configuration for different environments
- Data Unification: Merge multiple configuration sources
Key Benefits for Java Applications
- Type-Safe Configuration: Catch configuration errors at build time
- Multi-Environment Management: Manage dev, staging, prod configs consistently
- Configuration Validation: Ensure configuration meets business rules
- Template Reuse: Avoid configuration duplication
- IDE Support: Get autocomplete and validation in editors
Setting Up CUE with Java
1. CUE Installation
First, install CUE on your system:
# On macOS brew install cue-lang/tap/cue # On Linux wget https://github.com/cue-lang/cue/releases/download/v0.6.0/cue_v0.6.0_linux_amd64.tar.gz tar xzf cue_v0.6.0_linux_amd64.tar.gz sudo mv cue /usr/local/bin/ # Verify installation cue version
2. Java Dependencies
Add CUE Java integration to your pom.xml:
<properties> <cue.java.version>0.6.0</cue.java.version> <jackson.version>2.15.2</jackson.version> </properties> <dependencies> <!-- CUE Java binding --> <dependency> <groupId>org.cuelang</groupId> <artifactId>cue-java</artifactId> <version>${cue.java.version}</version> </dependency> <!-- JSON processing --> <dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-databind</artifactId> <version>${jackson.version}</version> </dependency> <!-- YAML support --> <dependency> <groupId>com.fasterxml.jackson.dataformat</groupId> <artifactId>jackson-dataformat-yaml</artifactId> <version>${jackson.version}</version> </dependency> <!-- For Spring Boot integration --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter</artifactId> <version>3.1.0</version> <optional>true</optional> </dependency> </dependencies> CUE Configuration Examples
1. Basic Application Configuration Schema
Create schemas/app.cue:
package app // Application configuration schema #AppConfig: { // Server configuration server: { port: int & >=1000 & <=9999 host: string | *"localhost" // SSL configuration ssl?: { enabled: bool | *false keyPath: string certPath: string } } // Database configuration database: { url: string username: string password: string pool: { min: int & >=1 & <=20 | *5 max: int & >=5 & <=100 | *20 timeout: int & >=5000 | *30000 } } // Feature flags features: { caching: bool | *true metrics: bool | *false tracing: bool | *false } // Logging configuration logging: { level: "DEBUG" | "INFO" | "WARN" | "ERROR" | *"INFO" format: "JSON" | "TEXT" | *"TEXT" file?: { path: string maxSize: string | *"100MB" maxBackups: int & >=1 | *10 } } } // Environment-specific defaults #Development: #AppConfig & { server: host: "localhost" features: metrics: true logging: level: "DEBUG" } #Production: #AppConfig & { server: host: "0.0.0.0" features: { caching: true metrics: true tracing: true } logging: { level: "WARN" format: "JSON" file: path: "/var/log/app.log" } } 2. Multi-Service Configuration
Create schemas/microservices.cue:
package microservices // Common service configuration #ServiceConfig: { name: string version: string environment: "dev" | "staging" | "production" // Kubernetes resources resources: { requests: { cpu: string | *"100m" memory: string | *"128Mi" } limits: { cpu: string | *"500m" memory: string | *"512Mi" } } // Service discovery discovery: { port: int & >=3000 & <=9999 healthCheck: string | *"/actuator/health" } } // Database service configuration #DatabaseService: #ServiceConfig & { name: "database-service" discovery: port: 5432 // Database-specific config database: { type: "postgres" | "mysql" | "mongodb" schema?: string migrations: { enabled: bool | *true location: string | *"classpath:db/migration" } } } // API service configuration #ApiService: #ServiceConfig & { name: "api-service" discovery: port: 8080 // API-specific config api: { rateLimit: int & >=0 | *1000 cors: { allowedOrigins: [...string] | *["*"] allowedMethods: [...string] | *["GET", "POST", "PUT", "DELETE"] } } // Dependencies dependencies: { database: string cache?: string messaging?: string } } 3. Validation Rules
Create schemas/validation.cue:
package validation // Email validation #Email: =~"^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}$" // URL validation #URL: =~"^https?://[a-zA-Z0-9.-]+(?:\\.[a-zA-Z]{2,})+(?:[/?].*)?$" // User configuration with validation #UserConfig: { username: string & =~"^[a-z0-9_-]{3,20}$" email: #Email profile: { firstName: string & =~"^[A-Za-z ]{1,50}$" lastName: string & =~"^[A-Za-z ]{1,50}$" age: int & >=0 & <=150 } preferences?: { theme: "light" | "dark" | *"light" language: string | *"en" notifications: { email: bool | *true push: bool | *false } } } // Business rules validation #OrderConfig: { orderId: string & =~"^ORD-[A-Z0-9]{8}$" customer: #UserConfig items: [...{ sku: string quantity: int & >0 price: number & >0.0 }] total: number & >0.0 // Business rule: total must equal sum of item prices let itemTotal = sum([for item in items { item.quantity * item.price }]) total: itemTotal } Java Integration
1. CUE Configuration Loader
@Component @Slf4j public class CueConfigurationLoader { private final ObjectMapper objectMapper; private final ProcessBuilder processBuilder; public CueConfigurationLoader() { this.objectMapper = new ObjectMapper(); this.processBuilder = new ProcessBuilder(); } public <T> T loadConfiguration(String cueFile, String expression, Class<T> configType) { try { // Execute CUE command to export configuration as JSON Process process = processBuilder.command( "cue", "export", cueFile, "-e", expression, "--out", "json" ).start(); // Read the JSON output String jsonOutput = readProcessOutput(process); int exitCode = process.waitFor(); if (exitCode != 0) { String errorOutput = readProcessError(process); throw new RuntimeException("CUE evaluation failed: " + errorOutput); } // Parse JSON into Java object return objectMapper.readValue(jsonOutput, configType); } catch (IOException | InterruptedException e) { Thread.currentThread().interrupt(); throw new RuntimeException("Failed to load CUE configuration", e); } } public void validateConfiguration(String cueFile, String expression) { try { Process process = processBuilder.command( "cue", "vet", cueFile, "-e", expression ).start(); int exitCode = process.waitFor(); if (exitCode != 0) { String errorOutput = readProcessError(process); throw new ConfigurationValidationException( "Configuration validation failed: " + errorOutput); } log.info("Configuration validation passed for: {}", expression); } catch (IOException | InterruptedException e) { Thread.currentThread().interrupt(); throw new RuntimeException("Configuration validation failed", e); } } private String readProcessOutput(Process process) throws IOException { try (BufferedReader reader = new BufferedReader( new InputStreamReader(process.getInputStream()))) { return reader.lines().collect(Collectors.joining("\n")); } } private String readProcessError(Process process) throws IOException { try (BufferedReader reader = new BufferedReader( new InputStreamReader(process.getErrorStream()))) { return reader.lines().collect(Collectors.joining("\n")); } } } class ConfigurationValidationException extends RuntimeException { public ConfigurationValidationException(String message) { super(message); } } 2. Configuration Data Classes
@Data @Builder @NoArgsConstructor @AllArgsConstructor public class AppConfig { private ServerConfig server; private DatabaseConfig database; private FeaturesConfig features; private LoggingConfig logging; @Data public static class ServerConfig { private int port; private String host; private SslConfig ssl; @Data public static class SslConfig { private boolean enabled; private String keyPath; private String certPath; } } @Data public static class DatabaseConfig { private String url; private String username; private String password; private PoolConfig pool; @Data public static class PoolConfig { private int min; private int max; private int timeout; } } @Data public static class FeaturesConfig { private boolean caching; private boolean metrics; private boolean tracing; } @Data public static class LoggingConfig { private String level; private String format; private FileConfig file; @Data public static class FileConfig { private String path; private String maxSize; private int maxBackups; } } } @Data public class UserConfig { private String username; private String email; private ProfileConfig profile; private PreferencesConfig preferences; @Data public static class ProfileConfig { private String firstName; private String lastName; private int age; } @Data public static class PreferencesConfig { private String theme; private String language; private NotificationsConfig notifications; @Data public static class NotificationsConfig { private boolean email; private boolean push; } } } 3. Spring Boot Integration
@Configuration @Slf4j public class CueConfiguration { @Value("${app.config.cue-file:config/app.cue}") private String cueConfigFile; @Value("${app.config.environment:development}") private String environment; @Bean public CueConfigurationLoader cueConfigurationLoader() { return new CueConfigurationLoader(); } @Bean @Primary public AppConfig appConfig(CueConfigurationLoader loader) { log.info("Loading CUE configuration for environment: {}", environment); // Validate configuration first loader.validateConfiguration(cueConfigFile, getEnvironmentExpression()); // Load configuration AppConfig config = loader.loadConfiguration( cueConfigFile, getEnvironmentExpression(), AppConfig.class ); log.info("Successfully loaded CUE configuration: {}", config); return config; } @Bean public UserConfig userConfigSchema(CueConfigurationLoader loader) { return loader.loadConfiguration( "schemas/validation.cue", "validation.#UserConfig", UserConfig.class ); } private String getEnvironmentExpression() { return switch (environment.toLowerCase()) { case "production" -> "app.#Production"; case "staging" -> "app.#AppConfig"; // Use base schema default -> "app.#Development"; }; } } // Property source for CUE configuration @Component @Slf4j public class CuePropertySource extends PropertySource<AppConfig> { private final AppConfig appConfig; public CuePropertySource(AppConfig appConfig) { super("cueConfiguration"); this.appConfig = appConfig; } @Override public Object getProperty(String name) { // Map configuration properties to Spring environment return switch (name) { case "server.port" -> appConfig.getServer().getPort(); case "server.host" -> appConfig.getServer().getHost(); case "database.url" -> appConfig.getDatabase().getUrl(); case "database.username" -> appConfig.getDatabase().getUsername(); case "database.password" -> appConfig.getDatabase().getPassword(); case "logging.level" -> appConfig.getLogging().getLevel(); default -> null; }; } } 4. Advanced CUE Service
@Service @Slf4j public class CueConfigurationService { private final CueConfigurationLoader loader; private final ObjectMapper objectMapper; public CueConfigurationService(CueConfigurationLoader loader) { this.loader = loader; this.objectMapper = new ObjectMapper() .configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); } public <T> T generateConfiguration(String templateFile, Map<String, Object> parameters, Class<T> configType) { try { // Create temporary CUE file with parameters Path tempFile = createParameterizedCueFile(templateFile, parameters); // Generate configuration T config = loader.loadConfiguration( tempFile.toString(), "config", configType ); // Cleanup Files.delete(tempFile); return config; } catch (IOException e) { throw new RuntimeException("Failed to generate configuration", e); } } public void validateUserConfiguration(UserConfig userConfig) { try { // Convert to JSON for CUE validation String userJson = objectMapper.writeValueAsString(userConfig); // Create temporary file for validation Path tempFile = Files.createTempFile("user-config", ".json"); Files.write(tempFile, userJson.getBytes()); // Validate against schema loader.validateConfiguration("schemas/validation.cue", "validation.#UserConfig & \(tempFile)"); // Cleanup Files.delete(tempFile); } catch (IOException e) { throw new RuntimeException("User configuration validation failed", e); } } public Map<String, Object> mergeConfigurations(String baseConfig, String overrideConfig) { try { // Create merged CUE configuration String mergedCue = String.format(""" import "list" base: %s override: %s config: base & override """, baseConfig, overrideConfig); Path tempFile = Files.createTempFile("merged-config", ".cue"); Files.write(tempFile, mergedCue.getBytes()); @SuppressWarnings("unchecked") Map<String, Object> result = loader.loadConfiguration( tempFile.toString(), "config", Map.class ); Files.delete(tempFile); return result; } catch (IOException e) { throw new RuntimeException("Configuration merge failed", e); } } private Path createParameterizedCueFile(String templateFile, Map<String, Object> parameters) throws IOException { String templateContent = Files.readString(Path.of(templateFile)); // Replace placeholders with actual values for (Map.Entry<String, Object> entry : parameters.entrySet()) { String placeholder = "\\$\\{" + entry.getKey() + "\\}"; templateContent = templateContent.replaceAll(placeholder, objectMapper.writeValueAsString(entry.getValue())); } Path tempFile = Files.createTempFile("parameterized", ".cue"); Files.write(tempFile, templateContent.getBytes()); return tempFile; } } 5. Template-Based Configuration Generation
Create templates/k8s-deployment.cue:
package k8s import "list" #DeploymentTemplate: { apiVersion: "apps/v1" kind: "Deployment" metadata: { name: string namespace: string | *"default" labels: [string]: string } spec: { replicas: int & >=1 | *3 selector: matchLabels: { "app": metadata.name } template: { metadata: labels: { "app": metadata.name "version": string } spec: { containers: [{ name: metadata.name image: string ports: [{ containerPort: int protocol: "TCP" | *"TCP" }] env: [...{ name: string value: string }] resources: { requests: { cpu: string memory: string } limits: { cpu: string memory: string } } livenessProbe: { httpGet: { path: string port: int } initialDelaySeconds: int | *30 periodSeconds: int | *10 } }] } } } } // Generate deployment for a service deployment: #DeploymentTemplate & { metadata: { name: "user-service" namespace: "production" labels: { "app": "user-service" "environment": "production" } } spec: { replicas: 3 template: { metadata: labels: { "app": "user-service" "version": "1.2.3" } spec: containers: [{ name: "user-service" image: "myregistry/user-service:1.2.3" ports: [{ containerPort: 8080 }] env: [ { name: "DATABASE_URL" value: "postgresql://localhost:5432/users" }, { name: "LOG_LEVEL" value: "INFO" } ] resources: { requests: { cpu: "100m" memory: "256Mi" } limits: { cpu: "500m" memory: "512Mi" } } livenessProbe: { httpGet: { path: "/actuator/health" port: 8080 } initialDelaySeconds: 45 } }] } } } 6. Kubernetes Configuration Generator
@Service @Slf4j public class KubernetesConfigGenerator { private final CueConfigurationService cueService; private final ObjectMapper yamlMapper; public KubernetesConfigGenerator(CueConfigurationService cueService) { this.cueService = cueService; this.yamlMapper = new ObjectMapper(new YAMLFactory()); } public String generateDeployment(String serviceName, String version, String environment, int replicas) { Map<String, Object> parameters = Map.of( "serviceName", serviceName, "version", version, "environment", environment, "replicas", replicas, "image", String.format("myregistry/%s:%s", serviceName, version) ); @SuppressWarnings("unchecked") Map<String, Object> deployment = cueService.generateConfiguration( "templates/k8s-deployment.cue", parameters, Map.class ); try { return yamlMapper.writeValueAsString(deployment); } catch (IOException e) { throw new RuntimeException("Failed to generate YAML", e); } } public void generateAllEnvironments(String serviceName, String version) { List<String> environments = List.of("development", "staging", "production"); for (String env : environments) { String deploymentYaml = generateDeployment( serviceName, version, env, getReplicaCount(env)); String filename = String.format("k8s/%s-%s-deployment.yaml", serviceName, env); try { Files.writeString(Path.of(filename), deploymentYaml); log.info("Generated deployment manifest: {}", filename); } catch (IOException e) { log.error("Failed to write deployment file: {}", filename, e); } } } private int getReplicaCount(String environment) { return switch (environment) { case "production" -> 5; case "staging" -> 2; default -> 1; }; } } Build and Usage
1. Maven Build Integration
Create src/main/cue/schemas/app.cue and add to pom.xml:
<build> <plugins> <plugin> <groupId>org.codehaus.mojo</groupId> <artifactId>exec-maven-plugin</artifactId> <version>3.1.0</version> <executions> <execution> <id>validate-cue-config</id> <phase>validate</phase> <goals> <goal>exec</goal> </goals> <configuration> <executable>cue</executable> <arguments> <argument>vet</argument> <argument>src/main/cue/schemas/app.cue</argument> <argument>app.#AppConfig</argument> </arguments> </configuration> </execution> </executions> </plugin> </plugins> </build>
2. Application Configuration
application.yml:
app: config: cue-file: "src/main/cue/schemas/app.cue" environment: ${APP_ENVIRONMENT:development} spring: profiles: active: ${APP_ENVIRONMENT:development} Benefits and Use Cases
Benefits:
- Type Safety: Catch configuration errors before runtime
- Validation: Complex business rules enforced in configuration
- Templating: Generate multiple environment configurations
- Reuse: Share configuration schemas across projects
- IDE Support: Autocomplete and validation in CUE-aware editors
Use Cases:
- Multi-environment configuration management
- Kubernetes manifest generation
- API specification validation
- Database schema management
- Feature flag configuration
- Microservices configuration sharing
Conclusion
Integrating CUE with Java applications provides a powerful approach to configuration management that combines type safety, validation, and templating capabilities. By leveraging CUE's strong typing and validation features, Java applications can:
- Eliminate configuration errors through compile-time validation
- Manage complex multi-environment setups with reusable templates
- Generate deployment artifacts like Kubernetes manifests
- Ensure consistency across microservices configurations
- Provide better developer experience with schema-aware configuration editing
The combination of CUE's powerful configuration language with Java's robust application runtime creates a foundation for reliable, maintainable, and scalable application configuration management.