Annotations Reference¶
Complete reference for all FlagZen annotations.
@Feature¶
Marks a Java interface as a feature flag dispatch point. The annotation processor generates a proxy class that delegates method calls to the active variant implementation.
Attributes¶
| Attribute | Type | Default | Description |
|---|---|---|---|
value |
String |
required | The flag key used to resolve the active variant. Must be a non-empty string. |
type |
FeatureType |
STRING |
The type of flag value used to dispatch variants. Must match the variant value types. |
fallback |
FallbackStrategy |
EXCEPTION |
Strategy when no variant matches the flag value. |
Constraints¶
- Must be applied to an interface, not a class
- The interface must be public
- The interface package becomes the package for the generated proxy class
- All
@Variantimplementations must haveof = <this interface class> - All variant values must match the declared
type
Example¶
@Feature("checkout-flow")
public interface CheckoutFlow {
String execute();
}
@Feature(value = "max-retries", type = FeatureType.INT)
public interface RetryStrategy {
int getMaxRetries();
}
@Feature(value = "dark-mode", type = FeatureType.BOOLEAN, fallback = FallbackStrategy.NOOP)
public interface DarkMode {
String theme();
}
@Variant¶
Maps one or more flag values to a variant implementation class. A feature requires at least one @Variant unless a @DefaultVariant is defined.
Attributes¶
| Attribute | Type | Default | Description |
|---|---|---|---|
value |
String[] |
{} |
String variant value(s) for STRING-type features. Auto-wraps scalars to single-element arrays. |
intValue |
int[] |
{} |
Integer variant value(s) for INT-type features. Auto-wraps scalars. |
longValue |
long[] |
{} |
Long variant value(s) for LONG-type features. Auto-wraps scalars. |
doubleValue |
CloseTo[] |
{} |
Double variant value(s) with approximate matching for DOUBLE-type features. |
booleanValue |
String |
"" (empty) |
Boolean variant value: "true" or "false". Empty string means not set. |
of |
Class<?> |
void.class |
The feature interface this variant belongs to. Required. |
Constraints¶
- Must be applied to a class (not an interface)
- The class must implement the interface specified in
of - At least one value array must be non-empty, or
booleanValuemust be non-empty - All value types must match the feature's declared
type - Variant values must be unique across all
@Variantannotations for the same feature - Cannot be used on abstract classes or inner classes
Compile-Time Validation¶
The annotation processor validates:
- Feature interface exists and is properly annotated
- Variant class implements the feature interface
- Value type matches feature type declaration
- No duplicate values across variants for the same feature
- No mismatch between feature type and variant value type
Examples¶
String variant (single value)
@Variant(value = "CLASSIC", of = CheckoutFlow.class)
public class ClassicCheckout implements CheckoutFlow {
@Override
public String execute() { return "classic"; }
}
String variant (multiple values)
@Variant(value = {"CLASSIC", "LEGACY"}, of = CheckoutFlow.class)
public class ClassicCheckout implements CheckoutFlow {
@Override
public String execute() { return "classic"; }
}
Integer variant (array)
@Variant(intValue = {3, 5}, of = RetryStrategy.class)
public class ConservativeRetry implements RetryStrategy {
@Override
public int getMaxRetries() { return 3; }
}
@DefaultVariant¶
Marks a variant as the fallback when no flag value matches any @Variant annotation. At most one @DefaultVariant per feature.
Attributes¶
| Attribute | Type | Default | Description |
|---|---|---|---|
of |
Class<?> |
void.class |
The feature interface this default variant belongs to. Required. |
Constraints¶
- Must be applied to a class implementing the feature interface
- At most one per feature
- Cannot be used with
@Varianton the same class - Only meaningful when the feature's
fallback = FallbackStrategy.EXCEPTION(default)
Example¶
@Feature("checkout-flow")
public interface CheckoutFlow {
String execute();
}
@DefaultVariant(of = CheckoutFlow.class)
public class SafeCheckout implements CheckoutFlow {
@Override
public String execute() { return "safe"; }
}
When checkout-flow flag is absent or unrecognized, SafeCheckout handles the request.
@WhenTrue / @WhenFalse¶
Convenience annotations for BOOLEAN-type features. Equivalent to @Variant(booleanValue = "true") and @Variant(booleanValue = "false") respectively.
Attributes¶
| Attribute | Type | Default | Description |
|---|---|---|---|
of |
Class<?> |
void.class |
The boolean feature interface this variant belongs to. Required. |
Constraints¶
- Can only be used on variants of
BOOLEAN-type features - Feature's
typeattribute must beFeatureType.BOOLEAN - Each boolean feature needs exactly one
@WhenTrueand optionally one@WhenFalse
Example¶
@Feature(value = "dark-mode", type = FeatureType.BOOLEAN)
public interface DarkMode {
String theme();
}
@WhenTrue(of = DarkMode.class)
public class DarkTheme implements DarkMode {
@Override
public String theme() { return "dark"; }
}
@WhenFalse(of = DarkMode.class)
public class LightTheme implements DarkMode {
@Override
public String theme() { return "light"; }
}
@CloseTo¶
Specifies a double variant value with approximate matching. Used within @Variant(doubleValue = {...}).
Attributes¶
| Attribute | Type | Default | Description |
|---|---|---|---|
value |
double |
required | The double value to match against. |
delta |
double |
1e-10 |
The maximum absolute difference for a match. Accepts values like 0.01 for 1% tolerance. |
Constraints¶
- Can only be used in
@Variant(doubleValue = {...}) - Feature type must be
FeatureType.DOUBLE deltamust be positive
Example¶
@Feature(value = "compression-ratio", type = FeatureType.DOUBLE)
public interface CompressionStrategy {
double getRatio();
}
@Variant(doubleValue = {@CloseTo(value = 0.5, delta = 0.01)}, of = CompressionStrategy.class)
public class MediumCompression implements CompressionStrategy {
@Override
public double getRatio() { return 0.5; }
}
@Variant(doubleValue = {@CloseTo(value = 0.9, delta = 0.05)}, of = CompressionStrategy.class)
public class HighCompression implements CompressionStrategy {
@Override
public double getRatio() { return 0.9; }
}
If the compression-ratio flag is 0.49 or 0.51, it matches the first variant (0.5 ± 0.01). If it is 0.88, 0.89, 0.90, 0.91, or 0.92, it matches the second variant (0.9 ± 0.05).
FeatureType Enum¶
Defines the type of flag value used to dispatch variants.
Values¶
| Value | Description | Range | Example Variant |
|---|---|---|---|
STRING |
Text-based flag values (default). | any string | @Variant(value = "PREMIUM") |
INT |
32-bit signed integer flag values. | -2,147,483,648 to 2,147,483,647 | @Variant(intValue = 42) |
LONG |
64-bit signed integer flag values. | -9,223,372,036,854,775,808 to 9,223,372,036,854,775,807 | @Variant(longValue = 1000000000000L) |
BOOLEAN |
Boolean flag values (true or false). |
true, false |
@WhenTrue(of = Feature.class) |
DOUBLE |
64-bit floating-point flag values with approximate matching. | IEEE 754 double range | @Variant(doubleValue = {@CloseTo(value = 0.5)}) |
FallbackStrategy Enum¶
Defines the runtime behavior when no variant matches the flag value.
Values¶
| Value | Description | Behavior |
|---|---|---|
EXCEPTION (default) |
Throw an exception if no variant matches. | Calls to the proxy throw UnmatchedVariantException if the flag value has no corresponding variant and no @DefaultVariant is defined. Fails loudly at runtime. |
NOOP |
Silently do nothing if no variant matches. | Calls to the proxy return default values (null for objects, 0 for primitives, false for booleans). No exception. Used for optional features. |
Usage¶
// Strict: must have a matching variant or default
@Feature("checkout-flow")
public interface CheckoutFlow {
String execute();
}
// Optional: missing variants are silently ignored
@Feature(value = "experimental-ui", fallback = FallbackStrategy.NOOP)
public interface ExperimentalUI {
void render();
}
Compile-Time Validation Summary¶
The annotation processor performs these validations at compile time:
| Check | Scope | Failure |
|---|---|---|
@Feature applied only to interfaces |
per feature | Error: cannot apply to class |
@Variant applied only to classes |
per variant | Error: cannot apply to interface |
| Variant implements feature interface | per variant | Error: class does not implement feature |
| All variant values match feature type | per variant | Error: type mismatch |
| No duplicate variant values | per feature | Error: duplicate value |
| Feature has at least one variant or default | per feature | Error: no variants found (unless @DefaultVariant) |
Boolean feature has @WhenTrue or @DefaultVariant |
per feature | Error: boolean feature with no true variant |
CloseTo only in DOUBLE features |
per variant | Error: type mismatch |
Generated Code¶
For each @Feature interface, the annotation processor generates:
-
Proxy class:
{Feature}_FlagZenProxyin the same package as the interface. This class is public with a package-private constructor and implements the feature interface. -
Metadata class:
{Feature}_FlagZenMetadatain the same package. This implements theFeatureMetadataSPI and is registered viaMETA-INF/services/com.flagzen.spi.FeatureMetadata.
The proxy is discovered and instantiated by FeatureDispatcher via the metadata class.
Best Practices¶
- Use short, descriptive flag keys:
checkout-flow,dark-mode, notcf,dm, orexperimental_ui_v2_for_testing_only - One feature per interface: Do not combine multiple concerns in a single
@Featureinterface - Consistent variant naming: Use SCREAMING_SNAKE_CASE for string variant values (e.g.,
CLASSIC,PREMIUM,EXPERIMENTAL) - Prefer
@DefaultVariantover exceptions: For optional features, define a sensible default rather than throwing on mismatch - Group variant implementations by package: Keep all variants for a feature in the same package as the feature interface
- Document variant semantics: Add Javadoc explaining what each variant value means and when it is used