nats.java

repository·main·Indexed 20 days ago

https://github.com/nats-io/nats.java

A high-performance Java client for the NATS messaging system, providing a lightweight API for Core NATS and JetStream features. It supports publishing, subscribing, request-reply, and advanced streaming capabilities, including Key Value and Object Store. The client maintains strict API parity with other official NATS clients and offers a simplified JetStream API for common tasks like message fetching and consumer iteration.

Tokens
20K
Snippets
45
Records
67
Agent score
71%

What's inside nats.java

  1. Overview of NATS Java Example Categories

    main

    The examples repository is organized into several functional categories to demonstrate different NATS capabilities:

    • Regular NATS Examples: Core messaging patterns including publishing (NatsPub), request-reply (NatsReq), synchronous/asynchronous subscription (NatsSub, NatsSubDispatch), and queue groups for load balancing (NatsSubQueue).
    • JetStream Examples: Advanced persistence and streaming features including async publishing, pull subscriptions (with batching, expiration, and fetching), push subscriptions (with durable consumers and flow control), and subject filtering.
    • JetStream Management/Admin: Examples for managing streams and consumers, and using custom account prefixes.
    • Key Value / Object Store: Demonstrations of NATS KV and Object Store capabilities.
    • Benchmarks & Stability: Tools like autobench, benchmark, and stability for performance testing and long-running reliability checks.
    • Multi-tool: jsmulti is a versatile tool for interacting with both Core NATS and JetStream subjects.
  2. How NATS reconnection and clustering work

    main

    The Java client automatically handles reconnections if the connection to the NATS server is lost.

    • Single Server: If only one server is provided, the client will continuously attempt to reconnect to that specific server.
    • Multiple Servers: If a list of servers is provided, the client will rotate through them.
    • Clustering: When NATS servers are part of a cluster, they inform the client about other available servers in the cluster. This allows a client that initially connected to one server to discover and reconnect to other members of the cluster if the initial server fails.

    To provide multiple servers for the initial connection, use the servers(String[] urls) method or call server(String url) multiple times on the Options.Builder.

    String[] serverUrls = {"nats://serverOne:4222", "nats://serverTwo:4222"};
    Options o = new Options.Builder().servers(serverUrls).build();
  3. Manage Executor lifecycles in Connection Options

    main

    When configuring Options for a NATS connection, be aware of how ExecutorService instances are handled:

    Shared Options and Executors

    An Options instance caches its resolved executors. If you reuse the same Options instance to create multiple Connection objects, those connections will share the same executors.

    Lifecycle Ownership

    • Client-managed: If you use the default executors or provide a ThreadFactory, the client creates the threads on demand and owns their lifecycle (it will shut them down).
    • User-managed: If you supply your own ExecutorService (via .executor(), .connectExecutor(), .callbackExecutor(), .readerExecutor(), or .writerExecutor()), the client uses it as-is and never shuts it down. You are responsible for managing its lifecycle.

    Critical Warning: Thread Starvation

    The general .executor() is used by the reader, the writer, and every Dispatcher as concurrent, long-lived tasks. Do not supply a single-threaded executor to the general .executor() method. Doing so will cause the reader loop to starve the writer, leading to a deadlock.

    If you need to limit threads, isolate the protocol I/O using .readerExecutor()/.writerExecutor() or .readerThreadFactory()/.writerThreadFactory() instead of restricting the general executor.

  4. Enable Advanced Request Behavior for detailed failure reasons

    main

    In version 2.26.0+, you can opt-in to advancedRequestBehavior via Options. When enabled, instead of receiving a generic timeout, you can inspect the specific reason why a request failed.

    JetStream Requests: These throw a RequestFailureException (which extends IOException). You can call .getReason(), .getConnectionStatus(), .getLastError(), and .getCause() on the exception to diagnose the failure.

    Core Requests: The Connection.request(...) method will no longer return null on failure. Instead, it returns a RequestFailureMessage (which implements the Message interface). You must check if the returned message is an instanceof RequestFailureMessage to inspect the failure details.

    Available RequestFailureReason values:

    • TIMEOUT
    • CONNECTION_CLOSING
    • NO_RESPONDERS
    • SERVER_ERROR
    • CANCELLED
    // Enable the option
    Options options = Options.builder()
        .server("nats://localhost:4222")
        .advancedRequestBehavior()
        .build();
    
    // Handling JetStream failures
    try {
        js.publish("subject", data);
    }
    catch (RequestFailureException e) {
        log.warn("publish failed: {} (status={})", e.getReason(), e.getConnectionStatus());
    }
    
    // Handling Core failures
    Message m = nc.request("subject", data, Duration.ofSeconds(2));
    if (m instanceof RequestFailureMessage) {
        RequestFailureMessage rfm = (RequestFailureMessage)m;
        log.warn("request failed: {} (status={})", rfm.getReason(), rfm.getConnectionStatus());
    }
  5. Benchmark the NATS Java client

    main

    The io.nats.examples package includes two benchmarking tools that run against an existing nats-server:

    1. io.nats.examples.benchmark.NatsBench: Runs two simple tests: a pure publisher test and a combined publish/subscribe test. Tests are executed with 1 thread/connection per publisher or subscriber.
    2. io.nats.examples.autobench.NatsAutoBench: Runs a comprehensive series of tests across various message sizes (from 0b to 8k) and patterns, including PubOnly, PubSub, PubDispatch, ReqReply, and Latency measurements.

    Note: Performance is typically limited by the processor and OS rather than memory. Even with constrained Java heap settings (e.g., -Xmx1g), performance remains comparable to unconstrained runs.

  6. Configure Ordered Push Subscriptions

    main

    An 'Ordered' Push Subscription guarantees the order of messages by having the library manage the consumer creation. It uses flow control with a default heartbeat of 5 seconds and sets the Ack Policy to AckPolicy.None (messages do not require acks).

    Restrictions for Ordered Consumers:

    • Ack Policy: Must be AckPolicy.None (or unset).
    • Queue/Deliver Group: Cannot be used.
    • Durable Name: Cannot be set.
    • Deliver Subject: Cannot be set.
    • Max Deliver: Must be 1 (or unset).
    • Idle Heartbeat: Cannot be less than 5 seconds.
    • Replicas: Must be 1 if supplied.
    • Memory Storage: Must be true if supplied.
  7. Acknowledge JetStream messages

    main

    JetStream provides several ways to acknowledge message processing:

    • Message.ack(): Standard acknowledgment.
    • Message.ackSync(Duration): Acknowledges and waits for confirmation. When used with deduplication, this provides exactly once delivery guarantees (within the deduplication window), but may impact performance.
    • Message.nak(): Negative acknowledgment; indicates processing failed and the message should be resent.
    • Message.term(): Terminates the message; it will never be sent again.
    • Message.inProgress(): Resets the redelivery timer on the server, indicating processing is still ongoing.
  8. Configure TLS Handshake First

    main

    In NATS Server 2.10.3 and later, you can enable handshake_first. When this is configured on the server, the Java client is instructed to perform the TLS handshake immediately after connecting, but before receiving the INFO protocol from the server.

    Warning: If this option is enabled in the client but the server is not configured with handshake_first, the connection will fail.

  9. Understand the difference between Core client (nats.java) and Orbit

    main

    NATS client functionality is split into two distinct layers to balance stability and rapid iteration:

    Core client (nats.java)

    This is the primary repository. It provides a direct, lightweight, and unopinionated API over Core NATS and JetStream.

    • Purpose: High-performance, protocol-level access.
    • Parity: The API surface is kept in strict parity with other official NATS clients (Go, Rust, Python, etc.) to ensure consistent behavior across languages.
    • Stability: Follows conservative semantic versioning; breaking changes are rare.

    Orbit (orbit.java)

    Orbit is a separate set of artifacts built on top of the core client.

    • Purpose: Provides higher-level, opinionated, and Java-idiomatic abstractions.
    • Flexibility: It can iterate quickly and use Java-specific patterns that don't require cross-language parity.
    • Examples: Includes KV codecs, distributed counters, and NATS contexts.

    Rule of thumb: Use the core client for thin mappings of nats-server features. Use Orbit for patterns, helpers, or abstractions layered on top of the core APIs.

  10. Configure TLS connection security

    main

    The NATS Java client supports TLS 1.2. You can establish secure connections using three primary methods depending on your security requirements and environment:

    1. Standard TLS (tls://): Use this for production environments. It requires an SSLContext to be configured. You can provide this via JVM system properties (for keystores and truststores) or by manually building an SSLContext in your code.
    2. OpenTLS (opentls://): Use this for development or behind firewalls where the client trusts the server. It uses a special SSLContext that trusts all server certificates but provides no client certificates. Note: This requires client verification to be disabled on the server.
    3. Manual SSLContext: For full control, build an SSLContext in your application and pass it to the connection options.
    SSLContext ctx = createContext();
    Options options = new Options.Builder().server(ts.getURI()).sslContext(ctx).build();
    Connection nc = Nats.connect(options);
  11. Run NATS examples with TLS

    main

    You can test secure connections using the tls and opentls schemas.

    Secure Connection (Full Client)

    To run with a full client and trust the default keystore, use System properties to point to your keystore and truststore. The repository provides sample certificates using the password password.

    Unverified Connection (Open TLS)

    To run with a completely unverified client, use the opentls:// schema.

    Server Configuration

    To test these client connections, you can run a NATS server using the provided configuration files in the test resources:

    • src/test/resources/tls.conf: Standard TLS configuration.
    • src/test/resources/tlsverify.conf: TLS configuration requiring client certificates.
    # Run with full TLS and keystores
    java -Djavax.net.ssl.keyStore=src/test/resources/keystore.jks -Djavax.net.ssl.keyStorePassword=password -Djavax.net.ssl.trustStore=src/test/resources/truststore.jks -Djavax.net.ssl.trustStorePassword=password io.nats.examples.NatsPub tls://localhost:4443 test "hello world"
    
    # Run with unverified client
    java -cp build/libs/java-nats-{major.minor.patch}-SNAPSHOT-uber.jar io.nats.examples.NatsSub opentls://localhost:4443 test 3
    
    # Run NATS server with TLS config
    nats-server --config src/test/resources/tls.conf