Skip to content
zml edited this page Apr 24, 2020 · 11 revisions

Getting Started

Configurate is designed to be easy to get started with. For our examples here we're going to be using the HOCON loader, but any of the other loaders will work similarly.

Add as a dependency:

In Gradle:

repositories {
    maven {
        url = "https://repo.spongepowered.org/maven"
        name = "sponge"
    }
}

dependencies {
    implementation("org.spongepowered:configurate-hocon:3.7")
}

In Maven:

<repositories>
    <repository>
        <id>sponge</id>
        <url>https://repo.spongepowered.org/maven</url>
    </repository>
</repositories>

<dependencies>
    <dependency>
        <groupId>org.spongepowered</groupId>
        <artifactId>configurate-hocon</artifactId>
        <version>3.7</version> <!-- this may not be the latest available version -->
    </dependency>
</dependencies>

Make sure to check the releases page for the latest release.

Load a configuration

We'll assume we're going to load a configuration from the working directory named myproject.conf, with the contents

    name = "Alice"
    messages {
        # Alice hasn't checked their messages in a while
        count = 20
        mood = CONFUSED
    }

Then, we'll load myproject.conf:

        HoconConfigurationLoader loader = HoconConfigurationLoader.builder()
                .setPath(Path.of("myproject.conf")) // Set where we will load and save to
                .build();

        CommentedConfigurationNode root;
        try {
            root = loader.load();
        } catch (IOException e) {
            System.err.println("An error occurred while loading this configuration: " + e.getMessage());
            if (e.getCause() != null) {
                e.getCause().printStackTrace();
            }
            System.exit(1);
            return;
        }

Configurate's loaders will provide empty nodes if the requested file does not exist. When saving, the loader will create any necessary parent directories.

Work with it!

Let's pretend we are writing information to a messages file. For this example,we'll have an enum class Mood, with the following contents:

    /**
     * A mood that a message may have
     */
    enum Mood {
        HAPPY, SAD, CONFUSED, NEUTRAL;

        public static final TypeToken<Mood> TYPE = TypeToken.of(Mood.class); // Keep track of our generic type, to avoid reinitialization
    }

Continuing from where we left off after loading the configuration, we'll retrieve some values from the configuration and print them to the user.

        ConfigurationNode countNode = root.getNode("messages", "count"),
                moodNode = root.getNode("messages", "mood");

        String name = root.getNode("name").getString();
        int count = countNode.getInt(Integer.MIN_VALUE);
        Mood mood = moodNode.getValue(Mood.TYPE);

        if (name == null || count == Integer.MIN_VALUE || mood == null) {
            System.err.println("Invalid configuration");
            System.exit(2);
            return;
        }

        System.out.println("Hello, " + name + "!");
        System.out.println("You have " + count + " " + mood + " messages!");
        System.out.println("Thanks for viewing your messages");

        // Update values
        countNode.setValue(0);
        moodNode.setValue(Mood.TYPE, Mood.NEUTRAL);

        root.getNode("accesses").act(n -> { // perform actions with the node at key "accesses" available at `n`
            n.setComment("The times messages have been accessed, in milliseconds since the epoch");
            n.appendListNode().setValue(System.currentTimeMillis());
        });

There are a few important things to note here. First off, it is possible to skip through levels of hierarchy within configuration nodes by providing each level as an element to the varargs method getNode(). While any object is a valid key, there may be limitations depending on the loader used to save the node. Additionally, using any key that is not a valid Map key will result in undefined behaviour.

Another important distinction shown in the above example is between native value types and serialized value types. Native value types are those that are directly supported by the loader and appear differently depending on the file format, while serialized types are any supported by the object mapper and can generally be serialized to any output. To take advantage of serialized types, the variant of ConfigurationNode.setValue() that takes a TypeToken must be used.

Save any changes

        // And save the node back to the file
        try {
            loader.save(root);
        } catch (IOException e) {
            System.err.println("Unable to save your messages configuration! Sorry! " + e.getMessage());
                System.exit(1);
        }

This will save the contents of the node root to the location originally specified for the loader we created at the beginning of this example. While the node we are saving (root) was originally created by this loader, any implementation of ConfigurationNode must be accepted by any loader.

Some formats place specific restrictions on the structure of output data (for example, requiring the root node to be in map format), so there can be conversion restrictions, but those schema violations must be documented in each loader.


Hopefully this gives a brief taste of how to use Configurate for basic configuration needs. For more complicated setups, take a look at the Object Mapper which allows representing configuration structures as objects, rather than accessing the node directly. As your project evolves, Configuration Transformations allow making changes to your configuration as a whole, including versioning the configuration structure. For a more in-depth look at the behaviour of any section of Configurate, the [Javadocs] have a thorough description of every class.

For any further questions, feel free to be in touch in the SpongePowered Discord

Clone this wiki locally