YAML Configuration with SnakeYAML in Java

Overview

SnakeYAML is a YAML parser and emitter for Java that provides complete YAML 1.1 support. It's widely used for configuration files, data serialization, and configuration management in Java applications.

Dependencies

Maven

<dependency>
<groupId>org.yaml</groupId>
<artifactId>snakeyaml</artifactId>
<version>2.2</version>
</dependency>

Gradle

implementation 'org.yaml:snakeyaml:2.2'

Basic Usage

1. Simple YAML Parsing

import org.yaml.snakeyaml.Yaml;
import java.io.InputStream;
import java.util.Map;
public class BasicYamlExample {
public static void main(String[] args) {
Yaml yaml = new Yaml();
// Parse YAML from string
String yamlString = """
name: John Doe
age: 30
active: true
""";
Map<String, Object> data = yaml.load(yamlString);
System.out.println("Name: " + data.get("name"));
System.out.println("Age: " + data.get("age"));
System.out.println("Active: " + data.get("active"));
// Parse YAML from file
try (InputStream inputStream = BasicYamlExample.class
.getClassLoader()
.getResourceAsStream("config.yml")) {
Map<String, Object> config = yaml.load(inputStream);
System.out.println("Config: " + config);
} catch (Exception e) {
e.printStackTrace();
}
// Generate YAML from Java objects
Map<String, Object> outputData = Map.of(
"username", "johndoe",
"email", "[email protected]",
"roles", new String[]{"USER", "ADMIN"}
);
String outputYaml = yaml.dump(outputData);
System.out.println("Generated YAML:\n" + outputYaml);
}
}

2. Custom Configuration Classes

import org.yaml.snakeyaml.Yaml;
import org.yaml.snakeyaml.constructor.Constructor;
import java.util.List;
import java.util.Map;
// Configuration classes
public class AppConfig {
private String appName;
private String version;
private DatabaseConfig database;
private ServerConfig server;
private List<String> features;
private Map<String, String> properties;
private LoggingConfig logging;
// Constructors, getters, and setters
public AppConfig() {}
public String getAppName() { return appName; }
public void setAppName(String appName) { this.appName = appName; }
public String getVersion() { return version; }
public void setVersion(String version) { this.version = version; }
public DatabaseConfig getDatabase() { return database; }
public void setDatabase(DatabaseConfig database) { this.database = database; }
public ServerConfig getServer() { return server; }
public void setServer(ServerConfig server) { this.server = server; }
public List<String> getFeatures() { return features; }
public void setFeatures(List<String> features) { this.features = features; }
public Map<String, String> getProperties() { return properties; }
public void setProperties(Map<String, String> properties) { this.properties = properties; }
public LoggingConfig getLogging() { return logging; }
public void setLogging(LoggingConfig logging) { this.logging = logging; }
}
class DatabaseConfig {
private String url;
private String username;
private String password;
private int maxConnections;
private boolean enableCache;
// Constructors, getters, and setters
public DatabaseConfig() {}
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 int getMaxConnections() { return maxConnections; }
public void setMaxConnections(int maxConnections) { this.maxConnections = maxConnections; }
public boolean isEnableCache() { return enableCache; }
public void setEnableCache(boolean enableCache) { this.enableCache = enableCache; }
}
class ServerConfig {
private int port;
private String host;
private String contextPath;
private SslConfig ssl;
private List<Endpoint> endpoints;
// Constructors, getters, and setters
public ServerConfig() {}
public int getPort() { return port; }
public void setPort(int port) { this.port = port; }
public String getHost() { return host; }
public void setHost(String host) { this.host = host; }
public String getContextPath() { return contextPath; }
public void setContextPath(String contextPath) { this.contextPath = contextPath; }
public SslConfig getSsl() { return ssl; }
public void setSsl(SslConfig ssl) { this.ssl = ssl; }
public List<Endpoint> getEndpoints() { return endpoints; }
public void setEndpoints(List<Endpoint> endpoints) { this.endpoints = endpoints; }
}
class SslConfig {
private boolean enabled;
private String keyStore;
private String keyStorePassword;
private String keyAlias;
// Constructors, getters, and setters
public SslConfig() {}
public boolean isEnabled() { return enabled; }
public void setEnabled(boolean enabled) { this.enabled = enabled; }
public String getKeyStore() { return keyStore; }
public void setKeyStore(String keyStore) { this.keyStore = keyStore; }
public String getKeyStorePassword() { return keyStorePassword; }
public void setKeyStorePassword(String keyStorePassword) { this.keyStorePassword = keyStorePassword; }
public String getKeyAlias() { return keyAlias; }
public void setKeyAlias(String keyAlias) { this.keyAlias = keyAlias; }
}
class Endpoint {
private String path;
private String method;
private boolean secured;
private List<String> roles;
// Constructors, getters, and setters
public Endpoint() {}
public String getPath() { return path; }
public void setPath(String path) { this.path = path; }
public String getMethod() { return method; }
public void setMethod(String method) { this.method = method; }
public boolean isSecured() { return secured; }
public void setSecured(boolean secured) { this.secured = secured; }
public List<String> getRoles() { return roles; }
public void setRoles(List<String> roles) { this.roles = roles; }
}
class LoggingConfig {
private String level;
private String filePath;
private int maxFileSize;
private int maxBackups;
private String pattern;
private Map<String, String> loggers;
// Constructors, getters, and setters
public LoggingConfig() {}
public String getLevel() { return level; }
public void setLevel(String level) { this.level = level; }
public String getFilePath() { return filePath; }
public void setFilePath(String filePath) { this.filePath = filePath; }
public int getMaxFileSize() { return maxFileSize; }
public void setMaxFileSize(int maxFileSize) { this.maxFileSize = maxFileSize; }
public int getMaxBackups() { return maxBackups; }
public void setMaxBackups(int maxBackups) { this.maxBackups = maxBackups; }
public String getPattern() { return pattern; }
public void setPattern(String pattern) { this.pattern = pattern; }
public Map<String, String> getLoggers() { return loggers; }
public void setLoggers(Map<String, String> loggers) { this.loggers = loggers; }
}
// YAML Configuration Loader
public class ConfigLoader {
public static AppConfig loadConfig(String configPath) {
Yaml yaml = new Yaml(new Constructor(AppConfig.class));
try (InputStream inputStream = ConfigLoader.class
.getClassLoader()
.getResourceAsStream(configPath)) {
if (inputStream == null) {
throw new RuntimeException("Config file not found: " + configPath);
}
return yaml.load(inputStream);
} catch (Exception e) {
throw new RuntimeException("Failed to load configuration", e);
}
}
public static void saveConfig(AppConfig config, String filePath) {
Yaml yaml = new Yaml();
try (FileWriter writer = new FileWriter(filePath)) {
yaml.dump(config, writer);
} catch (Exception e) {
throw new RuntimeException("Failed to save configuration", e);
}
}
}

Advanced YAML Features

1. Custom Constructors and Representers

import org.yaml.snakeyaml.Yaml;
import org.yaml.snakeyaml.constructor.Constructor;
import org.yaml.snakeyaml.representer.Representer;
import org.yaml.snakeyaml.nodes.Tag;
import org.yaml.snakeyaml.DumperOptions;
import java.util.Date;
public class AdvancedYamlFeatures {
// Custom type with special serialization needs
public static class CustomObject {
private String name;
private Date created;
private transient String transientField; // Won't be serialized
public CustomObject() {}
public CustomObject(String name, Date created) {
this.name = name;
this.created = created;
}
// Getters and setters
public String getName() { return name; }
public void setName(String name) { this.name = name; }
public Date getCreated() { return created; }
public void setCreated(Date created) { this.created = created; }
public String getTransientField() { return transientField; }
public void setTransientField(String transientField) { this.transientField = transientField; }
}
public static void demonstrateAdvancedFeatures() {
// Custom DumperOptions for better output
DumperOptions options = new DumperOptions();
options.setDefaultFlowStyle(DumperOptions.FlowStyle.BLOCK);
options.setPrettyFlow(true);
options.setIndent(2);
options.setDefaultScalarStyle(DumperOptions.ScalarStyle.PLAIN);
// Custom Representer to control serialization
Representer representer = new Representer(options);
representer.getPropertyUtils().setSkipMissingProperties(true);
Yaml yaml = new Yaml(representer, options);
// Serialize custom object
CustomObject obj = new CustomObject("Test Object", new Date());
obj.setTransientField("This won't appear in YAML");
String yamlOutput = yaml.dump(obj);
System.out.println("Serialized CustomObject:\n" + yamlOutput);
// Parse with custom constructor
Yaml parser = new Yaml(new Constructor(CustomObject.class));
CustomObject parsedObj = parser.load(yamlOutput);
System.out.println("Parsed name: " + parsedObj.getName());
}
}

2. Handling Multiple Documents

import org.yaml.snakeyaml.Yaml;
import java.util.ArrayList;
import java.util.List;
public class MultiDocumentYaml {
public static void handleMultipleDocuments() {
String multiDocYaml = """
---
name: Document 1
type: config
values:
- item1
- item2
---
name: Document 2
type: data
records:
- id: 1
value: test1
- id: 2
value: test2
---
name: Document 3
type: metadata
timestamp: 2024-01-01
""";
Yaml yaml = new Yaml();
List<Object> documents = new ArrayList<>();
// Load all documents
for (Object document : yaml.loadAll(multiDocYaml)) {
documents.add(document);
System.out.println("Loaded document: " + document);
}
System.out.println("Total documents: " + documents.size());
// Generate multiple documents
List<Object> outputDocs = List.of(
Map.of("doc", "first", "data", "value1"),
Map.of("doc", "second", "data", "value2"),
Map.of("doc", "third", "data", "value3")
);
StringBuilder multiDocOutput = new StringBuilder();
for (Object doc : outputDocs) {
multiDocOutput.append("---\n");
multiDocOutput.append(yaml.dump(doc));
}
System.out.println("Generated multi-document YAML:\n" + multiDocOutput);
}
}

Practical Configuration Examples

Example 1: Application Configuration Manager

import org.yaml.snakeyaml.Yaml;
import org.yaml.snakeyaml.constructor.Constructor;
import java.io.*;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.*;
public class ApplicationConfigManager {
private final String configPath;
private AppConfig config;
private final Yaml yaml;
public ApplicationConfigManager(String configPath) {
this.configPath = configPath;
DumperOptions options = new DumperOptions();
options.setIndent(2);
options.setPrettyFlow(true);
options.setDefaultFlowStyle(DumperOptions.FlowStyle.BLOCK);
this.yaml = new Yaml(new Constructor(AppConfig.class), options);
loadConfig();
}
public void loadConfig() {
try {
Path path = Paths.get(configPath);
if (Files.exists(path)) {
try (InputStream inputStream = Files.newInputStream(path)) {
this.config = yaml.load(inputStream);
}
} else {
// Create default config
this.config = createDefaultConfig();
saveConfig();
}
} catch (Exception e) {
throw new RuntimeException("Failed to load configuration from: " + configPath, e);
}
}
public void saveConfig() {
try {
Path path = Paths.get(configPath);
Files.createDirectories(path.getParent());
try (Writer writer = Files.newBufferedWriter(path)) {
yaml.dump(config, writer);
}
} catch (Exception e) {
throw new RuntimeException("Failed to save configuration to: " + configPath, e);
}
}
public void updateConfig(Map<String, Object> updates) {
// Implement deep merge logic for configuration updates
mergeConfig(config, updates);
saveConfig();
}
private void mergeConfig(Object target, Map<String, Object> updates) {
for (Map.Entry<String, Object> entry : updates.entrySet()) {
String key = entry.getKey();
Object value = entry.getValue();
if (target instanceof Map) {
@SuppressWarnings("unchecked")
Map<String, Object> targetMap = (Map<String, Object>) target;
if (value instanceof Map && targetMap.containsKey(key) && targetMap.get(key) instanceof Map) {
// Recursive merge for nested maps
@SuppressWarnings("unchecked")
Map<String, Object> nestedUpdates = (Map<String, Object>) value;
mergeConfig(targetMap.get(key), nestedUpdates);
} else {
targetMap.put(key, value);
}
}
}
}
private AppConfig createDefaultConfig() {
AppConfig defaultConfig = new AppConfig();
defaultConfig.setAppName("MyApplication");
defaultConfig.setVersion("1.0.0");
DatabaseConfig dbConfig = new DatabaseConfig();
dbConfig.setUrl("jdbc:mysql://localhost:3306/mydb");
dbConfig.setUsername("admin");
dbConfig.setPassword("password");
dbConfig.setMaxConnections(10);
dbConfig.setEnableCache(true);
defaultConfig.setDatabase(dbConfig);
ServerConfig serverConfig = new ServerConfig();
serverConfig.setPort(8080);
serverConfig.setHost("localhost");
serverConfig.setContextPath("/api");
SslConfig sslConfig = new SslConfig();
sslConfig.setEnabled(false);
serverConfig.setSsl(sslConfig);
List<Endpoint> endpoints = Arrays.asList(
createEndpoint("/health", "GET", false, null),
createEndpoint("/api/users", "GET", true, Arrays.asList("ADMIN")),
createEndpoint("/api/users", "POST", true, Arrays.asList("ADMIN"))
);
serverConfig.setEndpoints(endpoints);
defaultConfig.setServer(serverConfig);
defaultConfig.setFeatures(Arrays.asList("feature1", "feature2", "feature3"));
Map<String, String> properties = new HashMap<>();
properties.put("cache.timeout", "300");
properties.put("api.timeout", "30");
defaultConfig.setProperties(properties);
LoggingConfig loggingConfig = new LoggingConfig();
loggingConfig.setLevel("INFO");
loggingConfig.setFilePath("logs/app.log");
loggingConfig.setMaxFileSize(10);
loggingConfig.setMaxBackups(5);
loggingConfig.setPattern("%d{yyyy-MM-dd HH:mm:ss} - %msg%n");
Map<String, String> loggers = new HashMap<>();
loggers.put("com.myapp", "DEBUG");
loggers.put("org.springframework", "WARN");
loggingConfig.setLoggers(loggers);
defaultConfig.setLogging(loggingConfig);
return defaultConfig;
}
private Endpoint createEndpoint(String path, String method, boolean secured, List<String> roles) {
Endpoint endpoint = new Endpoint();
endpoint.setPath(path);
endpoint.setMethod(method);
endpoint.setSecured(secured);
endpoint.setRoles(roles);
return endpoint;
}
// Getters for configuration sections
public DatabaseConfig getDatabaseConfig() {
return config.getDatabase();
}
public ServerConfig getServerConfig() {
return config.getServer();
}
public LoggingConfig getLoggingConfig() {
return config.getLogging();
}
public String getProperty(String key) {
return config.getProperties().get(key);
}
public void setProperty(String key, String value) {
config.getProperties().put(key, value);
saveConfig();
}
// Utility method to get config as map
public Map<String, Object> getConfigAsMap() {
Yaml tempYaml = new Yaml();
String yamlString = yaml.dump(config);
return tempYaml.load(yamlString);
}
}

Example 2: Environment-Specific Configuration

public class EnvironmentAwareConfigManager {
private final String environment;
private final ApplicationConfigManager configManager;
private final Map<String, Object> environmentOverrides;
public EnvironmentAwareConfigManager(String environment, String baseConfigPath) {
this.environment = environment;
this.configManager = new ApplicationConfigManager(baseConfigPath);
this.environmentOverrides = loadEnvironmentOverrides();
applyEnvironmentOverrides();
}
private Map<String, Object> loadEnvironmentOverrides() {
String envConfigFile = "config-" + environment + ".yml";
try (InputStream inputStream = getClass().getClassLoader()
.getResourceAsStream(envConfigFile)) {
if (inputStream != null) {
Yaml yaml = new Yaml();
return yaml.load(inputStream);
}
} catch (Exception e) {
System.err.println("No environment-specific config found for: " + environment);
}
return Collections.emptyMap();
}
private void applyEnvironmentOverrides() {
if (!environmentOverrides.isEmpty()) {
configManager.updateConfig(environmentOverrides);
}
}
public DatabaseConfig getDatabaseConfig() {
return configManager.getDatabaseConfig();
}
public ServerConfig getServerConfig() {
return configManager.getServerConfig();
}
public String getEnvironment() {
return environment;
}
}
// Usage example
class ConfigExample {
public static void main(String[] args) {
// Load development configuration
EnvironmentAwareConfigManager devConfig = 
new EnvironmentAwareConfigManager("dev", "config.yml");
// Load production configuration  
EnvironmentAwareConfigManager prodConfig =
new EnvironmentAwareConfigManager("prod", "config.yml");
System.out.println("Dev DB URL: " + devConfig.getDatabaseConfig().getUrl());
System.out.println("Prod DB URL: " + prodConfig.getDatabaseConfig().getUrl());
}
}

Sample YAML Configuration Files

1. Main Configuration File (config.yml)

appName: MySpringApplication
version: 2.1.0
database:
url: jdbc:mysql://localhost:3306/myapp
username: ${DB_USERNAME:defaultuser}
password: ${DB_PASSWORD:defaultpass}
maxConnections: 20
enableCache: true
server:
port: 8080
host: 0.0.0.0
contextPath: /api
ssl:
enabled: false
keyStore: keystore.p12
keyStorePassword: changeit
keyAlias: myapp
endpoints:
- path: /health
method: GET
secured: false
roles: []
- path: /api/users
method: GET
secured: true
roles: [USER, ADMIN]
- path: /api/admin
method: POST
secured: true
roles: [ADMIN]
features:
- authentication
- authorization
- caching
- monitoring
properties:
cache.timeout: "300"
api.timeout: "30"
page.size: "20"
logging:
level: INFO
filePath: /var/log/myapp/application.log
maxFileSize: 50
maxBackups: 10
pattern: "%d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{36} - %msg%n"
loggers:
com.myapp: DEBUG
org.springframework.security: WARN
org.hibernate: ERROR

2. Development Configuration (config-dev.yml)

database:
url: jdbc:mysql://localhost:3306/myapp_dev
username: devuser
password: devpass
maxConnections: 5
server:
port: 9090
host: localhost
logging:
level: DEBUG
filePath: ./logs/application.log
properties:
cache.timeout: "60"
api.timeout: "60"

3. Production Configuration (config-prod.yml)

database:
url: jdbc:mysql://prod-db.cluster.amazonaws.com:3306/myapp_prod
username: ${PROD_DB_USERNAME}
password: ${PROD_DB_PASSWORD}
maxConnections: 50
server:
port: 443
host: api.myapp.com
ssl:
enabled: true
keyStore: /etc/ssl/prod-keystore.p12
keyStorePassword: ${SSL_KEYSTORE_PASSWORD}
logging:
level: WARN
filePath: /var/log/myapp/prod-application.log
maxFileSize: 100
maxBackups: 30
properties:
cache.timeout: "1800"
api.timeout: "10"

Advanced Features

1. Custom Type Conversion

import org.yaml.snakeyaml.constructor.Construct;
import org.yaml.snakeyaml.nodes.Node;
import org.yaml.snakeyaml.nodes.ScalarNode;
import org.yaml.snakeyaml.nodes.Tag;
import org.yaml.snakeyaml.constructor.Constructor;
public class CustomTypeConstructor extends Constructor {
public CustomTypeConstructor() {
this.yamlConstructors.put(new Tag("!duration"), new ConstructDuration());
this.yamlConstructors.put(new Tag("!size"), new ConstructSize());
}
private class ConstructDuration implements Construct {
@Override
public Object construct(Node node) {
String value = (String) constructScalar((ScalarNode) node);
return java.time.Duration.parse(value);
}
}
private class ConstructSize implements Construct {
@Override
public Object construct(Node node) {
String value = (String) constructScalar((ScalarNode) node);
return parseSize(value);
}
private long parseSize(String size) {
size = size.toUpperCase();
if (size.endsWith("KB")) {
return Long.parseLong(size.substring(0, size.length() - 2)) * 1024;
} else if (size.endsWith("MB")) {
return Long.parseLong(size.substring(0, size.length() - 2)) * 1024 * 1024;
} else if (size.endsWith("GB")) {
return Long.parseLong(size.substring(0, size.length() - 2)) * 1024 * 1024 * 1024;
} else {
return Long.parseLong(size);
}
}
}
}
// Usage with custom types
public class CustomConfig {
private java.time.Duration timeout;
private long maxFileSize;
// Getters and setters
public java.time.Duration getTimeout() { return timeout; }
public void setTimeout(java.time.Duration timeout) { this.timeout = timeout; }
public long getMaxFileSize() { return maxFileSize; }
public void setMaxFileSize(long maxFileSize) { this.maxFileSize = maxFileSize; }
}
// YAML with custom types
String customYaml = """
timeout: !duration PT30S
maxFileSize: !size 10MB
""";
Yaml yaml = new Yaml(new CustomTypeConstructor());
CustomConfig config = yaml.load(customYaml);

2. Validation and Schema

import org.yaml.snakeyaml.validator.Validator;
import org.yaml.snakeyaml.error.YAMLException;
import java.util.Set;
import java.util.HashSet;
public class ConfigValidator {
private final Set<String> allowedProperties;
public ConfigValidator() {
this.allowedProperties = Set.of(
"appName", "version", "database", "server", "features", 
"properties", "logging", "url", "username", "password",
"port", "host", "ssl", "level", "filePath"
);
}
public void validateConfig(Map<String, Object> config) {
validateProperties(config, "");
}
private void validateProperties(Map<String, Object> config, String path) {
for (String key : config.keySet()) {
String fullPath = path.isEmpty() ? key : path + "." + key;
if (!allowedProperties.contains(key)) {
throw new YAMLException("Unknown configuration property: " + fullPath);
}
Object value = config.get(key);
if (value instanceof Map) {
@SuppressWarnings("unchecked")
Map<String, Object> nestedMap = (Map<String, Object>) value;
validateProperties(nestedMap, fullPath);
}
}
}
}

Best Practices

  1. Use meaningful property names with consistent naming conventions
  2. Group related properties in nested structures
  3. Provide default values for optional properties
  4. Use environment variables for sensitive data
  5. Validate configuration on application startup
  6. Use comments to document configuration options
  7. Keep configuration files in version control (excluding secrets)
  8. Use environment-specific configuration files

This comprehensive YAML configuration system provides a robust foundation for managing application settings with SnakeYAML, supporting complex nested structures, environment-specific configurations, and custom type handling.

Leave a Reply

Your email address will not be published. Required fields are marked *


Macro Nepal Helper