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.