Skip to main content

1. Your First Config

This tutorial walks through MittenLib's Configuration Annotation Processor System.

This feature allows you to define configuration structures using simple, clean interfaces, letting the compiler generate all the heavy lifting for parsing, validation, and serialization.

Let's start by defining a basic configuration for a server connection.

The Configuration

Suppose our plugin connects to some server. We want to let admins configure the host and port of the server. Our config.yml might look like this:

host: localhost
port: 25565

MittenLib makes parsing and validating this config easy and safe!

Defining the Config Structure

We first have to define a type which describes the shape of our configuration. This is often called a DTO (Data Transfer Object) or Config Type.

You can use either an interface or a class to define your configuration structure. Interfaces are the recommended option in most situations.

package me.bristermitten.mittenlib.docs.tutorial;

import me.bristermitten.mittenlib.config.Config;

@Config
public interface SimpleInterfaceConfig {
String host();
int port();
}

What happens here?

  1. The @Config annotation tells the MittenLib annotation processor to inspect this type during compilation.
  2. The processor inspects the host and port methods.
  3. It generates a full implementation class (SimpleInterfaceConfigImpl), along with deserializers and serializers for loading/saving data.

The Generated Implementation

note

All properties are non-nullable by default, and always immutable.

Data Class

The generated class provides a type-safe, immutable representation of your configuration.

// Simplified view of the generated implementation
public class SimpleInterfaceConfigImpl implements SimpleInterfaceConfig {
private final String host;
private final int port;

public SimpleInterfaceConfigImpl(String host, int port) {
this.host = host;
this.port = port;
}

@Override
public String host() { return host; }

@Override
public int port() { return port; }

// Also generated: withHost(), withPort(), equals(), hashCode(), toString()
}

Deserializer and Serializer

MittenLib also generates separate deserializer and serializer classes for your config:

public class SimpleInterfaceConfigDeserializer implements DeserializationFunction<SimpleInterfaceConfig> {

private Result<String> deserializeHost(DeserializationContext context) {
// ...
}

private Result<Integer> deserializePort(DeserializationContext context) {
// ...
}

// the function to actually deserialize the config!
public Result<SimpleInterfaceConfig> apply(final DeserializationContext context) {
// ...
}
}

public class SimpleInterfaceConfigSerializer implements SerializationFunction<SimpleInterfaceConfig> {

// This function serializes the config to a DataTree
public DataTree apply(SimpleInterfaceConfig config, SerializationContext context) {
// ...
}

private DataTree serializeHost(String value, SerializationContext context) {
// ...
}

private DataTree serializePort(int value, SerializationContext context) {
// ...
}
}

Now that we have our basic structure, let's look at how we can add defaults, optional fields, and validation in the next step.