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
- Use meaningful property names with consistent naming conventions
- Group related properties in nested structures
- Provide default values for optional properties
- Use environment variables for sensitive data
- Validate configuration on application startup
- Use comments to document configuration options
- Keep configuration files in version control (excluding secrets)
- 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.