DEV Community

Cover image for How to manage your configuration file with YAML in Java programmatically
Kooin-Shin
Kooin-Shin

Posted on

How to manage your configuration file with YAML in Java programmatically

Introduction

YAML is most useful data format to describe structural hierarchy in these days. It's more simple and collision resistant.
It is useful to describe your configuration attributes in your developments.
We are going to learn to manage configuration YAML in Java programmatically.
For our goal, we will use SnakeYAML library.

OK, Let's dive in.

Set up environment

  1. At first We need to choose development tool. Even if any development IDE for Java could be used, but I would use Visual Studio Code that have been hotted on these days.
  2. Setting dependency to build.gradle file. if you do like below, It's would be configured classpath automatically.
dependencies {
    // Use JUnit test framework
    testImplementation 'junit:junit:4.12'

    // https://mvnrepository.com/artifact/org.yaml/snakeyaml
    compile group: 'org.yaml', name: 'snakeyaml', version: '1.27'
}
Enter fullscreen mode Exit fullscreen mode
  1. All of environment setting is over doing this.

Let's go to code

First, we need to define configuration on config.yml file formatted with YAML.

adminPassword: 1234
adminPort: 9292
adminUser: admin
forbiddenRemote: []
host: localhost
sessions:
  Oracle:
    allowedHosts: [127.0.0.1]
    connectionTimeout: 3
    remoteHosts: [192.168.1.152:1521]
    retry: 3
  Kafka:
    allowedHosts: [127.0.0.1]
    connectionTimeout: 3
    remoteHosts: [192.168.1.153:2181]
    retry: 3
Enter fullscreen mode Exit fullscreen mode

Second, we have to write Java Bean code mapping with config.yml fields. Top object of configuration is implemented to Config.java.

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

public class Config {
    private String adminPassword;
    private String adminUser;
    private String host;
    private int adminPort;
    private List<String> forbiddenRemote = new ArrayList<>();
    private Map<String, Sessions> sessions = new HashMap<>();

    Config() {}

    public String getAdminUser() {
        return adminUser;
    }
    public void setAdminUser(String adminUser) {
        this.adminUser = adminUser;
    }
    public String getAdminPassword() {
        return adminPassword;
    }
    public void setAdminPassword(String adminPassword) {
        this.adminPassword = adminPassword;
    }
    public int getAdminPort() {
        return adminPort;
    }
    public void setAdminPort(int adminPort) {
        this.adminPort = adminPort;
    }
    public String getHost() {
        return this.host;
    }
    public void setHost(String host) {
        this.host = host;
    }
    public List<String> getForbiddenRemote() {
        return forbiddenRemote;
    }
    public void setForbiddenRemote(List<String> forbiddenRemote) {
        this.forbiddenRemote = forbiddenRemote;
    }
    public  Map<String, Sessions> getSessions() {
        return sessions;
    }
    public void setSessions(Map<String, Sessions> sessions) {
        this.sessions = sessions;
    }
    public Sessions getSessions(String sessionName) {
        return this.sessions.get(sessionName);
    }

    @Override
    public String toString() {
        return "Config [adminUser=" + adminUser + ", adminPassword=" + adminPassword + ", host=" + host
                + ", adminPort=" + adminPort + ", forbiddenRemote=" + forbiddenRemote + ", sessions="
                + sessions + "]";
    }
}
Enter fullscreen mode Exit fullscreen mode

Sub classification of Config object is Sessions object. It will be placed it's own to Config object's sessions field as Map element.

import java.util.List;

public class Sessions {
    List<String> allowedHosts;
    int connectionTimeout;
    List<String> remoteHosts;
    int retry;

    public Sessions() {
    }
        public List<String> getAllowedHosts() {
        return allowedHosts;
    }

    public void setAllowedHosts(List<String> allowedHosts) {
        this.allowedHosts = allowedHosts;
    }

    public int getConnectionTimeout() {
        return connectionTimeout;
    }

    public void setConnectionTimeout(int connectionTimeout) {
        this.connectionTimeout = connectionTimeout;
    }

    public List<String> getRemoteHosts() {
        return remoteHosts;
    }

    public void setRemoteHosts(List<String> remoteHosts) {
        this.remoteHosts = remoteHosts;
    }

    public int getRetry() {
        return retry;
    }

    public void setRetry(int retry) {
        this.retry = retry;
    }

    @Override
    public String toString() {
        return "Sessions [allowedHosts=" + allowedHosts + ", connectionTimeout=" + connectionTimeout + ", remoteHosts="
                + remoteHosts + ", retry=" + retry + "]";
    }
}
Enter fullscreen mode Exit fullscreen mode

Now, we need to write Handler source file to load config.yml's contents to Java Beans objects we wrote before. To do this, we have to use SnakeYAML library and it's example down below.

Path configPath = Paths.get("./config.yml");
//At first, construct Constructor object using Config.class root object of contents.
Constructor constructor = new Constructor(Config.class);
//Construct Yaml object with constructor object.
Yaml yaml = new Yaml(constructor);
//And then load by given Stream object specified of config.yml file.
yaml.load(new FileInputStream(configPath.toFile()));
Enter fullscreen mode Exit fullscreen mode

Also, If we want to dump contents of Config object in memory to file, we should write dumping code like below.

Path configPath = Paths.get("./config.yml");
DumperOptions options = new DumperOptions();
options.setDefaultFlowStyle(FlowStyle.BLOCK);
options.setPrettyFlow(true);        
Yaml yml = new Yaml(options);
yml.dump(this.config, new FileWriter(configPath.toFile()));
Enter fullscreen mode Exit fullscreen mode

As setting detail options of DumperOptions object, we can handle YAML content for what we want.

We could write Handler to improve for our purpose, then will go to complete Handler code to ConfigHandler.java.
ConfigHandler object will be Singleton style for calling wherever we want in our application. and will have functionality of loading, dumping and referring Config object.
Complete ConfigHandler.java code is down below.

import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileWriter;
import java.io.IOException;
import java.nio.file.Path;
import java.nio.file.Paths;

import org.yaml.snakeyaml.DumperOptions;
import org.yaml.snakeyaml.DumperOptions.FlowStyle;
import org.yaml.snakeyaml.Yaml;
import org.yaml.snakeyaml.constructor.Constructor;

public class ConfigHandler {

    public static final Path configPath = Paths.get("./config.yml");

    private static ConfigHandler configHandler;

    Config config;

    /**
     * Get instance of ConfigHandler
     * @return
     * @throws FileNotFoundException 
     */
    public static ConfigHandler getInstance() throws FileNotFoundException {
        return getInstance(configPath);
    }

    /**
     * Get instance of ConfigHandler
     * @param configPath
     * @return
     * @throws FileNotFoundException
     */
    public static ConfigHandler getInstance(Path configPath) throws FileNotFoundException {
        if(configHandler == null) {
            configHandler = new ConfigHandler(configPath);
        }
        return configHandler;
    }

    /**
     * Constructor
     * @param configPath
     * @throws FileNotFoundException 
     */
    private ConfigHandler(Path configPath) throws FileNotFoundException {
        this.config = loadConfig(configPath);       
    }

    /**
     * Load config.yml
     * @param configPath
     * @throws FileNotFoundException
     */
    public Config loadConfig(Path configPath) throws FileNotFoundException {
        Constructor constructor = new Constructor(Config.class);
        Yaml yaml = new Yaml(constructor);
        return yaml.load(new FileInputStream(configPath.toFile()));
    }

    /**
     * Dump config to config.yml
     * @throws IllegalArgumentException
     * @throws IllegalAccessException
     * @throws IOException
     */
    public void dumpConfig() throws IllegalArgumentException, IllegalAccessException, IOException {
        dumpConfig(this.config, this.configPath);
    }

    /**
     * Dump config to config.yml
     * @param configPath
     * @throws IllegalArgumentException
     * @throws IllegalAccessException
     * @throws IOException
     */
    public void dumpConfig(Config config, Path configPath) throws IllegalArgumentException, IllegalAccessException, IOException {
        DumperOptions options = new DumperOptions();
        options.setDefaultFlowStyle(FlowStyle.BLOCK);
        options.setPrettyFlow(true);        
        Yaml yml = new Yaml(options);
        yml.dump(config, new FileWriter(configPath.toFile()));
    }

    /**
     * Get config object
     * @return
     */
    public Config getConfig() {
        return this.config;
    }   

    /**
     * Get session mapping object by session name
     * @param sessionName
     * @return
     */
    public Sessions getSessions(String sessionName) {
        return this.config.getSessions().get(sessionName);
    }

    public static void main(String[] args) throws IllegalArgumentException, IllegalAccessException, IOException, NoSuchFieldException, SecurityException {
        ConfigHandler handler = ConfigHandler.getInstance();
        Config config = handler.getConfig();
        System.out.println("ADMIN: "+config.getAdminUser());
        System.out.println("PASSWD: "+config.getAdminPassword());
        System.out.println("ORACLE: "+config.getSessions("Oracle").toString());     
        config.setAdminPassword("123456789");
        handler.dumpConfig();
    }
}
Enter fullscreen mode Exit fullscreen mode

Conclusion

YAML is most simple and very fine format to manage contents.
Easy readability and Structural expression is proving why it's famous in these days.
For the more, we could use this format to generate huge data files of Java Bean data for Business or Analysis on Big-Data Store.
All of elastic thinking is yours.

Thanks.

Top comments (5)

Collapse
 
dgolcher profile image
Monet Goldshire

Hey man thanks for the example I've wanted to change the config support I had on an old project from from a properties.config to yaml (no real reason why)
I've been a TPM for the last 6 years so my java dev skills are rusty bear with me.
However I have a couple of questions:
1) Why don't you use Try-with-resources ?
Wouldn't the yaml.load(new FileInputStream(configPath.toFile()));
require an explicit close to prevent the resource to remain open?
2) This example seems to assume an external yml, what if I wanted to have an internal resource with the yml defaults protected from changes on the external file?
getClass().getClassLoader().getResourceAsStream(PATH); ?

Cheers!

Collapse
 
kooin profile image
Kooin-Shin

Thanks for meaningful asking.
I didn't have tried to load or dump on internal resource file yet. so I can't have confidence that able to work on it. In future, I might try it. If anything happens, I will post again. GoodLuck!

Collapse
 
dgolcher profile image
Monet Goldshire

Looking forward to it and cheers from Costa Rica

Thread Thread
 
kooin profile image
Kooin-Shin • Edited

Hi,
A bit while before, I wrote about to be answer of your asking.
It's good to visit below link. GoodLuck!
dev.to/kooin/how-to-handling-inter...

Thread Thread
 
dgolcher profile image
Monet Goldshire

Hey man happy 2021, thanks! I'll compare it to the version I did trying to address the same questions.