@Param

Bind constructor parameters to configuration properties

Purpose: Binds a constructor parameter to a configuration property.

Pixie will automatically inject values from a properties file or the builder API.

Example

public final class User {
    private final String username;
    private final int age;

    public User(@Param("username") final String username,
                @Param("age") final int age) {
        this.username = username;
        this.age = age;
    }
}

Mapping to Properties

The @Param value is the property key suffix. In the properties file, each parameter is set as componentName.paramName = value:

user = new://org.example.User
user.username = alice
user.age = 30

Here user.username maps to @Param("username") and user.age maps to @Param("age").

Case Insensitivity

Property keys and @Param names are matched case insensitively. All of the following are equivalent:

user.username = alice
user.Username = alice
user.USERNAME = alice

This applies to both the component name prefix and the param name suffix — User.UserName, user.username, and USER.USERNAME all resolve the same way.

Type Conversion

All @Param values originate as strings — from properties files or the Builder API. Pixie uses the Converter from tomitribe-util to convert these strings into Java types. This is the same conversion system used by Crest for CLI argument parsing.

Conversion Chain

The converter tries the following strategies in order, using the first one that succeeds:

  1. Registered PropertyEditor — if a java.beans.PropertyEditor is registered for the type
  2. EnumEnum.valueOf() with case-insensitive fallback (exact match, then uppercase, then lowercase)
  3. Constructor(String) — any public constructor taking a single String parameter
  4. Constructor(CharSequence) — any public constructor taking CharSequence
  5. Public static factory method — any public static method taking String and returning the target type. The method name does not matter — valueOf, of, parse, from, or any other name will work.

Built-in Types

The following types work out of the box with no additional configuration:

CategoryTypes
Primitives & wrappersbyte, short, int, long, float, double, boolean, char and their boxed equivalents
StringsString, CharSequence
EnumsAny enum type (case-insensitive matching)
Files & pathsjava.io.File
Networkjava.net.URI, java.net.URL
Timejava.util.concurrent.TimeUnit
tomitribe-utilorg.tomitribe.util.Duration (e.g., "30 seconds", "5m"), org.tomitribe.util.Size (e.g., "10mb", "2.5 gb")

Example with Multiple Types

public class DataTypes {

    public DataTypes(@Param("aString") final String aString,
                     @Param("abyte") final byte abyte,
                     @Param("aint") final int aint,
                     @Param("along") final long along,
                     @Param("afloat") final float afloat,
                     @Param("adouble") final double adouble,
                     @Param("aboolean") final boolean aboolean,
                     @Param("achar") final char achar,
                     @Param("aTimeUnit") final TimeUnit aTimeUnit,
                     @Param("aURI") final URI aURI,
                     @Param("aFile") final File aFile) {
        // ...
    }
}
joe = new://org.example.DataTypes
joe.aString = Hello
joe.abyte = 123
joe.aint = 1234567890
joe.along = 1234567890123456789
joe.afloat = 1.234
joe.adouble = 0.1234567890
joe.aboolean = true
joe.achar = z
joe.aTimeUnit = SECONDS
joe.aURI = https://example.com
joe.aFile = /tmp/data.txt

Enums

Enums are matched case-insensitively. All of the following are equivalent:

server.state = RUNNING
server.state = running
server.state = Running

Duration and Size

Pixie includes tomitribe-util which provides human-readable Duration and Size types:

public class CacheConfig {
    public CacheConfig(@Param("ttl") final Duration ttl,
                       @Param("maxSize") final Size maxSize) {
        // ...
    }
}
cache = new://org.example.CacheConfig
cache.ttl = 30 seconds
cache.maxSize = 64 mb

Duration accepts formats like 10s, 30 seconds, 1 day and 5 hours, 500ms.

Size accepts formats like 10kb, 2.5 mb, 1 gigabyte, 512 bytes.

Collections and Maps

A single property can be injected as a List, Set, or Map by declaring the parameter with a generic element type. Each element of the value goes through the normal conversion chain, so any type that works as a scalar @Param also works as a collection element.

Lists and Sets

Values are split on commas (surrounding whitespace is tolerated):

public class FeedReader {
    public FeedReader(@Param("uris") final List<URI> uris) {
        // ...
    }
}
reader = new://org.example.FeedReader
reader.uris = http://one, http://two/dos

Supported collection types:

Declared typeImplementation
List, Collection, ArrayListArrayList
Set, HashSetHashSet
SortedSet, TreeSetTreeSet

The element type can be any type the converter supports — primitives, enums, URI, File, Duration, Size, custom types with a Constructor(String) or a static factory, etc.

public class Scheduler {
    public Scheduler(@Param("units") final Set<TimeUnit> units,
                     @Param("delays") final List<Duration> delays) {
        // ...
    }
}
scheduler = new://org.example.Scheduler
scheduler.units = SECONDS, MINUTES, HOURS
scheduler.delays = 30 seconds, 5 minutes, 1 hour

Maps

Values use the standard Java properties format — one key=value pair per line — and both keys and values go through the converter:

public class Quotas {
    public Quotas(@Param("limits") final Map<String, Integer> limits) {
        // ...
    }
}

In code, the value is a multi-line string:

properties.setProperty("quotas", "new://org.example.Quotas");
properties.setProperty("quotas.limits", "alpha=10\nbeta=25\ngamma=100");

Supported map types:

Declared typeImplementation
Map, HashMapHashMap
SortedMap, TreeMapTreeMap

Custom Types

Any class you write that has a public Constructor(String) automatically works as a @Param type:

public class EmailAddress {
    private final String value;

    public EmailAddress(final String value) {
        if (!value.contains("@")) {
            throw new IllegalArgumentException("Invalid email: " + value);
        }
        this.value = value;
    }

    public String get() { return value; }
}
public class NotificationService {
    public NotificationService(@Param("admin") final EmailAddress admin) {
        // ...
    }
}
notifications = new://org.example.NotificationService
notifications.admin = admin@example.com

Classes with any public static method that takes a String and returns an instance of the class also work automatically. Common conventions include valueOf(String), of(String), parse(String), and from(String), but the method name is not restricted — any name will be discovered.

Custom PropertyEditor

For full control over conversion, register a java.beans.PropertyEditor:

public class HostPortEditor extends PropertyEditorSupport {
    @Override
    public void setAsText(final String text) {
        final String[] parts = text.split(":");
        setValue(new HostPort(parts[0], Integer.parseInt(parts[1])));
    }
}

Register it with Java’s PropertyEditorManager:

PropertyEditorManager.registerEditor(HostPort.class, HostPortEditor.class);

The PropertyEditor strategy has the highest priority in the conversion chain, so it will take precedence over constructors and static factory methods.