Architecture
The design keeps a Spring-free core generic over the application’s event type E, with a
thin Spring Boot starter on top. The core never references any application domain type — a
single NotificationAdapter<E> bridges it.
1. Modules
| Module | Role |
|---|---|
|
The engine. No Spring dependency; uses the JDK |
|
Auto-configuration binding |
|
Dependency BOM for version alignment. |
|
Runnable example. Built under the |
2. Core concepts
Notifier<E>-
The SPI —
void notify(E). Implementations must never throw; a failing channel must not break the caller. NotificationAdapter<E>-
Supplied by the application. Maps
Etoid(stable identity for transition tracking),status, andmessage. The only place the app’s domain type is referenced, which is what lets channels stay event-agnostic. Notifications<E>-
The application-facing facade. Holds a fan-out list of channels.
send(event)/send(event, routeTags)delivers to every channel whose tags overlap the route tags (untagged channels always fire). Two gates sit in front: a runtime-mute filter and per-channel tags; each channel additionally applies its own transition filter. Per-channel `RuntimeException`s are caught and logged. NotificationsFactory<E>-
Builds
Notificationsfacades from a URL list with shared defaults. Single-tenant apps get one global facade; multi-tenant apps inject the factory and build one facade per tenant (and setnotify4j.global=false). NotifierUrlParser<E>-
Turns a URL into a channel (notifier + routing tags). Adding a channel means adding a case here plus the notifier class.
3. Delivery flow
send(event, tags)
└─ mute gate (FilteringNotifier) suppress active runtime mutes
└─ for each channel matching tags:
└─ transition filter fire only on a real status change
└─ channel notifier POST the channel-specific payload
(RuntimeException -> caught + logged, never propagated)
4. Notifier hierarchy
AbstractEventNotifier<E>-
Base: an
enabledflag, ashouldNotifyguard, and error isolation (notifyisfinal, catches and logs). Subclasses implementdoNotify. AbstractHttpNotifier<E>-
Base for webhook-style channels. Configured with functions (no subclass-per-event); owns the JDK
HttpClient, applies a transition filter, POSTs JSON. Subclasses implementpayload(event)and optionallyheaders(). Concrete: Slack, Teams, Discord, Webhook, Telegram, Ntfy, PagerDuty, OpsGenie. AbstractTransitionNotifier<E>-
Alternative base for non-HTTP notifiers that subclass instead of taking functions.
Wrappers (decorators): CompositeNotifier (fan-out), FilteringNotifier (mute),
RemindingNotifier (re-notify entities stuck in a state), LoggingNotifier (default sink).
5. Using the engine without Spring
The core has no Spring dependency. Build a facade directly:
var notifications = new Notifications<>(
List.of("slack://...", "pagerduty://<key>?tags=failed"),
adapter, // your NotificationAdapter<E>
List.of(), // extra programmatic notifiers
List.of("*:RUNNING"), // ignore-changes
true); // include the logging sink
notifications.send(event);