# pixie
**Repository Path**: mirrors_tomitribe/pixie
## Basic Information
- **Project Name**: pixie
- **Description**: Tiny but powerful library creating your java objects with a bit of magical pixie dust
- **Primary Language**: Unknown
- **License**: Apache-2.0
- **Default Branch**: main
- **Homepage**: None
- **GVP Project**: No
## Statistics
- **Stars**: 0
- **Forks**: 0
- **Created**: 2024-12-21
- **Last Updated**: 2026-02-15
## Categories & Tags
**Categories**: Uncategorized
**Tags**: None
## README
= Pixie
Pixie is a tiny 100k jar that handles configuration, dependency injection and events.
It can handle any scenario where you would use reflection to instantiate a Java object.
.Maven dependency:
[source,xml]
----
org.tomitribe.pixie
pixie
2.0
----
== Example
Let's imagine we want to use Pixie to configure and build two classes we've created called `Messages` and `Address`.
Our first step is to annotate the constructor parameters with Pixie annotations. Pixie will map the configuration to
our constructor based on the names we use.
[source,java]
----
import org.tomitribe.pixie.Component;
import org.tomitribe.pixie.Default;
import org.tomitribe.pixie.Name;
import org.tomitribe.pixie.Param;
public class Person {
private final String name;
private final Integer age;
private final Address address;
public Person(@Name final String name,
@Param("age") final Integer age,
@Paran("address") @Component final Address address) {
this.name = name;
this.age = age;
this.address = address;
}
//...
}
public class Address {
private final String street;
private final String city;
private final State state;
private final int zipcode;
private final String country;
public Address(@Param("street") final String street,
@Param("city") final String city,
@Param("state") final State state,
@Param("zipcode") final int zipcode,
@Param("country") @Default("USA") final String country) {
this.street = street;
this.city = city;
this.state = state;
this.zipcode = zipcode;
this.country = country;
}
//...
}
public enum State {
WI, MN, CA;
}
----
We can use the Pixie `System` to configure and build the above objects via either a Properties file defintion or in code via a Builder API
=== Properties
Our properties file might look like so. The file name and location can be anything as it's our
job to read this into a `java.util.Properties` instance.
[source,properties]
----
jon = new://org.example.Person
jon.age = 46
jon.address = @home
home = new://org.example.Address
home.street = 823 Roosevelt Street
home.city = River Falls
home.state = WI
home.zipcode = 54022
home.country = USA
----
The following code would build a Pixie `System` via a `java.util.Properties` instance and lookup
the `Messages` instance by type.
[source,java]
----
import java.util.Properties;
import org.example.Person;
import org.tomitribe.pixie.System;
//...
public static void main(String[]) {
final Properties properties = new Properties();
properties.load(...); // read the properties file
final System system = new System(properties);
final Person person = system.get(Person.class);
assertEquals("jane", person.getName();
final Address address = person.getAddress();
assertNotNull(address);
assertEquals("820 Roosevelt Street", address.getStreet());
}
----
=== Builder API
Alternatively, we can skip the properties and build our `System` in code fluently.
[source,java]
----
import java.util.Properties;
import org.example.Person;
import org.tomitribe.pixie.System;
//...
public static void main(String[]) {
final System system = System.builder()
.definition(Person.class, "jane")
.param("age", 37)
.comp("address", "home")
.definition(Address.class, "home")
.param("street", "820 Roosevelt Street")
.param("city", "River Falls")
.param("state", "WI")
.param("zipcode", "54022")
.build();
final Person person = system.get(Person.class);
//...
}
----
== Pixie Constructor Annotations
Pixie supports constructor injection. Each parameter the constructor Pixie will use must be annotated with either `@Param`, `@Component`, `@Event` or `@Name`.
[options="header"]
|===
| Annotation | Purpose | Example Usage
| `@Param` | Maps a constructor parameter to a config property | `@Param("username") final String username`
| `@Default` | Provides a default value if the property is missing | `@Param("country") @Default("USA") final String country`
| `@Component` | Injects a dependent object built by or given to Pixie `System` | `@Component final PaymentProcessor paymentProcessor`
| `@Nullable` | Allows a property to be `null` if missing | `@Nullable @Param("footer") final String footer`
| `@Name` | Injects the component's name from the config | `@Name final String serviceName`
| `@Event` | Injects a `Consumer` to fire events | `@Event final Consumer event`
| `@Observes` | Marks a method as an event listener | `public void onEvent(@Observes OrderPlaced event)`
|===
All the above annotations are in the `org.tomitribe.pixie` package.
== `@Param`
*Purpose:* Binds a constructor parameter to a configuration property.
*Usage:* Pixie will automatically inject values from a properties file or the builder API.
.Example:
[source,java]
----
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;
}
}
----
*Maps to a properties file entry:*
[source,properties]
----
user=new://org.example.User
user.username=alice
user.age=30
----
Any Java type that can be created from a `String` is supported. Pixie will inspect the java class and look for one of the following:
- Public constructor with a single parameter of type `String`
- Public static method with a single parameter of type `String` returning an instance of the type
---
== `@Default`
*Purpose:* Specifies a default value for a constructor parameter if it is not set in the configuration.
.Example:
[source,java]
----
public final class Address {
private final String country;
public Address(@Param("country") @Default("USA") final String country) {
this.country = country;
}
}
----
If `country` is missing from the config, `"USA"` is used. Applies to both `@Param` and `@Component`. When used on `@Component` it implies the name of the component that should be injected.
---
== `@Component`
*Purpose:* Indicates that a constructor parameter should be injected as a component dependency.
.Example:
[source,java]
----
public final class ShoppingCart {
private final PaymentProcessor paymentProcessor;
public ShoppingCart(@Param("processor") @Component final PaymentProcessor paymentProcessor) {
this.paymentProcessor = paymentProcessor;
}
}
----
Pixie can resolve this reference by name or by type.
.Resolution by name:
[source,properties]
----
cart=new://org.example.ShoppingCart
cart.processor=@stripe
----
With the above configuration Pixie will look in the `System` for an object with the
name `stripe` and inject it as the value of the `processor` when constructing the `ShoppingCart`.
A `ConstructionFailedException` will be thrown if any of the following scenarios occurs:
- no `@Param` was provided
- no object with that name is found
- if the object found is of the wrong type.
.Resolution by type:
[source,properties]
----
cart=new://org.example.ShoppingCart
----
In the above configuration the `processor` name has not be specified. In this situation, Pixie will
look in the `System` for any object with the the type `PaymentProcessor` and inject it as the value of the `processor` when constructing the `ShoppingCart`.
If there are *multiple instances* of `PaymentProcessor` they will be sorted in descending order by name and the first will be picked.
A `ConstructionFailedException` will be thrown if no objects with that type are found.
=== Adding Custom Components
The `@Component` annotation can be used to resolve components which are added directly to the Pixie `System`.
.Properties:
[source,properties]
----
jane=new://org.example.Person\n" +
jane.age = 37
jane.address=@home
----
In the above properties, the Person object has a `@Component` reference to `Address` called `home` which is not defined.
The `home` instance can be added directly to the Pixie `System` before we load the properties.
.Adding to Pixie System:
[source,java]
----
final Properties properties = //...
final System system = new System();
system.add("home", new Address("820 Roosevelt Street","River Falls", State.WI, 54022, "USA"));
system.load(properties);
final Person person = system.get(Person.class);
assertNotNull(person.getAddress());
----
In the above code we've directly created the `Address` instance and handed it to Pixie `System` with the name `home`.
---
== `@Nullable`
*Purpose:* Marks a constructor parameter as optional (can be `null` if not configured).
.Example:
[source,java]
----
public final class Notification {
private final String message;
private final String footer;
public Notification(@Param("message") final String message, @Nullable @Param("footer") final String footer) {
this.message = message;
this.footer = footer;
}
}
----
If `footer` is missing from the config, it will be `null` instead of throwing an error.
---
== `@Name`
*Purpose:* Injects the component’s name from the configuration.
.Example:
[source,java]
----
public final class Service {
private final String serviceName;
public Service(@Name final String serviceName) {
this.serviceName = serviceName;
}
}
----
If configured as `myService = new://com.foo.Service`, the constructor will receive `"myService"`.
---
== `@Event`
*Purpose:* Injects an event consumer (`Consumer`) into a component so it can fire events.
.Example:
[source,java]
----
public final class OrderService {
private final Consumer orderPlacedEvent;
public OrderService(@Event final Consumer orderPlacedEvent) {
this.orderPlacedEvent = orderPlacedEvent;
}
public void placeOrder(final String orderId) {
orderPlacedEvent.accept(new OrderPlaced(orderId));
}
}
----
Pixie will inject a `Consumer` that calls `System.fire(event)` which will
invoke all observer methods in all components in the `System`.
---
== `@Observes`
*Purpose:* Marks a method as an event listener.
.Example:
[source,java]
----
public final class OrderListener {
public void onOrderPlaced(@Observes final OrderPlaced event) {
System.out.println("Order placed: " + event.getOrderId());
}
}
----
When `OrderPlaced` is fired, this method will be called automatically.
It is possible to listen for events by any assignable type, even `java.lang.Object`
.Example:
[source,java]
----
public final class EverythingListener {
public void onEvent(@Observes final Object event) {
System.out.println("Event observed: " + event);
}
}
----
---
== Configuration Validation
Pixie provides strict validation to ensure configuration correctness and prevent common issues with properties files.
=== Case Insensitivity
All properties in Pixie are **case insensitive**, meaning users will not encounter failures due to incorrect capitalization. For example, the following entries are treated as equivalent:
[source,properties]
----
user.name=Alice
User.Name=Alice
USER.NAME=Alice
----
Regardless of how the property is written, it will be correctly matched and retrieved.
=== Strict Property Validation
Pixie enforces strict validation of configuration properties to prevent misconfigurations:
- If a property is **specified in the configuration file but does not exist in the corresponding class**, Pixie will **throw an exception** at startup.
- This ensures that typos or removed properties do not lead to silent failures.
For example, given the following properties file:
[source,properties]
----
app.mode=production
app.timeout=5000
----
If the `app.timeout` property is removed from the Java class but remains in the configuration file, Pixie will **fail fast** with an error, preventing users from relying on "dead" properties.