Conflux Programming Guide
The Ctx class in the conflux package is a lightweight, type-safe, generic, application-wide context. It provides key-based, strongly typed storage with thread-safe put/get, type consistency for each key, and listener support to observe changes. Listeners are weakly referenced to avoid memory leaks. Ctx is ideal for coordinating shared data among otherwise decoupled classes.
Quick Start
First, import the required classes:
import conflux.Ctx;
import conflux.Key;
import conflux.Listener;
Creating Keys
Define typed keys for the data you want to store:
Key<Integer> counterKey = Key.of("counter", Integer.class);
Key<String> nameKey = Key.of("name", String.class);
Key<int[]> arrayKey = Key.of("numbers", int[].class);
Key<MyPojo> pojoKey = Key.of("myPojo", MyPojo.class);
Storing and Retrieving Values
Storing
Ctx.INSTANCE.put(counterKey, 42);
Ctx.INSTANCE.put(nameKey, "Alice");
Ctx.INSTANCE.put(arrayKey, new int[]{1, 2, 3});
Ctx.INSTANCE.put(pojoKey, new MyPojo("Widget", 99));
Retrieving
Integer counter = Ctx.INSTANCE.get(counterKey);
System.out.println("Counter: " + counter);
String name = Ctx.INSTANCE.get(nameKey);
System.out.println("Name: " + name);
int[] numbers = Ctx.INSTANCE.get(arrayKey);
System.out.println("Numbers: " + Arrays.toString(numbers));
MyPojo pojo = Ctx.INSTANCE.get(pojoKey);
System.out.println("POJO: " + pojo);
Listening for Changes
You can attach listeners to keys so other classes can react when a value changes. Listeners are weakly referenced to avoid memory leaks.
Listener<Integer> counterListener = newValue ->
System.out.println("Counter changed to " + newValue);
Ctx.INSTANCE.addListener(counterKey, counterListener);
Ctx.INSTANCE.put(counterKey, 100); // triggers listener
Ctx.INSTANCE.put(counterKey, 200); // triggers listener again
Ctx.INSTANCE.removeListener(counterKey, counterListener);
Working with Arrays
int[] oldArray = new int[]{1, 2, 3};
int[] newArray = new int[]{4, 5, 6};
Ctx.INSTANCE.put(arrayKey, oldArray);
Ctx.INSTANCE.addListener(arrayKey, newValue ->
System.out.println("Array changed to " + Arrays.toString(newValue))
);
Ctx.INSTANCE.put(arrayKey, newArray); // triggers listener
Using with Objects
Define a simple POJO:
public class MyPojo {
private final String name;
private final int price;
public MyPojo(String name, int price) {
this.name = name;
this.price = price;
}
@Override
public String toString() {
return name + " ($" + price + ")";
}
}
And store it:
MyPojo initialPojo = new MyPojo("Widget", 50);
Ctx.INSTANCE.put(pojoKey, initialPojo);
Ctx.INSTANCE.addListener(pojoKey, newValue ->
System.out.println("POJO changed to " + newValue)
);
Ctx.INSTANCE.put(pojoKey, new MyPojo("Widget Pro", 99)); // triggers listener
Type Safety
Once a key is registered with a type, you cannot use the same name with a different type:
Key<Integer> myKey = Key.of("sharedKey", Integer.class);
Ctx.INSTANCE.put(myKey, 5);
// later:
Key<String> badKey = Key.of("sharedKey", String.class);
Ctx.INSTANCE.put(badKey, "wrong type"); // throws IllegalStateException
Removing and Clearing
Remove a single key and its listeners:
Ctx.INSTANCE.remove(nameKey);
Or clear everything:
Ctx.INSTANCE.clear();
Ephemeral Named Contexts
You can create multiple independent, ephemeral contexts, each identified by a unique name. These contexts behave like isolated key-value stores:
- They are created lazily on first access.
- They live only as long as you keep a reference to them.
- They are automatically deregistered once no strong references remain (via the garbage collector), or can be removed explicitly.
// Obtain or create named contexts
CtxInterface orderCtx = Ctx.INSTANCE.getContext("order");
CtxInterface userCtx = Ctx.INSTANCE.getContext("user");
// Define keys for each context
Key<String> orderIdKey = Key.of("orderId", String.class);
Key<String> userIdKey = Key.of("userId", String.class);
// Put values into separate contexts
orderCtx.put(orderIdKey, "ORD123");
userCtx.put(userIdKey, "USR456");
// Retrieve independently
String orderId = orderCtx.get(orderIdKey);
String userId = userCtx.get(userIdKey);
You can list all named contexts:
Set<String> contexts = Ctx.INSTANCE.contextNames();
System.out.println("Active contexts: " + contexts);
Each context is fully isolated—changes in one do not affect any others—making them suitable for modular scoping (e.g., per-request, per-user, per-transaction).
Best Practices
- Define keys as constants - centralize Key definitions to avoid typos and ensure consistency across the codebase.
- Scope listeners carefully - register listeners only where reactive updates are needed; remove them when the owning component is disposed. (The framework uses weak references to help, but explicit cleanup is still good practice.)
- Use descriptive, unique key names - this prevents accidental collisions within a context and improves readability.
- Separate concerns with multiple contexts - create distinct contexts (e.g., "order", "user", "session") to isolate unrelated data and reduce coupling.
- Treat named contexts as ephemeral - hold onto a CtxInterface only for as long as you need it; once all strong references are gone, the context is automatically reclaimed by the garbage collector.
Complete Example
Key<Integer> temperatureKey = Key.of("temperature", Integer.class);
Listener<Integer> display = t ->
System.out.println("Temperature changed to " + t + "°C");
Ctx.INSTANCE.addListener(temperatureKey, display);
Ctx.INSTANCE.put(temperatureKey, 20);
Ctx.INSTANCE.put(temperatureKey, 25);
Ctx.INSTANCE.removeListener(temperatureKey, display);
Ctx.INSTANCE.put(temperatureKey, 30); // no notification after listener removed
Home | API Reference | Design and Architecture | FAQ | Contributing