Skip to content
powercas_gamer edited this page Jan 9, 2024 · 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. The full source code for this example is included in the examples repository

Add as a dependency:

In Gradle:

repositories {
    mavenCentral()
}

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

In Maven:

<dependencies>
    <dependency>
        <groupId>org.spongepowered</groupId>
        <artifactId>configurate-hocon</artifactId>
        <version>4.1.2</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:

final HoconConfigurationLoader loader = HoconConfigurationLoader.builder()
        .path(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;
    
}

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

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

final @Nullable String name = root.node("name").getString();
final int count = countNode.getInt(Integer.MIN_VALUE);
final @Nullable Mood mood = moodNode.get(Mood.class);

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.raw(0); // native type
moodNode.set(Mood.class, Mood.NEUTRAL); // serialized type

root.node("accesses").act(n -> {
    n.commentIfAbsent("The times messages have been accessed, in milliseconds since the epoch");
    n.appendListNode().set(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 node(). 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. These are the values taken from and accepted by the raw() methods. Serialized types are any supported by the object mapper and are more flexible in how they are specified. All values from the get and set methods are serialized.

Save any changes

// And save the node back to the file
try {
    loader.save(root);
} catch (final ConfigurateException 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, 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.