Class JetStreamContext
-
- All Implemented Interfaces:
public final class JetStreamContextContext for JetStream operations
Provides type-safe, coroutine-friendly access to JetStream functionality including publishing, stream management, and consumer operations.
-
-
Field Summary
Fields Modifier and Type Field Description private final JetStreamManagementmanagement
-
Method Summary
Modifier and Type Method Description final JetStreamManagementgetManagement()Access to the underlying JetStream management client for administrative operations. final <T extends Any> PublishAckpublish(String subject, T message, Headers headers, PublishExpectations expectations)Publish a typed message to JetStream with optional expectationsUses Dispatchers.IO because JetStream publish operations block waiting for PublishAck from the server. final PublishAckpublishBytes(String subject, ByteArray data, Headers headers, PublishExpectations expectations)Publish raw bytes to JetStreamUses Dispatchers.IO because JetStream publish operations block waiting for PublishAck from the server. final StreamContextstream(String name, Function1<StreamConfigBuilder, Unit> block)Create or update a JetStream streamIf the stream doesn't exist, a new one is created from the configuration block. final StreamContextgetStream(String name)Get an existing stream by name final Flow<StreamContext>streams()List all streams in the JetStream accountReturns a Flow that emits StreamContext for each stream. final BooleandeleteStream(String name)Delete a stream by name (convenience method) final ConsumerContextconsumer(String stream, String name, Function1<ConsumerConfigBuilder, Unit> block)Create or update a JetStream consumerIf the consumer doesn't exist, a new one is created from the configuration block. final ConsumerContextgetConsumer(String stream, String name)Get an existing consumer by name final BooleandeleteConsumer(String stream, String name)Delete a consumer by name (convenience method) final <T extends Any> Flow<JetStreamMessage<T>>subscribe(String stream, String filterSubject, String queue, String consumerName, Function1<ConsumerConfigBuilder, Unit> config, Integer bufferSize, DecodeErrorStrategy onDecodeError)Subscribe to a JetStream stream with a push consumerCreates an ephemeral push consumer and returns a Flow of typed messages. final <T extends Any> Flow<JetStreamBatch<T>>pull(String stream, String consumer, Integer batchSize, Duration timeout, Boolean noWait, Function1<ConsumerConfigBuilder, Unit> config, DecodeErrorStrategy onDecodeError)Create a pull consumer that fetches messages in batchesCreates a durable pull consumer and returns a Flow of message batches. final KeyValueStorekeyValue(String bucket, Function1<KeyValueConfigBuilder, Unit> block)Create or access a Key-Value store bucketIf the bucket already exists, it will be returned. final KeyValueStoregetKeyValue(String bucket)Get an existing Key-Value store bucket final BooleandeleteKeyValue(String bucket)Delete a Key-Value store bucket (convenience method) final ObjectStoreobjectStore(String bucket, Function1<ObjectStoreConfigBuilder, Unit> block)Create or access an Object Store bucketIf the bucket already exists, it will be returned. final ObjectStoregetObjectStore(String bucket)Get an existing Object Store bucket final BooleandeleteObjectStore(String bucket)Delete an Object Store bucket (convenience method) -
-
Method Detail
-
getManagement
final JetStreamManagement getManagement()
Access to the underlying JetStream management client for administrative operations.
Use this for stream and consumer lifecycle management:
Creating, updating, and deleting streams
Creating, updating, and deleting consumers
Retrieving stream and consumer information
Purging streams and managing messages
For publishing and consuming messages, use the publish() and subscribe() methods on JetStreamContext directly rather than accessing the management API.
- Returns:
The JetStreamManagement client (thread-safe)
-
publish
final <T extends Any> PublishAck publish(String subject, T message, Headers headers, PublishExpectations expectations)
Publish a typed message to JetStream with optional expectations
Uses Dispatchers.IO because JetStream publish operations block waiting for PublishAck from the server. Unlike core NATS publish (which just buffers), JetStream publish is a synchronous API call that must not run on the default dispatcher.
The codec's content type is always stamped onto the outgoing headers, and headers and expectations combine freely: jnats merges the expectation headers into the caller's own (
JetStream.publish(subject, headers, body, options)).- Parameters:
subject- The subject to publish tomessage- The message to publishheaders- Optional headers to includeexpectations- Optional publish expectations for ordering and deduplication- Returns:
the server's acknowledgement, naming the stream and sequence it was stored at
-
publishBytes
final PublishAck publishBytes(String subject, ByteArray data, Headers headers, PublishExpectations expectations)
Publish raw bytes to JetStream
Uses Dispatchers.IO because JetStream publish operations block waiting for PublishAck from the server. This is a synchronous API call that must not run on the default dispatcher.
No content type is stamped - raw bytes carry no codec - but headers and expectations combine freely: jnats merges the expectation headers into the caller's own (
JetStream.publish(subject, headers, body, options)).- Parameters:
subject- The subject to publish todata- The raw data to publishheaders- Optional headers to includeexpectations- Optional publish expectations- Returns:
the server's acknowledgement, naming the stream and sequence it was stored at
-
stream
final StreamContext stream(String name, Function1<StreamConfigBuilder, Unit> block)
Create or update a JetStream stream
If the stream doesn't exist, a new one is created from the configuration block. If it already exists, only the properties the block assigns are changed; everything else keeps the value the server currently holds, including settings natsy's DSL does not model.
Example:
val streamCtx = js.stream("MY_STREAM") { subjects = listOf("orders.*", "payments.*") retentionPolicy = RetentionPolicy.LIMITS maxMessages = 1000000 storageType = StorageType.FILE replicas = 3 }- Parameters:
name- The name of the streamblock- Configuration block for the stream- Returns:
a context for the stream that is now in place
-
getStream
final StreamContext getStream(String name)
Get an existing stream by name
- Parameters:
name- The name of the stream to retrieve- Returns:
a context for the stream, or
nullif there is no stream by that name
-
streams
final Flow<StreamContext> streams()
List all streams in the JetStream account
Returns a Flow that emits StreamContext for each stream.
Example:
js.streams().collect { streamCtx -> val info = streamCtx.getInfo() println("Stream: ${info.configuration.name}") }- Returns:
Flow of StreamContext
-
deleteStream
final Boolean deleteStream(String name)
Delete a stream by name (convenience method)
- Parameters:
name- The name of the stream to delete- Returns:
whether the server reported the deletion as done
-
consumer
final ConsumerContext consumer(String stream, String name, Function1<ConsumerConfigBuilder, Unit> block)
Create or update a JetStream consumer
If the consumer doesn't exist, a new one is created from the configuration block. If it already exists, only the properties the block assigns are changed; everything else keeps the value the server currently holds, including settings natsy's DSL does not model.
For ephemeral consumers, set name to null and don't set the durable property. An auto-generated name will be used.
Example:
// Durable consumer val consumerCtx = js.consumer("MY_STREAM", "MY_CONSUMER") { durable = "MY_CONSUMER" ackPolicy = AckPolicy.EXPLICIT maxDeliver = 5 filterSubject = "orders.>" } // Ephemeral consumer val ephemeralCtx = js.consumer("MY_STREAM", null) { ackPolicy = AckPolicy.EXPLICIT filterSubject = "orders.pending" }- Parameters:
stream- The name of the streamname- Optional name for the consumer (null for ephemeral)block- Configuration block for the consumer- Returns:
a context for the consumer that is now in place
-
getConsumer
final ConsumerContext getConsumer(String stream, String name)
Get an existing consumer by name
- Parameters:
stream- The name of the streamname- The name of the consumer- Returns:
a context for the consumer, or
nullif the stream has no consumer by that name
-
deleteConsumer
final Boolean deleteConsumer(String stream, String name)
Delete a consumer by name (convenience method)
- Parameters:
stream- The name of the streamname- The name of the consumer to delete- Returns:
whether the server reported the deletion as done
-
subscribe
final <T extends Any> Flow<JetStreamMessage<T>> subscribe(String stream, String filterSubject, String queue, String consumerName, Function1<ConsumerConfigBuilder, Unit> config, Integer bufferSize, DecodeErrorStrategy onDecodeError)
Subscribe to a JetStream stream with a push consumer
Creates an ephemeral push consumer and returns a Flow of typed messages. Each message includes acknowledgment controls and metadata.
The consumer will automatically create a unique deliver subject and subscribe to it. Messages are decoded using the codec registry and wrapped in JetStreamMessage for type-safe processing with acknowledgment controls.
Flow Control: When enabled via ConsumerConfig.flowControl, the subscription binds through jnats' push API, so its PushMessageManager answers the server's flow control requests and absorbs idle heartbeats before they reach this flow. No additional code is required. Flow control and idle heartbeat cannot be combined with queue: queue members share a deliver subject, so only one of them would ever see - and answer - a flow control request.
Example:
js.subscribe<OrderEvent>( stream = "ORDERS", filterSubject = "orders.pending" ).collect { msg -> processOrder(msg.payload) msg.ack() }Queue groups (work distribution) require consumerName: members balance by subscribing to one shared consumer's deliver subject, and consumerName is what makes every member bind to that same consumer. Passing queue without it throws IllegalArgumentException - it would give each subscriber its own ephemeral consumer, and therefore a full copy of the stream rather than a share of the work.
js.subscribe<OrderEvent>( stream = "ORDERS", queue = "order-workers", consumerName = "order-worker", // shared by every member of the group filterSubject = "orders.>" ).collect { msg -> // Work is distributed across multiple subscribers in the queue group processOrder(msg.payload) msg.ack() }- Parameters:
stream- The name of the stream to subscribe tofilterSubject- Optional subject filter (e.g., "orders.queue- Optional queue group name for work distribution; requires consumerNameconsumerName- Optional durable consumer name (null for ephemeral); mandatory when queue is setconfig- Configuration block for consumer settingsbufferSize- Delivery channel capacity.onDecodeError- What to do with a message that cannot be decoded, see DecodeErrorStrategy.- Returns:
Flow of JetStreamMessage<T> with acknowledgment controls
-
pull
final <T extends Any> Flow<JetStreamBatch<T>> pull(String stream, String consumer, Integer batchSize, Duration timeout, Boolean noWait, Function1<ConsumerConfigBuilder, Unit> config, DecodeErrorStrategy onDecodeError)
Create a pull consumer that fetches messages in batches
Creates a durable pull consumer and returns a Flow of message batches. Each batch contains the requested number of messages (or fewer if not available). This is the most efficient way to consume large volumes of messages.
The consumer must be durable (have a name) since pull consumers require explicit management. Use the batch operations to efficiently acknowledge all messages at once or selectively acknowledge up to a specific message.
Example:
js.pull<OrderEvent>( stream = "ORDERS", consumer = "order-processor", batchSize = 100, timeout = 5.seconds ).collect { batch -> // Process entire batch batch.messages.forEach { msg -> processOrder(msg.payload) } // Acknowledge entire batch at once batch.ackAll() }For flow-based processing with operators:
js.pull<OrderEvent>("ORDERS", "processor", 50) .collect { batch -> batch.asFlow() .filter { it.payload.amount > 100 } .collect { msg -> processHighValueOrder(msg.payload) msg.ack() } }- Parameters:
stream- The name of the stream to pull fromconsumer- The name of the durable pull consumerbatchSize- Number of messages to fetch per batch (default: 10); rejected if the consumer caps batches below it via ConsumerConfigBuilder.maxBatchtimeout- Maximum time to wait for messages in each fetch (default: 1 second)noWait- If true, return immediately with available messages without waiting for full batch (default: false)config- Optional configuration block for consumer settings (only used if consumer doesn't exist)onDecodeError- What to do with a message that cannot be decoded, see DecodeErrorStrategy.- Returns:
Flow of JetStreamBatch<T> with batch operations
-
keyValue
final KeyValueStore keyValue(String bucket, Function1<KeyValueConfigBuilder, Unit> block)
Create or access a Key-Value store bucket
If the bucket already exists, it will be returned. If it doesn't exist, a new bucket will be created with the specified configuration.
Example:
val kv = js.keyValue("user-preferences") { description = "User preference storage" maxHistoryPerKey = 5 ttl = 24.hours storageType = StorageType.MEMORY } kv.put("user:123:theme", "dark") val theme = kv.get<String>("user:123:theme") ?: "light"- Parameters:
bucket- The name of the bucketblock- Configuration block for the bucket (optional)- Returns:
the bucket, created if it was not already there
-
getKeyValue
final KeyValueStore getKeyValue(String bucket)
Get an existing Key-Value store bucket
- Parameters:
bucket- The name of the bucket to retrieve- Returns:
the bucket, or
nullif there is no bucket by that name
-
deleteKeyValue
final Boolean deleteKeyValue(String bucket)
Delete a Key-Value store bucket (convenience method)
- Parameters:
bucket- The name of the bucket to delete- Returns:
trueonce the bucket is gone
-
objectStore
final ObjectStore objectStore(String bucket, Function1<ObjectStoreConfigBuilder, Unit> block)
Create or access an Object Store bucket
If the bucket already exists, it will be returned. If it doesn't exist, a new bucket will be created with the specified configuration.
Object stores provide efficient storage for large files and binary data with streaming support to avoid loading entire objects into memory.
Example:
val os = js.objectStore("file-storage") { description = "Application file storage" storageType = StorageType.FILE maxBucketSize = 10 * 1024 * 1024 * 1024L // 10GB replicas = 3 } // Upload a file os.putFile("backup.zip", File("/path/to/backup.zip")) // Download a file os.getFile("backup.zip", File("/path/to/restore.zip"))- Parameters:
bucket- The name of the bucketblock- Configuration block for the bucket (optional)- Returns:
the bucket, created if it was not already there
-
getObjectStore
final ObjectStore getObjectStore(String bucket)
Get an existing Object Store bucket
- Parameters:
bucket- The name of the bucket to retrieve- Returns:
the bucket, or
nullif there is no bucket by that name
-
deleteObjectStore
final Boolean deleteObjectStore(String bucket)
Delete an Object Store bucket (convenience method)
- Parameters:
bucket- The name of the bucket to delete- Returns:
trueonce the bucket is gone
-
-
-
-