Documentation
Everything you need to integrate NotiFlow into your Java application.
Installation
NotiFlow is available on Maven Central. Choose the artifact that fits your setup.
Spring Boot Starter
For Spring Boot applications, the starter provides auto-configuration with sensible defaults and zero boilerplate. notiflow-core is a transitive dependency — one declaration is enough.
Core Library (Standalone)
The core library has zero Spring dependencies. Use it with any Java framework or Jakarta WebSocket container (Tyrus, Jetty, Undertow).
Spring Boot Setup
Add @EnableNotiflow to your main application class. A WebSocket endpoint is immediately available at /notifications.
All components are auto-configured with @ConditionalOnMissingBean, so you can override any bean by declaring your own.
Standalone Setup
Without Spring, wire the components manually. Every class uses constructor injection with no hidden dependencies.
Sending Notifications
Build notifications using the builder pattern, then use NotificationService to deliver them.
When a user is offline, sendToUser automatically queues the notification. When the user reconnects, queued notifications are delivered immediately with QUEUED envelope type.
Topics
Topics are server-managed and bound to user IDs (not sessions). This means subscriptions persist across reconnections — an offline user remains subscribed and will receive queued topic notifications.
Offline Queueing
When a user is offline, notifications are stored in the NotificationQueue with the following behavior:
- Each notification has a TTL — expired entries are cleaned up automatically
- Each user has a capacity limit — when exceeded, the oldest notifications are evicted first
- On reconnect, all queued notifications are delivered immediately with
QUEUEDenvelope type
QUEUED type lets clients distinguish between live notifications and previously queued ones, enabling different UI treatments (e.g., batch display vs. toast).
Delivery & ACK
When ACK-based delivery is enabled, every sent notification is tracked until the client confirms receipt.
If the client does not acknowledge within the ack timeout:
After exhausting max-retries, the notification is passed to the DeliveryFailureHandler.
Heartbeat
When heartbeat is enabled, the server periodically sends PING messages to detect stale connections.
- Server sends
PINGeveryping-interval - Client must respond with
PONG - If no activity is detected within
ping-timeout, the session is closed withGOING_AWAYreason
Message Types
| Type | Direction | Description |
|---|---|---|
NOTIFICATION | Server → Client | New live notification |
QUEUED | Server → Client | Previously queued notification delivered on reconnect |
ACK | Client → Server | Client confirms receipt of a notification |
PING | Server → Client | Heartbeat check |
PONG | Client → Server | Heartbeat response |
Envelope Format
All messages are wrapped in an Envelope with a consistent structure:
Client Integration
Connect via standard WebSocket and handle the protocol messages:
Pluggable Components
Every component follows the interface + default implementation pattern. The Spring Boot starter registers each bean with @ConditionalOnMissingBean, so defining your own bean replaces the default.
Authenticator
NoOpAuthenticator accepts all connections. Always replace it in production.
Annotate your implementation with @Component — the @ConditionalOnMissingBean on the default causes it to be skipped automatically. To pass the authenticated identity to UserService, store it in session.getUserProperties() under the key "notiflow.userId". The default DefaultUserService reads this key, so no custom UserService is needed unless you use a different key.
UserService
Extracts the stable user ID from a WebSocket session after authentication. The default DefaultUserService resolves user ID from:
- Session property
notiflow.userIdset byAuthenticatorviasession.getUserProperties() - Raw WebSocket session ID as fallback — never returns
null
If your Authenticator stores the identity under "notiflow.userId", the default works without any changes. Only replace UserService when you use a different key or need custom resolution logic.
SessionRegistry
The default InMemorySessionRegistry stores sessions in a ConcurrentHashMap. It enforces single session per user — connecting with the same user ID replaces the previous session.
Replace for distributed deployments:
TopicService
Server-managed topic subscriptions bound to user IDs. The default InMemoryTopicService is lost on restart. For production, implement persistence using JPA or any other store — annotate your class with @Component and the default is skipped.
NotificationQueue
Stores notifications for offline users with configurable TTL and per-user capacity. The default InMemoryNotificationQueue is lost on restart. For production, persist with JPA: implement enqueue / dequeue / remove / peek / size / clear backed by a database entity, then annotate with @Component.
DeliveryTracker
Tracks unacknowledged notifications. When ACK is enabled, the tracker monitors for acknowledgment and retries up to max-retries times. For production, use the decorator pattern: wrap InMemoryDeliveryTracker to keep retry scheduling, and add persistence as a layer on top. This avoids reimplementing the retry clock from scratch.
AuditingDeliveryTracker delegates all retry/scheduling calls to InMemoryDeliveryTracker and adds a notification_audit row on track(), stamps ackedAt on acknowledge(), and failedAt on markFailed(). Declare the @Bean with return type DeliveryTracker — this satisfies the @ConditionalOnMissingBean check and prevents the auto-configured default from being created.
DeliveryFailureHandler
Called when a notification permanently fails after all retry attempts:
MessageCodec
The default JacksonMessageCodec uses Jackson with ISO-8601 date formatting, unknown property tolerance, and null exclusion. To customize:
Custom Message Handlers
Implement MessageHandler to process any incoming WebSocket message type.
The auto-configuration collects all MessageHandler beans via
beanFactory.getBeansOfType(MessageHandler.class) — the
Spring bean name is used as the message type string.
Custom handlers are registered after the built-in ones, so a bean named
"ACK" or "PONG" replaces the built-in handler for that type.
Adding a new handler
Annotate with @Component("TYPE_NAME") where TYPE_NAME matches the
type field the client sends in the Envelope:
The client sends:
Replacing the built-in ACK handler
The built-in ACK handler calls DeliveryTracker.acknowledge(userId, notificationId).
To extend it — for example to add custom audit logging on top — declare a @Bean named
"ACK". It is registered after the built-in, so it takes precedence:
Envelope.TYPE_ACK = "ACK" and Envelope.TYPE_PONG = "PONG". Any bean named with one of these strings will replace the corresponding built-in handler. All other names add new types without affecting existing handlers.
Configuration Reference
All properties are under the notiflow.* prefix with Spring Boot's type-safe binding.
Property Reference
| Property | Type | Default | Description |
|---|---|---|---|
notiflow.web-socket.enabled | boolean | true | Enable/disable the WebSocket endpoint |
notiflow.web-socket.path | String | /notifications | WebSocket endpoint path |
notiflow.queue.ttl | Duration | 5m | How long queued notifications are kept |
notiflow.queue.max-per-user | int | 100 | Max queued notifications per user |
notiflow.delivery.ack-enabled | boolean | true | Enable ACK-based delivery confirmation |
notiflow.delivery.ack-timeout | Duration | 30s | Time to wait for client ACK |
notiflow.delivery.max-retries | int | 3 | Max delivery attempts before failure |
notiflow.delivery.retry-interval | Duration | 10s | Delay between retry attempts |
notiflow.heartbeat.enabled | boolean | true | Enable ping/pong heartbeat |
notiflow.heartbeat.ping-interval | Duration | 30s | How often the server sends PING |
notiflow.heartbeat.ping-timeout | Duration | 60s | Time without activity before closing session |
Concurrency Model
All default implementations are thread-safe:
ConcurrentHashMapfor session, topic, queue, and delivery storageCopyOnWriteArrayListfor notification queue entriesvolatilefor session activity timestampsScheduledExecutorServicewith daemon threads for background tasks
The NotificationWebSocketHandler manages two scheduled tasks:
- Heartbeat task — sends PING and closes stale sessions (runs at
pingInterval) - Retry task — processes unacknowledged notifications (runs every 10 seconds)
Both executors use daemon threads (notiflow-pool, notiflow-queue-cleanup) so they don't prevent JVM shutdown.
Requirements
| Component | Version |
|---|---|
| Java | 21+ |
| Spring Boot | 3.2+ (starter only) |
| Jakarta WebSocket | 2.2+ |
| Jackson | 2.15+ |
NotiFlow · Open Source · Built for Java
NotiFlow App — Standalone Service
Don't want to integrate a library? notiflow-app is a fully deployed notification microservice with a REST API, JWT + API key auth, PostgreSQL persistence, and Swagger UI. Call it from any backend — no Java required on the caller side.