io_nats_client_impl - RichardHightower/jnats GitHub Wiki
MockNatsConnection
The MockNatsConnection class is a subclass of NatsConnection and is packaged in the current module with package scope. It is designed to have the necessary access for its functionality.
MessageQueueBenchmark
The MessageQueueBenchmark class is a public class used for benchmarking message queues. It is primarily used by software engineers to measure the performance of different message queue implementations. The class is responsible for setting up and executing benchmark tests on message queues, allowing developers to compare the speed and efficiency of various queueing systems.
public static void main(String[] args) throws InterruptedException {
int msgCount = 10_000_000;
NatsMessage[] msgs = new NatsMessage[msgCount];
long start, end;
System.out.printf("Running benchmarks with %s messages.\n", NumberFormat.getInstance().format(msgCount));
System.out.println("Warmed up ...");
byte[] warmBytes = "a".getBytes();
MessageQueue warm = new MessageQueue(false);
for (int j = 0; j < msgCount; j++) {
msgs[j] = new ProtocolMessage(warmBytes);
warm.push(msgs[j]);
}
System.out.println("Starting tests ...");
MessageQueue push = new MessageQueue(false);
start = System.nanoTime();
for (int i = 0; i < msgCount; i++) {
push.push(msgs[i]);
}
end = System.nanoTime();
System.out.printf("\nTotal time to perform %s push operations was %s ms, %s ns/op\n", NumberFormat.getInstance().format(msgCount), NumberFormat.getInstance().format((end - start) / 1_000_000L), NumberFormat.getInstance().format(((double) (end - start)) / ((double) (msgCount))));
System.out.printf("\tor %s op/s\n", NumberFormat.getInstance().format(1_000_000_000L * ((double) (msgCount)) / ((double) (end - start))));
start = System.nanoTime();
for (int i = 0; i < msgCount; i++) {
push.popNow();
}
end = System.nanoTime();
System.out.printf("\nTotal time to perform %s popnow operations was %s ms, %s ns/op\n", NumberFormat.getInstance().format(msgCount), NumberFormat.getInstance().format((end - start) / 1_000_000L), NumberFormat.getInstance().format(((double) (end - start)) / ((double) (msgCount))));
System.out.printf("\tor %s op/s\n", NumberFormat.getInstance().format(1_000_000_000L * ((double) (msgCount)) / ((double) (end - start))));
MessageQueue accumulateQueue = new MessageQueue(true);
for (int j = 0; j < msgCount; j++) {
msgs[j].next = null;
}
for (int i = 0; i < msgCount; i++) {
accumulateQueue.push(msgs[i]);
}
start = System.nanoTime();
while (accumulateQueue.length() > 0) {
// works for single thread, but not multi
accumulateQueue.accumulate(10_000, 100, Duration.ofMillis(500));
}
end = System.nanoTime();
System.out.printf("\nTotal time to perform accumulate %s messages was %s ms, %s ns/op\n", NumberFormat.getInstance().format(msgCount), NumberFormat.getInstance().format((end - start) / 1_000_000L), NumberFormat.getInstance().format(((double) (end - start)) / ((double) (msgCount))));
System.out.printf("\tor %s op/s\n", NumberFormat.getInstance().format(1_000_000_000L * ((double) (msgCount)) / ((double) (end - start))));
for (int j = 0; j < msgCount; j++) {
msgs[j].next = null;
}
final MessageQueue pushPopThreadQueue = new MessageQueue(false);
final Duration timeout = Duration.ofMillis(10);
final CompletableFuture<Void> go = new CompletableFuture<>();
Thread pusher = new Thread(() -> {
try {
go.get();
for (int i = 0; i < msgCount; i++) {
pushPopThreadQueue.push(msgs[i]);
}
} catch (Exception exp) {
exp.printStackTrace();
}
});
pusher.start();
Thread popper = new Thread(() -> {
try {
go.get();
for (int i = 0; i < msgCount; i++) {
pushPopThreadQueue.pop(timeout);
}
} catch (Exception exp) {
exp.printStackTrace();
}
});
popper.start();
start = System.nanoTime();
go.complete(null);
pusher.join();
popper.join();
end = System.nanoTime();
System.out.printf("\nTotal time to perform %s pushes in one thread and pop with timeout in another was %s ms, %s ns/op\n", NumberFormat.getInstance().format(msgCount), NumberFormat.getInstance().format((end - start) / 1_000_000L), NumberFormat.getInstance().format(((double) (end - start)) / ((double) (msgCount))));
System.out.printf("\tor %s op/s\n", NumberFormat.getInstance().format(1_000_000_000L * ((double) (msgCount)) / ((double) (end - start))));
final CompletableFuture<Void> go2 = new CompletableFuture<>();
for (int j = 0; j < msgCount; j++) {
msgs[j].next = null;
}
final MessageQueue pushPopNowThreadQueue = new MessageQueue(false);
pusher = new Thread(() -> {
try {
go2.get();
for (int i = 0; i < msgCount; i++) {
pushPopNowThreadQueue.push(msgs[i]);
}
} catch (Exception exp) {
exp.printStackTrace();
}
});
pusher.start();
popper = new Thread(() -> {
try {
go2.get();
for (int i = 0; i < msgCount; i++) {
pushPopNowThreadQueue.popNow();
}
} catch (Exception exp) {
exp.printStackTrace();
}
});
popper.start();
start = System.nanoTime();
go2.complete(null);
pusher.join();
popper.join();
end = System.nanoTime();
System.out.printf("\nTotal time to perform %s pushes in one thread and pop nows in another was %s ms, %s ns/op\n", NumberFormat.getInstance().format(msgCount), NumberFormat.getInstance().format((end - start) / 1_000_000L), NumberFormat.getInstance().format(((double) (end - start)) / ((double) (msgCount))));
System.out.printf("\tor %s op/s\n", NumberFormat.getInstance().format(1_000_000_000L * ((double) (msgCount)) / ((double) (end - start))));
final CompletableFuture<Void> go3 = new CompletableFuture<>();
for (int j = 0; j < msgCount; j++) {
msgs[j].next = null;
}
final MessageQueue pushAccumulateThreadQueue = new MessageQueue(true);
pusher = new Thread(() -> {
try {
go3.get();
for (int i = 0; i < msgCount; i++) {
pushAccumulateThreadQueue.push(msgs[i]);
}
} catch (Exception exp) {
exp.printStackTrace();
}
});
pusher.start();
popper = new Thread(() -> {
try {
go3.get();
int remaining = msgCount;
while (remaining > 0) {
NatsMessage cursor = pushAccumulateThreadQueue.accumulate(10_000, 100, Duration.ofMillis(500));
while (cursor != null) {
remaining--;
cursor = cursor.next;
}
}
} catch (Exception exp) {
exp.printStackTrace();
}
});
popper.start();
start = System.nanoTime();
go3.complete(null);
pusher.join();
popper.join();
end = System.nanoTime();
System.out.printf("\nTotal time to perform %s pushes in one thread and accumlates in another was %s ms, %s ns/op\n", NumberFormat.getInstance().format(msgCount), NumberFormat.getInstance().format((end - start) / 1_000_000L), NumberFormat.getInstance().format(((double) (end - start)) / ((double) (msgCount))));
System.out.printf("\tor %s op/s\n", NumberFormat.getInstance().format(1_000_000_000L * ((double) (msgCount)) / ((double) (end - start))));
}The main method in the io.nats.client.impl.MessageQueueBenchmark class performs a benchmark test on the MessageQueue class.
- It initializes a variable
msgCountwith a value of 10 million and creates an arraymsgsofNatsMessageobjects with the same size. - It prints the number of messages being used in the benchmark test.
- It prints "Warmed up ..." to indicate that the warm-up phase is starting.
- It creates a
MessageQueuecalledwarmand initializes it with 10 millionProtocolMessageobjects containing a byte array with the letter 'a'. - It prints "Starting tests ..." to indicate that the actual benchmark tests are starting.
- It creates a new
MessageQueuecalledpush. - It measures the time it takes to push all the
msgsinto thepushqueue and prints the total time and operations per second for the push operations. - It measures the time it takes to pop all the messages from the
pushqueue and prints the total time and operations per second for the pop operations. - It creates a new
MessageQueuecalledaccumulateQueue. - It pushes all the
msgsinto theaccumulateQueuequeue. - It measures the time it takes to accumulate all the messages in the
accumulateQueuequeue and prints the total time and operations per second for the accumulate operations. - It creates a new
MessageQueuecalledpushPopThreadQueue. - It initializes a
CompletableFuturecalledgo. - It creates a thread called
pusherthat pushes all themsgsinto thepushPopThreadQueuequeue. - It creates a thread called
popperthat pops messages from thepushPopThreadQueuequeue with a timeout of 10 milliseconds. - It measures the time it takes to perform the push and pop operations in separate threads and prints the total time and operations per second for these operations.
- It initializes a new
CompletableFuturecalledgo2. - It creates a new
MessageQueuecalledpushPopNowThreadQueue. - It creates a new thread called
pusherthat pushes all themsgsinto thepushPopNowThreadQueuequeue. - It creates a new thread called
popperthat pops messages from thepushPopNowThreadQueuequeue without any timeout. - It measures the time it takes to perform the push and popnow operations in separate threads and prints the total time and operations per second for these operations.
- It initializes a new
CompletableFuturecalledgo3. - It creates a new
MessageQueuecalledpushAccumulateThreadQueue. - It creates a new thread called
pusherthat pushes all themsgsinto thepushAccumulateThreadQueuequeue. - It creates a new thread called
popperthat accumulates messages from thepushAccumulateThreadQueuequeue until all messages have been accumulated. - It measures the time it takes to perform the push and accumulate operations in separate threads and prints the total time and operations per second for these operations.
The main method in the class io.nats.client.impl.MessageQueueBenchmark is used to benchmark the performance of various operations on a message queue.
The method starts by initializing an array of NatsMessage objects with a specified count of messages. It then runs a series of tests, measuring the time it takes to perform different operations on the message queue.
The method first warms up the message queue by pushing messages onto it. It then measures the time taken to perform a series of push operations on the queue. Next, it measures the time taken to perform a series of pop-now operations, which tries to pop a message from the queue with a timeout.
After that, the method initializes a new message queue and pushes messages onto it. It then measures the time taken to perform a series of accumulate operations, which continuously accumulates messages from the queue until it becomes empty.
The method then runs two threads concurrently - one pushing messages onto the queue and the other popping messages with a timeout. It measures the time taken for this scenario.
Finally, the method runs two threads - one pushing messages onto the queue and the other popping messages immediately. It measures the time taken for this scenario as well.
For each test scenario, the method outputs the total time taken for the operations, the time taken per operation, and the operations per second.
Overall, this method provides a benchmark for measuring the performance of various operations on a message queue implementation.

MessageProtocolCreationBenchmark
The MessageProtocolCreationBenchmark class is a public class used for benchmarking the creation of message protocols.
public static void main(String[] args) throws InterruptedException {
int warmup = 1_000_000;
int msgCount = 50_000_000;
System.out.printf("### Running benchmarks with %s messages.\n", NumberFormat.getInstance().format(msgCount));
for (int j = 0; j < warmup; j++) {
new NatsMessage("subject", "replyTo", EMPTY_BODY);
}
long start = System.nanoTime();
for (int j = 0; j < msgCount; j++) {
new NatsMessage("subject", "replyTo", EMPTY_BODY);
}
long end = System.nanoTime();
System.out.printf("\n### Total time to create %s non-utf8 messages for sending was %s ms\n\t%f ns/op\n\t%s op/sec\n", NumberFormat.getInstance().format(msgCount), NumberFormat.getInstance().format((end - start) / 1_000_000L), ((double) (end - start)) / ((double) (msgCount)), NumberFormat.getInstance().format(((double) (1_000_000_000L * msgCount)) / ((double) (end - start))));
start = System.nanoTime();
for (int j = 0; j < msgCount; j++) {
new NatsMessage("subject", "replyTo", EMPTY_BODY);
}
end = System.nanoTime();
System.out.printf("\n### Total time to create %s utf8 messages for sending was %s ms\n\t%f ns/op\n\t%s op/sec\n", NumberFormat.getInstance().format(msgCount), NumberFormat.getInstance().format((end - start) / 1_000_000L), ((double) (end - start)) / ((double) (msgCount)), NumberFormat.getInstance().format(((double) (1_000_000_000L * msgCount)) / ((double) (end - start))));
start = System.nanoTime();
for (int j = 0; j < msgCount; j++) {
new ProtocolMessage(EMPTY_BODY);
}
end = System.nanoTime();
System.out.printf("\n### Total time to create %s a protocol message was %s ms\n\t%f ns/op\n\t%s op/sec\n", NumberFormat.getInstance().format(msgCount), NumberFormat.getInstance().format((end - start) / 1_000_000L), ((double) (end - start)) / ((double) (msgCount)), NumberFormat.getInstance().format(((double) (1_000_000_000L * msgCount)) / ((double) (end - start))));
}The main method in the class io.nats.client.impl.MessageProtocolCreationBenchmark performs the following steps:
- Define the variables
warmupandmsgCount.warmupis set to 1,000,000 andmsgCountis set to 50,000,000 (representing the number of messages to be processed). - Print a message indicating the number of messages that will be used for the benchmark.
- Perform a warm-up phase by creating a new
NatsMessageobject 1,000,000 times using the values "subject", "replyTo", andEMPTY_BODY. This is done to warm-up the JVM and optimize any code before running the actual benchmark. - Record the start time using
System.nanoTime(). - Create a new
NatsMessageobjectmsgCounttimes using the same values as in the warm-up phase. This measures the time taken to createmsgCountnon-utf8 messages for sending. - Record the end time using
System.nanoTime(). - Print the total time taken to create
msgCountnon-utf8 messages in milliseconds, the average time taken per operation in nanoseconds, and the number of operations per second. - Repeat steps 5-7 with utf8 messages instead of non-utf8 messages.
- Repeat steps 5-7 with a
ProtocolMessageobject instead ofNatsMessageto measure the time taken to create a protocol message. - The benchmark is completed.
The main method in the MessageProtocolCreationBenchmark class is a benchmarking method. It measures the time taken to create different types of messages for sending.
Here's an overview of what the method does:
- Sets the value for
warmupandmsgCountvariables, which specify the number of warmup iterations and the total number of messages, respectively. - Prints the number of messages being benchmarked.
- Performs warmup iterations, where each iteration creates a new
NatsMessageobject with specific parameters. - Starts timing.
- Performs
msgCountiterations, where each iteration creates a newNatsMessageobject with specific parameters. - Stops timing and prints the total time taken to create the non-utf8 messages, the average time per operation, and the number of operations per second.
- Resets timing and repeats steps 4-6 for creating utf8 messages.
- Resets timing and repeats steps 4-6 for creating a protocol message.
Overall, this method helps measure the performance of creating different types of messages used in communication. The time taken for message creation, the average time per operation, and the number of operations per second are printed as benchmark results for each type of message.

NatsPackageScopeWorkarounds
The NatsPackageScopeWorkarounds class is a public class that provides workarounds for package scope limitations in the Nats package.
ParseTesting
ParseTesting is a public class that is used for testing parsing functionality. It contains various methods and properties to facilitate parsing operations.
public static void main(String[] args) throws InterruptedException {
int iterations = 1_000_000;
System.out.println("###");
System.out.printf("### Running parse tests with %s msgs.\n", NumberFormat.getInstance().format(iterations));
System.out.println("###");
runTest(iterations, "+OK");
runTest(iterations, "PONG");
runTest(iterations, "INFO " + infoJson);
runTest(iterations, "MSG longer.subject.abitlikeaninbox 22 longer.replyto.abitlikeaninbox 234");
runTest(iterations, "-ERR some error with spaces in it");
}The main method in the ParseTesting class performs the following steps:
- Declare and initialize a variable
iterationswith the value1_000_000. - Print the header information using
System.out.println. - Use
NumberFormat.getInstance().format(iterations)to format the value ofiterations. - Print a message indicating the number of iterations being tested.
- Call the
runTestmethod with the specified iterations and the string"+OK". - Call the
runTestmethod with the specified iterations and the string"PONG". - Call the
runTestmethod with the specified iterations and the string"INFO " + infoJson, whereinfoJsonis a variable that holds a JSON object. - Call the
runTestmethod with the specified iterations and the string"MSG longer.subject.abitlikeaninbox 22 longer.replyto.abitlikeaninbox 234". - Call the
runTestmethod with the specified iterations and the string"-ERR some error with spaces in it".
The runTest method is not defined in the given code snippet, so its functionality cannot be determined.
The main method in the ParseTesting class is used to run parse tests.
It first sets the number of iterations to 1 million and then prints out a header indicating the start of the parse tests, along with the number of messages involved in the test.
The runTest method is then called multiple times, passing different test strings as arguments. These test strings include various patterns such as "+OK", "PONG", "INFO" followed by infoJson, a longer subject and replyto message, and a message with an error.
Overall, this method executes parse tests with different message strings to test the parsing functionality of the software.

public static String[] splitCharBuffer(CharBuffer buffer) {
ArrayList<String> list = new ArrayList<>();
StringBuilder builder = new StringBuilder();
while (buffer.hasRemaining()) {
char c = buffer.get();
if (c == ' ') {
list.add(builder.toString());
builder = new StringBuilder();
} else {
builder.append(c);
}
}
if (builder.length() > 0) {
list.add(builder.toString());
}
return list.toArray(new String[0]);
}The splitCharBuffer method in the ParseTesting class, defined in the io.nats.client.impl package, is used to split a CharBuffer into an array of strings. Here is a step-by-step description of what the method does:
- Create an empty
ArrayListto store the individual strings extracted from theCharBuffer. - Create an empty
StringBuilderas a temporary container for each individual string. - Start a loop that continues as long as there are remaining characters in the
CharBuffer. - Retrieve the next character from the
CharBufferusing theget()method, and assign it to the variablec. - Check if the character
cis a space (' '). - If the character
cis a space, convert the contents of theStringBuilderto a string using thetoString()method, and add it to theArrayList. - Create a new empty
StringBuilderto start collecting a new string. - If the character
cis not a space, append the character to theStringBuilderusing theappend()method. - Repeat steps 4 to 8 until all characters in the
CharBufferhave been processed. - Check if the
StringBuilderstill contains characters that have not been added to theArrayList. - If so, convert the remaining characters in the
StringBuilderto a string using thetoString()method, and add it to theArrayList. - Convert the
ArrayListto an array of strings using thetoArray(T[] a)method with an empty array as the argument. - Return the resulting array of strings.
This method essentially splits the characters in the CharBuffer by spaces, creating an array of strings where each element represents a word or a group of non-space characters.
The method splitCharBuffer in class ParseTesting is used to split a CharBuffer into an array of strings based on spaces (' ') as the delimiter.
Here is a brief description of what the method does:
- Creates an empty
ArrayListto hold the individual strings. - Initializes a
StringBuilderto build each string. - Iterates through each character in the
CharBufferusing awhileloop. - Checks if the current character is a space (' ').
- If it is a space, the contents of the
StringBuilderare added to theArrayListas a string, and theStringBuilderis cleared to build a new string. - If it is not a space, the character is appended to the
StringBuilder.
- If it is a space, the contents of the
- After the loop, the contents of the
StringBuilderare added to theArrayListif it is not empty. - Converts the
ArrayListinto an array of strings using thetoArraymethod. - Returns the array of strings.

public static String grabNextWithBuilder(CharBuffer buffer) {
StringBuilder builder = new StringBuilder();
while (buffer.hasRemaining()) {
char c = buffer.get();
if (c == ' ') {
return builder.toString();
} else {
builder.append(c);
}
}
return builder.toString();
}The method grabNextWithBuilder defined in the class io.nats.client.impl.ParseTesting takes a CharBuffer named buffer as input and returns a String.
Here is a step-by-step description of what the method does:
-
Create a new
StringBuildercalledbuilderto store characters from thebuffer. -
Enter a loop that continues as long as the
bufferhas remaining characters. -
Get the next character from the
bufferand assign it to the variablec. -
Check if the character
cis equal to a space (' '). If it is, then it means the method has encountered a space and it should stop reading characters from thebuffer. In this case, the method returns the current contents of thebuilderas aString. -
If the character
cis not equal to a space, then it means the method has not encountered a space yet and it should continue reading characters from thebuffer. In this case, the charactercis appended to thebuilder. -
After appending the character
cto thebuilder, the loop continues to the next iteration to get the next character from thebuffer. -
If the loop completes without encountering a space character, it means that the entire
bufferhas been consumed. In this case, the method returns the current contents of thebuilderas aString.
This method essentially reads characters from the buffer until it encounters a space character or reaches the end of the buffer. It then returns the read characters as a String.
The grabNextWithBuilder method defined in the ParseTesting class of the io.nats.client.impl package is a static method that takes a CharBuffer as its parameter.
The purpose of this method is to extract the next string from the given CharBuffer. It does this by iterating over each character in the buffer. If a space character (' ') is encountered, the method returns the accumulated characters in the StringBuilder as a string. If any other character is encountered, it is appended to the StringBuilder.
After processing all characters in the buffer, the method returns the accumulated characters in the StringBuilder as a string, regardless of whether a space was encountered or not.

public static String grabNextWithSubsequence(CharBuffer buffer) {
if (!buffer.hasRemaining()) {
return null;
}
int start = buffer.position();
while (buffer.hasRemaining()) {
char c = buffer.get();
if (c == ' ') {
int end = buffer.position();
buffer.position(start);
//don't grab the space
CharBuffer slice = buffer.subSequence(0, end - start - 1);
buffer.position(end);
return slice.toString();
}
}
buffer.position(start);
String retVal = buffer.toString();
buffer.position(buffer.limit());
return retVal;
}The grabNextWithSubsequence method in the ParseTesting class is used to extract a substring from a given CharBuffer until the next occurrence of a space character (' '). Here is a step-by-step description of what the method does:
-
Check if the input
bufferhas any remaining characters. If it doesn't, returnnull. -
Get the current position of the
bufferand assign it to thestartvariable. This is the starting position from where the substring will be extracted. -
Enter a while loop that runs until there are no more characters remaining in the
buffer. -
Get the next character in the
bufferand assign it to the variablec. -
Check if the character
cis equal to a space (' '). If it is, it means we have found the next occurrence of a space character. -
Get the current position of the
bufferand assign it to theendvariable. This is the ending position of the substring. -
Set the position of the
bufferback to thestartposition so that we can create a subsequence from the originalbufferstarting at thestartposition. -
Create a new
CharBuffernamedsliceusing thesubSequencemethod, which extracts a subsequence of characters from thebufferstarting at thestartposition and ending atend - start - 1position. This is done to exclude the space character. -
Set the position of the
bufferto theendposition so that we can continue iterating through the remaining characters. -
Convert the
sliceCharBufferto aStringusing thetoStringmethod and assign it to theretValvariable. -
Set the position of the
bufferto its limit, which effectively marks the end of the buffer. -
Return the
retValstring, which is the extracted substring.
By following these steps, the grabNextWithSubsequence method efficiently extracts the next substring from the CharBuffer until the next space character is encountered.
The grabNextWithSubsequence method in the io.nats.client.impl.ParseTesting class is used to extract the next subsequence of characters from a given CharBuffer object.
Here's how it works:
- It starts by checking if the given
CharBufferhas any remaining characters. If not, it returnsnull. - If there are remaining characters, it captures the starting position of the buffer.
- It then iterates over the buffer, character by character, until it encounters a space character.
- Once a space is found, it captures the ending position of the subsequence and creates a new
CharBufferslice from the original buffer, excluding the space character. - Finally, it resets the position of the original buffer to the starting position, returns the string representation of the subsequence, and sets the position of the buffer to its limit.
In summary, this method extracts the next subsequence of characters from a CharBuffer until it reaches a space character, excluding the space itself.

public static CharSequence grabNextAsSubsequence(CharBuffer buffer) {
if (!buffer.hasRemaining()) {
return null;
}
int start = buffer.position();
while (buffer.hasRemaining()) {
char c = buffer.get();
if (c == ' ') {
int end = buffer.position();
buffer.position(start);
//don't grab the space
CharBuffer slice = buffer.subSequence(0, end - start - 1);
buffer.position(end);
return slice;
}
}
buffer.position(start);
CharSequence retVal = buffer.subSequence(0, buffer.remaining());
buffer.position(buffer.limit());
return retVal;
}Description: This method is used to grab the next character sequence from the input CharBuffer as a subsequence. It stops when it encounters a space character (' ') and returns the subsequence excluding the space. If the buffer is empty or contains only spaces, it returns null.
Parameters:
-
buffer: The input CharBuffer from which to grab the next subsequence.
Returns:
-
CharSequence: The grabbed subsequence excluding space, or null if the buffer is empty or contains only spaces.
Steps: The method performs the following steps:
-
Check if the buffer is empty (
buffer.hasRemaining()). If not, proceed to the next step. Otherwise, return null. -
Get the current position of the buffer (
start = buffer.position()). -
Enter a while loop, iterating until the buffer has no more remaining characters.
-
Get the next character from the buffer (
c = buffer.get()). -
Check if the character is a space (' '). If true, go to the next step. Otherwise, continue with the next iteration of the loop.
-
Get the current position of the buffer (
end = buffer.position()). -
Set the position of the buffer back to the start position (
buffer.position(start)). -
Create a new CharBuffer slice from the original buffer, starting from index 0 and ending at
end - start - 1(excluding the space). Assign it toslicevariable. -
Set the position of the buffer to the end position (
buffer.position(end)). -
Return the grabbed subsequence (
slice). -
If the loop ends without encountering a space character, set the buffer position back to the start position (
buffer.position(start)). -
Create a new CharBuffer subsequence from the original buffer, starting from index 0 and ending at the remaining characters in the buffer. Assign it to
retValvariable. -
Set the buffer position to the buffer limit (
buffer.position(buffer.limit())). -
Return the grabbed subsequence (
retVal).
The grabNextAsSubsequence method in the ParseTesting class takes a CharBuffer as input and returns the next sequence of characters in the buffer until it encounters a space character (' ').
It starts by checking if the buffer has remaining characters. If it doesn't, it returns null. Otherwise, it records the starting position of the buffer.
The method then iterates over the remaining characters in the buffer. For each character, it checks if it is a space character. If it is, it records the ending position of the buffer and creates a subsequence (excluding the space character) from the starting position to the ending position. The buffer's position is then updated to the ending position, and the subsequence is returned.
If no space character is found, the method resets the buffer's position to the starting position, and creates a subsequence from the starting position to the end of the buffer. The buffer's position is then set to its limit, and the subsequence is returned.
In summary, this method retrieves the next sequence of characters from a CharBuffer until it encounters a space character, excluding the space character from the resulting subsequence.

public static String grabNextWithCharArray(CharBuffer buffer) {
int remaining = buffer.remaining();
if (remaining == 0) {
return null;
}
int i = 0;
while (remaining > 0) {
char c = buffer.get();
if (c == ' ') {
return new String(buff, 0, i);
} else {
buff[i] = c;
i++;
}
remaining--;
}
return new String(buff, 0, i);
}The grabNextWithCharArray method in the ParseTesting class is a public static method that takes a CharBuffer as its parameter. The purpose of this method is to extract the next string from the CharBuffer.
-
buffer: ACharBufferobject that contains the characters to be processed.
- If the buffer is empty (i.e.,
remaining == 0), the method returnsnull. - If the next character in the buffer is a space character, the method returns a new
Stringobject containing the characters frombuff[0]tobuff[i-1](wherebuffis an array of characters). - Otherwise, the method continues to read characters from the buffer until a space character is encountered or the end of the buffer is reached. It then returns a new
Stringobject containing the characters frombuff[0]tobuff[i-1].
- The method starts by retrieving the remaining number of characters in the buffer using the
remaining()method of theCharBuffer. - If the remaining count is equal to 0, the method returns
null, indicating that there are no more characters to process. - If there are remaining characters, the method initializes a counter variable
ias 0 to keep track of the current position in thebuffarray (initialized elsewhere in the code and not shown). - The method enters a loop that will continue until there are no remaining characters in the buffer.
- In each iteration of the loop, the method retrieves the next character from the buffer using the
get()method of theCharBufferand assigns it to the variablec. - If the character
cis a space character, the method creates a newStringobject using the characters stored in thebuffarray from index 0 toi-1(these characters represent the parsed string), and returns it. - If the character
cis not a space, it is stored in thebuffarray at the current positioni, and theicounter is incremented by 1. - The method then decrements the
remainingcount by 1. - The loop continues until the
remainingcount reaches 0, indicating that all characters from the buffer have been processed. - If no space character was encountered in the buffer, the method creates a new
Stringobject using the characters stored in thebuffarray from index 0 toi-1and returns it.
Note: The code provided does not show the declaration and initialization of the buff array, which is assumed to be present in the class and accessible to this method.
The method grabNextWithCharArray is a public static method defined in the class io.nats.client.impl.ParseTesting. It takes a CharBuffer object as a parameter.
The purpose of this method is to extract the next word delimited by a space character (' ') from the CharBuffer and return it as a String. If there are no more characters left in the buffer, it returns null.
Internally, the method iterates over the characters in the CharBuffer using a while loop. It reads each character one by one and checks if it is a space character. If it is, it creates a new String object from the characters read so far using the new String(buff, 0, i) constructor and returns it.
If the character is not a space, it adds the character to an internal character array buff at the current position i. After each character is read, remaining is decremented and i is incremented. This process continues until either a space character is encountered or there are no more characters left in the buffer.
Finally, if the end of the buffer is reached without encountering a space character, it creates a new String object from the characters read so far using new String(buff, 0, i) and returns it.
Note: The buff array is not declared in the given code snippet, so the method assumes that it is already declared and accessible within the class scope.

public static String protocolFor(char[] chars, int length) {
if (length == 3) {
if (chars[0] == '+' && chars[1] == 'O' && chars[2] == 'K') {
return OP_OK;
} else if (chars[0] == 'M' && chars[1] == 'S' && chars[2] == 'G') {
return OP_MSG;
} else {
return null;
}
} else if (length == 4) {
// order for uniqueness
if (chars[1] == 'I' && chars[0] == 'P' && chars[2] == 'N' && chars[3] == 'G') {
return OP_PING;
} else if (chars[0] == 'P' && chars[1] == 'O' && chars[2] == 'N' && chars[3] == 'G') {
return OP_PONG;
} else if (chars[0] == '-' && chars[1] == 'E' && chars[2] == 'R' && chars[3] == 'R') {
return OP_ERR;
} else if (chars[2] == 'F' && chars[0] == 'I' && chars[1] == 'N' && chars[3] == 'O') {
return OP_INFO;
} else {
return null;
}
} else {
return null;
}
}The protocolFor method in the io.nats.client.impl.ParseTesting class is responsible for determining the protocol type based on the input chars and length.
The method accepts two parameters - chars which is a character array and length which denotes the size of the array.
If the length of the array is 3, it checks if the first character is '+', the second character is 'O', and the third character is 'K'. If it matches, it returns the value OP_OK.
Else, it checks if the first character is 'M', the second character is 'S', and the third character is 'G'. If it matches, it returns the value OP_MSG.
If none of the conditions are met, it returns null.
If the length of the array is 4, it checks for the following conditions:
- If the second character is 'I', the first character is 'P', the third character is 'N' and the fourth character is 'G', it returns the value
OP_PING. - If the first character is 'P', the second character is 'O', the third character is 'N' and the fourth character is 'G', it returns the value
OP_PONG. - If the first character is '-', the second character is 'E', the third character is 'R', and the fourth character is 'R', it returns the value
OP_ERR. - If the third character is 'F', the first character is 'I', the second character is 'N', and the fourth character is 'O', it returns the value
OP_INFO. If none of the conditions are met, it returnsnull.
If the length of the array is neither 3 nor 4, it returns null.
Overall, this method determines the protocol type based on the characters in the chars array and returns the corresponding protocol value.
The method protocolFor in the class ParseTesting is used to determine the protocol for the given array of characters (chars) and its length (length). It checks the length of the array and performs different checks based on the length to determine the protocol.
If the length is 3, it checks if the characters at indexes 0, 1, and 2 are equal to '+', 'O', and 'K' respectively. If they are, it returns the constant OP_OK. Otherwise, it checks if the characters represent the 'MSG' protocol and returns OP_MSG. If none of these conditions are met, it returns null.
If the length is 4, it performs similar checks for different protocols. For example, it checks if the characters at indexes 0, 1, 2, and 3 are equal to 'P', 'I', 'N', and 'G' respectively to determine the 'PING' protocol. It checks for other protocols like 'PONG', 'ERR', and 'INFO' in a similar manner. If none of these conditions are met, it returns null.
If the length is neither 3 nor 4, it returns null.
In summary, the method protocolFor is used to determine the protocol based on the given array of characters and its length. It returns the corresponding protocol constant or null if the protocol cannot be determined.

public static String grabProtocol(CharBuffer buffer) {
int remaining = buffer.remaining();
if (remaining == 0) {
return null;
}
int i = 0;
while (remaining > 0) {
char c = buffer.get();
if (c == ' ') {
return protocolFor(buff, i);
} else {
buff[i] = c;
i++;
}
remaining--;
}
return protocolFor(buff, i);
}The grabProtocol method takes a CharBuffer as a parameter and returns a string representing the protocol.
-
Check the number of remaining characters in the buffer using
buffer.remaining(). If there are no remaining characters (i.e.,remaining == 0), returnnull. -
Initialize a variable
iwith a value of 0. This variable will be used as an index to store characters in a temporary buffer. -
Enter a loop while there are remaining characters in the buffer (i.e.,
remaining > 0). -
Get the next character from the buffer using
buffer.get()and assign it to a variablec. -
Check if the character
cis a space character.a. If it is a space character, call a method
protocolForwith the temporary bufferbuff(not shown in the provided code) and the indexias arguments, and return the result of this method call as the protocol string.b. If it is not a space character, store the character
cin the temporary bufferbuffat indexi. -
Decrement the
remainingvariable by 1, indicating that one character has been processed. -
After exiting the loop, call the
protocolFormethod again with the temporary bufferbuffand the indexias arguments, and return the result as the protocol string.
Here is a modified version of the provided code with the missing buff variable declaration and the protocolFor method called with the correct arguments:
public static String grabProtocol(CharBuffer buffer) {
int remaining = buffer.remaining();
if (remaining == 0) {
return null;
}
char[] buff = new char[remaining]; // Temporary buffer to store characters
int i = 0;
while (remaining > 0) {
char c = buffer.get();
if (c == ' ') {
return protocolFor(buff, i);
} else {
buff[i] = c;
i++;
}
remaining--;
}
return protocolFor(buff, i);
}The grabProtocol method in the ParseTesting class, defined in the io.nats.client.impl package, takes a CharBuffer as input.
It first checks the number of remaining characters in the buffer. If it is zero, it returns null.
Otherwise, it initializes a counter variable i and enters a loop while there are remaining characters in the buffer. Inside the loop, it retrieves each character from the buffer and checks if it is a space (' '). If it is, it invokes the protocolFor method with the buff array and the current value of i as arguments, and returns the result.
If the current character is not a space, it stores it in the buff array at index i and increments i by 1.
Regardless of whether a space character is found or not, the method decrements the remaining variable by 1 after processing each character.
Finally, if the loop completes without encountering a space character, the method again invokes the protocolFor method with the buff array and the final value of i as arguments, and returns the result.
This method is expected to determine the protocol from the input CharBuffer by extracting the string until the first space (' ') character is encountered.

public static void runTest(int iterations, String serverMessage) {
Pattern space = Pattern.compile(" ");
ByteBuffer protocolBuffer = ByteBuffer.allocate(32 * 1024);
protocolBuffer.put(serverMessage.getBytes(StandardCharsets.UTF_8));
protocolBuffer.flip();
CharBuffer buffer = StandardCharsets.UTF_8.decode(protocolBuffer);
String pl = buffer.toString();
buffer.rewind();
protocolBuffer.rewind();
String[] newversion = splitCharBuffer(buffer);
String[] oldversion = space.split(pl);
buffer.rewind();
ArrayList<String> opAware = new ArrayList<>();
CharSequence s = null;
while ((s = grabNext(buffer)) != null) {
opAware.add(s.toString());
}
String[] opAwareArray = opAware.toArray(new String[0]);
System.out.printf("### Parsing server string: %s\n", serverMessage);
boolean newOk = Arrays.equals(newversion, oldversion);
System.out.println("### Old and new versions are equal: " + newOk);
if (!newOk) {
System.exit(-1);
}
boolean protoOk = Arrays.equals(opAwareArray, oldversion);
System.out.println("### Old and op-aware versions are equal: " + protoOk);
if (!protoOk) {
System.exit(-1);
}
long start = System.nanoTime();
for (int i = 0; i < iterations; i++) {
StandardCharsets.UTF_8.decode(protocolBuffer).toString();
protocolBuffer.rewind();
}
long end = System.nanoTime();
System.out.printf("### %s raw utf8 decode/sec.\n", NumberFormat.getInstance().format(1_000_000_000L * iterations / (end - start)));
start = System.nanoTime();
for (int i = 0; i < iterations; i++) {
String protocolLine = StandardCharsets.UTF_8.decode(protocolBuffer).toString();
space.split(protocolLine);
protocolBuffer.rewind();
}
end = System.nanoTime();
System.out.printf("### %s old parses/sec.\n", NumberFormat.getInstance().format(1_000_000_000L * iterations / (end - start)));
start = System.nanoTime();
for (int i = 0; i < iterations; i++) {
CharBuffer charBuffer = StandardCharsets.UTF_8.decode(protocolBuffer);
splitCharBuffer(charBuffer);
protocolBuffer.rewind();
}
end = System.nanoTime();
System.out.printf("### %s new parses/sec.\n", NumberFormat.getInstance().format(1_000_000_000L * iterations / (end - start)));
start = System.nanoTime();
for (int i = 0; i < iterations; i++) {
CharBuffer charBuffer = StandardCharsets.UTF_8.decode(protocolBuffer);
String op = grabProtocol(charBuffer);
switch(op) {
case OP_MSG:
//subject
grabNext(charBuffer);
// sid
grabNext(charBuffer);
// replyto or length
grabNext(charBuffer);
// length or null
grabNext(charBuffer);
break;
case OP_ERR:
grabTheRest(charBuffer);
break;
case OP_OK:
case OP_PING:
case OP_PONG:
case OP_INFO:
grabTheRest(charBuffer);
break;
default:
break;
}
protocolBuffer.rewind();
}
end = System.nanoTime();
System.out.printf("### %s op-aware parses/sec.\n", NumberFormat.getInstance().format(1_000_000_000L * iterations / (end - start)));
System.out.println();
}The runTest method in the io.nats.client.impl.ParseTesting class is performing the following steps:
- It receives two parameters:
iterations(the number of iterations to run the test) andserverMessage(the message to be parsed). - It creates a
Patternobject calledspaceto match a space character. - It creates a
ByteBuffercalledprotocolBufferwith a size of 32 * 1024 bytes. - It puts the bytes of the
serverMessagestring into theprotocolBufferusing the UTF-8 encoding. - It flips the
protocolBufferto prepare for reading. - It decodes the bytes in the
protocolBufferusing the UTF-8 charset to create aCharBuffercalledbuffer. - It converts the
bufferto a stringpl. - It rewinds the
bufferandprotocolBufferto prepare for reading. - It splits the
bufferinto an array of strings callednewversionusing a custom methodsplitCharBuffer. - It splits the
plstring into an array of strings calledoldversionby matching spaces. - It rewinds the
bufferand creates anArrayListcalledopAware. - It enters a loop where it grabs the next sequence of characters from the
bufferand adds it to theopAwarelist until there are no more sequences. - It converts the
opAwarelist to an array of strings calledopAwareArray. - It prints the
serverMessagein a formatted string. - It checks if
newversionandoldversionarrays are equal and assigns the result tonewOk. - It prints whether the old and new versions are equal.
- If the old and new versions are not equal, it exits the program with a status of -1.
- It checks if
opAwareArrayandoldversionarrays are equal and assigns the result toprotoOk. - It prints whether the old and op-aware versions are equal.
- If the old and op-aware versions are not equal, it exits the program with a status of -1.
- It initializes a variable
startwith the current system time in nanoseconds. - It enters a loop where it decodes the
protocolBufferusing UTF-8 encoding and converts it to a string, and then rewinds theprotocolBuffer, for each iteration. - It calculates the elapsed time in nanoseconds by subtracting
startfrom the current system time and assigns it toend. - It prints the number of raw UTF-8 decodes per second.
- It initializes
startwith the current system time in nanoseconds. - It enters a loop where it decodes the
protocolBuffer, converts it to a string, splits the resulting string using spaces, and then rewinds theprotocolBuffer, for each iteration. - It calculates the elapsed time in nanoseconds by subtracting
startfrom the current system time and assigns it toend. - It prints the number of old parses per second.
- It initializes
startwith the current system time in nanoseconds. - It enters a loop where it decodes the
protocolBufferandsplitCharBufferthe resultingCharBuffer, and then rewinds theprotocolBuffer, for each iteration. - It calculates the elapsed time in nanoseconds by subtracting
startfrom the current system time and assigns it toend. - It prints the number of new parses per second.
- It initializes
startwith the current system time in nanoseconds. - It enters a loop where it decodes the
protocolBuffer, gets the next operation from the resultingCharBuffer, and performs different actions based on the operation, and then rewinds theprotocolBuffer, for each iteration. - It calculates the elapsed time in nanoseconds by subtracting
startfrom the current system time and assigns it toend. - It prints the number of op-aware parses per second.
- It prints an empty line.
The runTest method in the ParseTesting class is a performance test method used to compare different parsing approaches for a given server message.
This method takes in two parameters: iterations (representing the number of iterations to run the test) and serverMessage (representing the message received from the server).
The method starts by initializing the necessary variables, such as space (to split strings by space), protocolBuffer (a byte buffer to store the server message), and buffer (a character buffer decoded from the byte buffer using UTF-8). It also splits the buffer and pl (the decoded server message) into arrays of strings.
Next, it creates an ArrayList called opAware and iterates over the characters in the buffer, adding them to the opAware list as separate strings. It then converts the opAware list to an array.
The method then prints some debug information about the server message and compares the old and new versions of the parsed message. If they are not equal, the method exits with an error code.
It then performs several benchmarking tests on the parsing approaches. It measures the time it takes to decode the server message multiple times using the old and new parsing methods, as well as an "op-aware" parsing method. It also measures the time it takes to grab specific protocol elements based on their operation type.
Finally, it prints the results of the benchmarking tests.
The purpose of this method is to assess the performance of different parsing approaches and determine the most efficient one for decoding and processing server messages.

StringListReader
The StringListReader class is an abstract class that extends the AbstractListReader. It provides functionality to read a list of strings.
void processItems(List items)
@Override
void processItems(List<JsonValue> items) {
for (JsonValue v : items) {
if (v.string != null) {
strings.add(v.string);
}
}
}This method is implemented in the io.nats.client.impl.StringListReader class and overrides the processItems method from its parent class.
The purpose of this method is to process a list of JsonValue items and add any strings found in the items to the strings list.
- Iterate over each
JsonValuein theitemslist - For each
JsonValue, check if thestringfield is not null - If the
stringfield is not null, add it to thestringslist
The processItems method, defined in the StringListReader class in the io.nats.client.impl package, is an overridden method that takes a List of JsonValues as input and performs the following operations:
- It iterates over each JsonValue
vin the provided list. - It checks if the
stringproperty ofvis not null. - If the
stringproperty is not null, it adds the value ofv.stringto thestringscollection.
In summary, the processItems method collects non-null string values from a list of JsonValues and adds them to the strings collection.
ListRequestEngine
The ListRequestEngine class is an extension of the ApiResponse class. It represents a request engine for listing items and provides functionalities for managing and retrieving lists of items.
byte[] internalNextJson(String fieldName, String filter) {
if (hasMore()) {
if (filter == null) {
return noFilterJson();
}
return (OFFSET_JSON_START + nextOffset() + ",\"" + fieldName + "\":\"" + filter + "\"}").getBytes(StandardCharsets.US_ASCII);
}
return null;
}
```## NatsConsumer
**NatsConsumer**
The `NatsConsumer` class is an abstract class that implements the `Consumer` interface. This class is used for consuming messages from a NATS messaging system. It provides the necessary functionality for connecting to a NATS server, subscribing to specific topics, and processing messages received from the server. Users can extend this class to create their own custom consumer implementations for handling NATS messages.
### boolean hasReachedPendingLimits()
```java
boolean hasReachedPendingLimits() {
long ml = maxMessages.get();
if (ml > 0 && getPendingMessageCount() >= ml) {
return true;
}
long bl = maxBytes.get();
return bl > 0 && getPendingByteCount() >= bl;
}The hasReachedPendingLimits() method, defined in the NatsConsumer class within the io.nats.client.impl package, is responsible for determining whether the consumer has reached its pending limits.
Here's a step-by-step breakdown of what the method does:
- Retrieve the value of the
maxMessagesvariable, which represents the maximum number of messages allowed to be pending. Store it in themlvariable. - Check if
mlis greater than 0 and if the number of pending messages, obtained from thegetPendingMessageCount()method, is greater than or equal toml. - If the above condition is true, it means that the pending message count has reached or exceeded the maximum allowed limit. In this case, the method returns
true, indicating that the pending limits have been reached. - If the above condition is false, retrieve the value of the
maxBytesvariable, which represents the maximum number of bytes allowed to be pending. Store it in theblvariable. - Check if
blis greater than 0 and if the number of pending bytes, obtained from thegetPendingByteCount()method, is greater than or equal tobl. - If the above condition is true, it means that the pending byte count has reached or exceeded the maximum allowed limit. In this case, the method returns
true, indicating that the pending limits have been reached. - If both conditions in step 3 and step 6 are false, it means that the pending limits have not been reached. In this case, the method returns
false.
The hasReachedPendingLimits() method essentially checks if either the maximum number of pending messages or bytes has been reached and returns true, otherwise it returns false.
The hasReachedPendingLimits method in the NatsConsumer class is a boolean method that determines whether the consumer has reached its pending message or byte limits.
It first retrieves the maximum number of messages (ml) using the maxMessages field. If ml is greater than 0 and the number of pending messages is equal to or exceeds ml, the method returns true.
If the number of pending messages is below ml or ml is 0, the method proceeds to check the maximum number of bytes (bl) using the maxBytes field. If bl is greater than 0 and the number of pending bytes is equal to or exceeds bl, the method returns true.
If both the maximum number of messages and maximum number of bytes are below their respective limits, the method returns false.

public CompletableFuture<Boolean> drain(Duration timeout) throws InterruptedException {
if (!this.isActive() || this.connection == null) {
throw new IllegalStateException("Consumer is closed");
}
if (isDraining()) {
return this.getDrainingFuture();
}
Instant start = Instant.now();
final CompletableFuture<Boolean> tracker = new CompletableFuture<>();
this.markDraining(tracker);
this.sendUnsubForDrain();
try {
// Flush and wait up to the timeout
this.connection.flush(timeout);
} catch (TimeoutException e) {
this.connection.processException(e);
}
this.markUnsubedForDrain();
// Wait for the timeout or the pending count to go to 0, skipped if conn is
// draining
connection.getExecutor().submit(() -> {
try {
Instant now = Instant.now();
while (timeout == null || timeout.equals(Duration.ZERO) || Duration.between(start, now).compareTo(timeout) < 0) {
if (this.isDrained()) {
break;
}
// Sleep 1 milli
Thread.sleep(1);
now = Instant.now();
}
this.cleanUpAfterDrain();
} catch (InterruptedException e) {
this.connection.processException(e);
} finally {
tracker.complete(this.isDrained());
}
});
return getDrainingFuture();
}The drain method is defined in the class io.nats.client.impl.NatsConsumer and takes a Duration parameter called timeout. It returns a CompletableFuture<Boolean>.
Here is a step-by-step description of what the drain method does based on its body:
- Check if the consumer is active and if the connection is not null. If either condition is false, throw an
IllegalStateExceptionwith the message "Consumer is closed". - Check if the consumer is already draining. If it is, return the result of
getDrainingFuture(), which is aCompletableFuture<Boolean>. - Get the current timestamp and create a new
CompletableFuture<Boolean>calledtracker. - Mark the consumer as draining using the
markDrainingmethod and pass in thetrackeras an argument. - Send an unsubscribe message for draining using the
sendUnsubForDrainmethod. - Try to flush the connection and wait up to the specified
timeout. If aTimeoutExceptionoccurs, handle it by calling theprocessExceptionmethod of the connection. - Mark the consumer as unsubscribed for draining using the
markUnsubedForDrainmethod. - Submit a task to the connection's executor to run the following code:
- Create a new timestamp called
now. - Enter a loop that continues while the
timeoutis null, equal toDuration.ZERO, or the difference betweenstartandnowis less thantimeout. - Inside the loop, check if the consumer is drained. If it is, break out of the loop.
- Sleep for 1 millisecond.
- Update the value of
nowto the current timestamp. - After the loop ends, call the
cleanUpAfterDrainmethod. - Finally, complete the
trackerwith the boolean value indicating if the consumer is drained.
- Create a new timestamp called
- Return the result of
getDrainingFuture().
Please note that this description is based on the provided code snippet, and further details about the class and its dependencies may be necessary to fully understand the functionality of the drain method.
The drain method in the NatsConsumer class is used to gracefully stop consuming messages from a NATS server. When called, it performs the following steps:
- Checks if the consumer is active and if a connection to the NATS server is established. If not, it throws an
IllegalStateException. - Checks if the consumer is already in the process of draining (i.e., stopping). If it is, it returns a future that represents the current draining state.
- Marks the consumer as draining and creates a new
CompletableFutureobject to track its draining state. - Sends an unsubscribe message to the NATS server to stop receiving new messages.
- Flushes the connection and waits for the specified timeout duration for pending messages to be sent.
- If the flush times out, it processes the timeout exception in the connection.
- Marks the consumer as unsubscribed for draining.
- Spawns a new thread that continuously checks if the draining has completed within the specified timeout.
- Within this thread, it checks if the draining has completed or if the timeout duration has passed. If so, it breaks out of the loop.
- After the loop exits, it performs necessary cleanup tasks after the draining process.
- Finally, it sets the draining future to the current draining state and returns it.
Overall, the drain method allows for controlled termination of message consumption from the NATS server, ensuring that any pending messages are sent before stopping and providing a way to track the draining state.

StringAuthHandler
The StringAuthHandler class is an implementation of the AuthHandler interface. It provides functionality for authenticating users based on a string representation of their credentials.
public byte[] sign(byte[] nonce) {
try {
NKey nkey = NKey.fromSeed(this.nkey);
byte[] sig = nkey.sign(nonce);
nkey.clear();
return sig;
} catch (Exception exp) {
throw new IllegalStateException("problem signing nonce", exp);
}
}The sign method in the StringAuthHandler class in the io.nats.client.impl package is responsible for generating a signature for a given nonce.
Here is a step-by-step description of what the method is doing:
- Convert the
nkeystring (presumably containing a seed) into anNKeyobject using thefromSeedmethod. - Generate a signature for the given
nonceby calling thesignmethod on theNKeyobject. The generated signature will be stored in a byte array. - Clear the
NKeyobject by calling theclearmethod. This is done to securely dispose of any sensitive information stored in theNKeyobject. - Return the generated signature as a byte array.
If any exceptions occur during the process, an IllegalStateException is thrown with the message "problem signing nonce" and the corresponding exception as the cause.
The method sign in the StringAuthHandler class is used to sign a given nonce using an NKey.
To do this, it first creates an instance of the NKey class using the nkey property of the StringAuthHandler object. Then, it calls the sign method of the NKey instance, passing the nonce as a parameter. The returned value is the signature of the nonce.
After the signature is obtained, the clear method is called on the NKey instance to clear any sensitive information related to the key.
Finally, the method returns the signature as a byte array.
If any exception occurs during the signing process, an IllegalStateException is thrown with an error message indicating the problem encountered.

NatsJetStreamImpl
The NatsJetStreamImpl class is an implementation of the NatsJetStreamConstants interface. It provides functionality related to the NATS JetStream feature.
// ----------------------------------------------------------------------------------------------------
// Management that is also needed by regular context // ---------------------------------------------------------------------------------------------------- ConsumerInfo _getConsumerInfo(String streamName, String consumerName) throws IOException, JetStreamApiException
// ----------------------------------------------------------------------------------------------------
// Management that is also needed by regular context
// ----------------------------------------------------------------------------------------------------
ConsumerInfo _getConsumerInfo(String streamName, String consumerName) throws IOException, JetStreamApiException {
String subj = String.format(JSAPI_CONSUMER_INFO, streamName, consumerName);
Message resp = makeRequestResponseRequired(subj, null, jso.getRequestTimeout());
return new ConsumerInfo(resp).throwOnHasError();
}The _getConsumerInfo method is defined in the NatsJetStreamImpl class in the io.nats.client.impl package. It takes two parameters: streamName and consumerName.
Below is a step-by-step description of what this method does based on its body:
- It creates a subject by formatting the
streamNameandconsumerNameusing theJSAPI_CONSUMER_INFOformat string. This subject is used to request information about a specific consumer in a specific JetStream stream.
String subj = String.format(JSAPI_CONSUMER_INFO, streamName, consumerName);- It makes a request to the NATS server and expects a response. The
makeRequestResponseRequiredmethod, which is not shown in the code snippet, is responsible for sending the request and handling the response. Thesubjparameter is used as the subject for the request, and thenullparameter indicates that the request does not include any additional payload data. Thejso.getRequestTimeout()method is used to determine the timeout for the request.
Message resp = makeRequestResponseRequired(subj, null, jso.getRequestTimeout());- It creates a
ConsumerInfoobject using the response message obtained from the previous step. TheConsumerInfoclass is responsible for parsing and representing the consumer information received from the JetStream server.
return new ConsumerInfo(resp).throwOnHasError();- It returns the
ConsumerInfoobject. Before returning, it checks if theConsumerInfoobject contains any error. If an error is detected, it throws aJetStreamApiExceptionwith details about the error.
That's the step-by-step description of the _getConsumerInfo method based on its body.
The _getConsumerInfo method in the class io.nats.client.impl.NatsJetStreamImpl is responsible for retrieving the information of a specific consumer within a given stream.
Given the streamName and consumerName as parameters, the method constructs a subject string using JSAPI_CONSUMER_INFO format and sends a request using the makeRequestResponseRequired method from the same class. It expects a response message from the NATS server.
Then, it creates a ConsumerInfo object from the response message and checks if any error occurred during the retrieval process. If an error exists, it throws a JetStreamApiException. Otherwise, it returns the retrieved ConsumerInfo.
This method is part of the management functionality required by the regular context of the NatsJetStreamImpl class.

ConsumerInfo _createConsumer(String streamName, ConsumerConfiguration config) throws IOException, JetStreamApiException
ConsumerInfo _createConsumer(String streamName, ConsumerConfiguration config) throws IOException, JetStreamApiException {
// ConsumerConfiguration validates that name and durable are the same if both are supplied.
String consumerName = config.getName();
if (consumerName != null && !consumerCreate290Available) {
throw JsConsumerCreate290NotAvailable.instance();
}
String durable = config.getDurable();
String subj;
if (consumerCreate290Available) {
if (consumerName == null) {
// if both consumerName and durable are null, generate a name
consumerName = durable == null ? generateConsumerName() : durable;
}
String fs = config.getFilterSubject();
if (fs == null || fs.equals(">")) {
subj = String.format(JSAPI_CONSUMER_CREATE_V290, streamName, consumerName);
} else {
subj = String.format(JSAPI_CONSUMER_CREATE_V290_W_FILTER, streamName, consumerName, fs);
}
} else if (durable == null) {
subj = String.format(JSAPI_CONSUMER_CREATE, streamName);
} else {
subj = String.format(JSAPI_DURABLE_CREATE, streamName, durable);
}
ConsumerCreateRequest ccr = new ConsumerCreateRequest(streamName, config);
Message resp = makeRequestResponseRequired(subj, ccr.serialize(), conn.getOptions().getConnectionTimeout());
return new ConsumerInfo(resp).throwOnHasError();
}This method is defined in the class io.nats.client.impl.NatsJetStreamImpl and is responsible for creating a JetStream consumer with the given stream name and consumer configuration.
ConsumerInfo _createConsumer(String streamName, ConsumerConfiguration config) throws IOException, JetStreamApiException-
Check if the
consumerNamein theconfigis not null and ifconsumerCreate290Availableis false.- If true, throw an instance of
JsConsumerCreate290NotAvailable, indicating that consumer creation is not available.
- If true, throw an instance of
-
Get the
durableoption from theconfig. -
Determine the subject for creating the consumer based on whether
consumerCreate290Availableis true or false. -
If
consumerCreate290Availableis true, check ifconsumerNameis null.- If both
consumerNameanddurableare null, generate a consumer name using thegenerateConsumerNamemethod.
- If both
-
Get the
filterSubjectfrom theconfig.- If
filterSubjectis null or equals to ">", format the subject usingJSAPI_CONSUMER_CREATE_V290withstreamNameandconsumerName. - Otherwise, format the subject using
JSAPI_CONSUMER_CREATE_V290_W_FILTERwithstreamName,consumerName, andfilterSubject.
- If
-
If
consumerCreate290Availableis false anddurableis null, format the subject usingJSAPI_CONSUMER_CREATEandstreamName. -
If none of the above conditions match, format the subject using
JSAPI_DURABLE_CREATEwithstreamNameanddurable. -
Create a
ConsumerCreateRequestobject withstreamNameandconfig. -
Make a request with a response required using the subject and serialized
ConsumerCreateRequest.- Use the connection timeout from the
conn.getOptions().
- Use the connection timeout from the
-
Create a new
ConsumerInfoobject with the response message and throw an exception if the response has an error. -
Return the
ConsumerInfoobject.
Note: The method throws IOException and JetStreamApiException.
The _createConsumer method is responsible for creating a consumer for a specific stream in the NATS JetStream implementation. It takes in the stream name and a ConsumerConfiguration object as parameters.
First, it validates that the consumer name is provided only if the consumerCreate290Available flag is false. If the flag is true and the consumer name is not provided, a name is generated.
Next, it determines the subject to use for creating the consumer based on the availability of the consumerCreate290Available flag and the presence of a filter subject.
Finally, it creates a ConsumerCreateRequest using the stream name and configuration, makes a request to create the consumer using the determined subject, and returns a ConsumerInfo object representing the created consumer. Any errors encountered during the creation process are thrown as JetStreamApiException.

void _createConsumerUnsubscribeOnException(String stream, ConsumerConfiguration cc, NatsJetStreamSubscription sub) throws IOException, JetStreamApiException
void _createConsumerUnsubscribeOnException(String stream, ConsumerConfiguration cc, NatsJetStreamSubscription sub) throws IOException, JetStreamApiException {
try {
ConsumerInfo ci = _createConsumer(stream, cc);
sub.setConsumerName(ci.getName());
} catch (IOException | JetStreamApiException e) {
// create consumer can fail, unsubscribe and then throw the exception to the user
if (sub.getDispatcher() == null) {
sub.unsubscribe();
} else {
sub.getDispatcher().unsubscribe(sub);
}
throw e;
}
}The _createConsumerUnsubscribeOnException method in the class NatsJetStreamImpl is responsible for creating a consumer for a specific stream using the given consumer configuration. If an exception occurs during the creation of the consumer, the method will automatically unsubscribe the subscription and then re-throw the exception to the user.
Here is a step-by-step description of what the method is doing:
- The method accepts three parameters:
stream(the name of the stream),cc(the consumer configuration), andsub(the NatsJetStreamSubscription object). - The method tries to create a consumer using the
_createConsumermethod, passing thestreamandccparameters. This method will return aConsumerInfoobject. - If the consumer creation is successful, the method sets the consumer name in the
subobject using thesetNamemethod. - If an exception occurs during the consumer creation (either an
IOExceptionor aJetStreamApiException), the catch block is executed. - Inside the catch block, the method checks if the
subobject has a dispatcher (aMessageDispatcherobject) attached to it. - If the dispatcher is not present, the method calls the
unsubscribemethod on thesubobject to unsubscribe it. - If the dispatcher is present, the method calls the
unsubscribemethod on the dispatcher, passing thesubobject as a parameter. - Finally, the method re-throws the caught exception (
e) to the user, ensuring that the calling code is aware of the failure that occurred during consumer creation.
By unsubscribing the subscription in case of an exception, the method ensures that no messages will be received by the consumer in an inconsistent state. Instead, the subscription is safely closed, and the exception is propagated to the user for appropriate error handling and reporting.
This method, _createConsumerUnsubscribeOnException, is used to create a consumer subscription for a given stream with the specified consumer configuration. It takes in three parameters: String stream, ConsumerConfiguration cc, and NatsJetStreamSubscription sub.
Inside the method, it first attempts to create the consumer using the _createConsumer method and assigns the consumer's name to the subscription. If any exception occurs during this process, the method catches it and performs the necessary cleanup.
If the subscription's dispatcher is null, it calls the unsubscribe() method to unsubscribe the subscription completely. Otherwise, it calls the unsubscribe(sub) method on the dispatcher.
Finally, regardless of whether an exception occurred or not, the method throws the caught exception to the user.

StreamInfo _getStreamInfo(String streamName, StreamInfoOptions options) throws IOException, JetStreamApiException
StreamInfo _getStreamInfo(String streamName, StreamInfoOptions options) throws IOException, JetStreamApiException {
String subj = String.format(JSAPI_STREAM_INFO, streamName);
StreamInfoReader sir = new StreamInfoReader();
while (sir.hasMore()) {
Message resp = makeRequestResponseRequired(subj, sir.nextJson(options), jso.getRequestTimeout());
sir.process(resp);
}
return cacheStreamInfo(streamName, sir.getStreamInfo());
}This method is defined in the class io.nats.client.impl.NatsJetStreamImpl. It takes in two parameters - streamName (a String) and options (a StreamInfoOptions object).
The method formats the subject using the streamName parameter and a constant JSAPI_STREAM_INFO. The formatted subject is stored in the subj variable.
A StreamInfoReader object sir is initialized. The method then enters a loop to read the StreamInfo response using the sir.hasMore() method.
Inside the loop, the method makes a request by calling the makeRequestResponseRequired method with the subj and sir.nextJson(options) as parameters. The jso.getRequestTimeout() value is passed as an argument as well.
The response received is then processed by calling the sir.process(resp) method.
After exiting the loop, the method caches the StreamInfo by calling the cacheStreamInfo method with the streamName and sir.getStreamInfo() as parameters.
Finally, it returns the cached StreamInfo as the output of the method.
Note: The method also throws two exceptions - IOException and JetStreamApiException.
The _getStreamInfo method in the NatsJetStreamImpl class is used to retrieve information about a stream in the NATS JetStream messaging system.
- The method takes two parameters:
streamName(the name of the stream to get information about) andoptions(additional options for retrieving stream information). - It constructs a subject string for sending a request to the server using the
JSAPI_STREAM_INFOformat. - It creates a
StreamInfoReaderobject to read the response messages from the server. - It then enters a loop to process all the response messages and extract the stream information using the
sirobject. - Finally, it caches the retrieved stream information using the
cacheStreamInfomethod and returns the cached information.
This method can throw an IOException if there is an error during the request-response process, and a JetStreamApiException if there is an error related to the JetStream API.

List<String> _getStreamNames(String subjectFilter) throws IOException, JetStreamApiException {
StreamNamesReader snr = new StreamNamesReader();
while (snr.hasMore()) {
Message resp = makeRequestResponseRequired(JSAPI_STREAM_NAMES, snr.nextJson(subjectFilter), jso.getRequestTimeout());
snr.process(resp);
}
return snr.getStrings();
}This method is defined in the NatsJetStreamImpl class in the io.nats.client.impl package. It retrieves a list of stream names based on a subject filter.
-
subjectFilter(Type: String): It is the filter used to match the stream names.
-
IOException: This exception is thrown if there is an error while performing IO operations. -
JetStreamApiException: This exception is thrown if there is an error related to JetStream API.
- Initialize a
StreamNamesReaderobject namedsnr. - Enter a loop that continues while
snrhas more elements. - Inside the loop, perform the following steps:
- Create a
Messageobjectrespby making a request with theJSAPI_STREAM_NAMESidentifier, the nextJson obtained fromsnrusing thesubjectFilter, and the request timeout from thejsoobject. - Process the response
respusing thesnr.processmethod.
- Create a
- After the loop ends, return the strings obtained from the
snrobject using thegetStringsmethod.
Please note that more implementation details might be present for some methods or variables used in this method, but they are not mentioned in the provided BODY.
The _getStreamNames method is a part of the NatsJetStreamImpl class in the io.nats.client.impl package. This method takes a subjectFilter as input and returns a list of stream names that match the filter.
The method starts by creating a StreamNamesReader object. It then enters a loop that continues as long as there are more stream names to process.
Within the loop, the method makes a request to retrieve the stream names by calling the makeRequestResponseRequired method. This method requires a response and throws an exception if it does not receive one. The request is made with the JSAPI_STREAM_NAMES parameter and the next JSON object from the StreamNamesReader which includes the subjectFilter and the request timeout from the jso object.
The response is then processed by calling the snr.process method, which updates the StreamNamesReader object with the names retrieved from the response.
Once there are no more stream names to process, the method returns the list of stream names by calling the snr.getStrings method.
This method may throw an IOException or JetStreamApiException, which should be handled by the calling code.

// ----------------------------------------------------------------------------------------------------
// General Utils // ---------------------------------------------------------------------------------------------------- ConsumerInfo lookupConsumerInfo(String streamName, String consumerName) throws IOException, JetStreamApiException
// ----------------------------------------------------------------------------------------------------
// General Utils
// ----------------------------------------------------------------------------------------------------
ConsumerInfo lookupConsumerInfo(String streamName, String consumerName) throws IOException, JetStreamApiException {
try {
return _getConsumerInfo(streamName, consumerName);
} catch (JetStreamApiException e) {
// the right side of this condition... ( starting here \/ ) is for backward compatibility with server versions that did not provide api error codes
if (e.getApiErrorCode() == JS_CONSUMER_NOT_FOUND_ERR || (e.getErrorCode() == 404 && e.getErrorDescription().contains("consumer"))) {
return null;
}
throw e;
}
}The lookupConsumerInfo method in the NatsJetStreamImpl class is used to retrieve information about a specific consumer from a given stream.
Here is a step-by-step description of the method's functionality:
-
The method takes two parameters:
streamName(the name of the stream) andconsumerName(the name of the consumer). -
The method tries to retrieve the consumer information by calling the
_getConsumerInfomethod with the providedstreamNameandconsumerName. -
If the
_getConsumerInfomethod throws aJetStreamApiException, the method checks the exception'sapiErrorCodeanderrorCode. -
If the
apiErrorCodematches theJS_CONSUMER_NOT_FOUND_ERRconstant or theerrorCodeis 404 and the error description contains the string "consumer", this means that the consumer was not found. In this case, the method returns null. -
If the exception does not match the above conditions, it is re-thrown to be handled by the calling code.
Note: The method assumes that the _getConsumerInfo method has the necessary logic to retrieve the consumer information based on the provided stream and consumer names.
The lookupConsumerInfo method is used to retrieve information about a specific consumer in a JetStream stream. It takes in the streamName and consumerName as parameters and returns a ConsumerInfo object.
Internally, it calls the _getConsumerInfo method to retrieve the consumer information. If the consumer is not found, it handles backward compatibility by checking the error code and error description provided by the JetStream API. If the error code indicates a consumer not found error or the error description contains "consumer", the method returns null. Otherwise, it rethrows the JetStreamApiException for other types of errors.

// ----------------------------------------------------------------------------------------------------
// Request Utils // ---------------------------------------------------------------------------------------------------- Message makeRequestResponseRequired(String subject, byte[] bytes, Duration timeout) throws IOException
// ----------------------------------------------------------------------------------------------------
// Request Utils
// ----------------------------------------------------------------------------------------------------
Message makeRequestResponseRequired(String subject, byte[] bytes, Duration timeout) throws IOException {
try {
return responseRequired(conn.request(prependPrefix(subject), bytes, timeout));
} catch (InterruptedException e) {
throw new IOException(e);
}
}This method is used to send a request with a required response using the NATS messaging system. It takes in a subject, a byte array, and a timeout duration as inputs and returns a Message object.
- Prepend the subject with the configured prefix using the
prependPrefixmethod. - Send the request using the NATS connection object (
conn) by calling therequestmethod with the prepended subject, the byte array, and the timeout duration. - Call the
responseRequiredmethod to handle the response received from the request. - If the request is interrupted and an
InterruptedExceptionis thrown, wrap it in anIOExceptionand throw it.
The makeRequestResponseRequired method is a utility method that is used to send a request message to a NATS server and waits for a response.
It takes three parameters:
-
subject: The subject of the message. -
bytes: The message payload as a byte array. -
timeout: The duration for which the method will wait for a response before throwing an exception.
Internally, the method makes use of the conn.request method to send the request message to the NATS server. It prepends a prefix to the subject before sending the request.
If a response is received within the specified timeout duration, the method returns the received Message object. Otherwise, it throws an IOException.
Overall, makeRequestResponseRequired provides a convenient way to send request messages and handle the corresponding responses.

Message makeInternalRequestResponseRequired(String subject, Headers headers, byte[] data, Duration timeout, CancelAction cancelAction) throws IOException
Message makeInternalRequestResponseRequired(String subject, Headers headers, byte[] data, Duration timeout, CancelAction cancelAction) throws IOException {
try {
return responseRequired(conn.requestInternal(subject, headers, data, timeout, cancelAction));
} catch (InterruptedException e) {
throw new IOException(e);
}
}The makeInternalRequestResponseRequired method is defined in the NatsJetStreamImpl class in the io.nats.client.impl package. The purpose of this method is to send an internal NATS request with the expectation of receiving a response.
Here is a step-by-step description of what this method is doing:
-
The method takes the following parameters:
-
subject(String): The subject of the NATS request. -
headers(Headers): The headers to be included in the request. -
data(byte[]): The payload data of the request. -
timeout(Duration): The maximum duration to wait for a response. -
cancelAction(CancelAction): An action to be executed if the request is cancelled.
-
-
The method tries to make the internal NATS request by calling the
requestInternalmethod on the connection (conn) object. This method returns aMessageobject representing the response.- The
requestInternalmethod is passed thesubject,headers,data,timeout, andcancelActionparameters. - The method might throw an interrupted exception, so it is surrounded by a try-catch block.
- The
-
If the
requestInternalmethod call is successful, the method calls theresponseRequiredmethod to process the received response.- The
responseRequiredmethod is passed theMessageobject representing the response. - The result of this method call is returned by the
makeInternalRequestResponseRequiredmethod.
- The
-
If an
InterruptedExceptionis caught during the execution of thetryblock, it is wrapped in anIOExceptionand thrown.
In summary, the makeInternalRequestResponseRequired method sends an internal NATS request with the specified subject, headers, and payload data. It waits for a response up to the specified timeout duration, and then processes the received response before returning it.
The makeInternalRequestResponseRequired method is a part of the NatsJetStreamImpl class in the io.nats.client.impl package.
This method is responsible for making an internal request to the NATS server and requiring a response. It takes in the parameters subject (the subject of the message), headers (optional headers for the message), data (the payload of the message), timeout (the maximum time to wait for a response), and cancelAction (an action to execute if the request is cancelled).
Internally, this method calls the requestInternal method of the conn object (an instance of Connection) to make the request and waits for the response. It then returns the response as a Message object.
If the method is interrupted while waiting for the response, it throws an IOException.

Message responseRequired(Message respMessage) throws IOException {
if (respMessage == null) {
throw new IOException("Timeout or no response waiting for NATS JetStream server");
}
return respMessage;
}The responseRequired method defined in the NatsJetStreamImpl class is responsible for handling the response message received from the NATS JetStream server. Here is a step-by-step description of what the method does based on its body:
- The method takes a parameter
respMessage, which is the response message received from the server. - If the
respMessageisnull, it means that the method has either timed out waiting for a response or there was no response from the NATS JetStream server. - In this case, the method throws an
IOExceptionwith the message "Timeout or no response waiting for NATS JetStream server" to indicate the error condition. - If the
respMessageis notnull, it means that a response message was received from the server. - In this case, the method simply returns the
respMessageas-is.
Overall, the responseRequired method allows you to handle the response message received from the NATS JetStream server, throwing an exception if no response was received or returning the response message if it was received successfully.
The responseRequired method is a part of the NatsJetStreamImpl class and it takes a Message object as input parameter and returns a Message object.
It checks if the input respMessage is null and throws an IOException with a message indicating a timeout or no response from the NATS JetStream server.
If the respMessage is not null, it simply returns the respMessage.
This method is used to verify if a response is required from the NATS JetStream server and handles the logic for handling timeouts or missing responses.

NatsJetStream
The NatsJetStream class is a subclass of NatsJetStreamImpl and implements the JetStream interface. It provides functionality for working with JetStream in NATS, a high-performance messaging system.
// ----------------------------------------------------------------------------------------------------
// Publish // ---------------------------------------------------------------------------------------------------- /** *
// ----------------------------------------------------------------------------------------------------
// Publish
// ----------------------------------------------------------------------------------------------------
/**
* {@inheritDoc}
*/
@Override
public PublishAck publish(String subject, byte[] body) throws IOException, JetStreamApiException {
return publishSyncInternal(subject, null, body, null);
}The publish method defined in the NatsJetStream class is responsible for publishing a message to a subject in the NATS server using the NATS JetStream API.
Here is a step-by-step description of what the method is doing based on its body:
-
The method signature indicates that it returns a
PublishAckobject and can potentially throwIOExceptionorJetStreamApiException. -
The method overrides the
publishmethod declared in a super class. -
The
subjectandbodyparameters are passed to the method. Thesubjectparameter specifies the subject to which the message will be published, and thebodyparameter is the byte array representing the content of the message. -
The method calls the private method
publishSyncInternalwith thesubject,nullas thereplyToparameter,body, andnullas theheadersparameter. -
The
publishSyncInternalmethod is responsible for executing the actual publish operation synchronously. -
The
PublishAckobject returned bypublishSyncInternalis then returned by thepublishmethod. -
If an
IOExceptionorJetStreamApiExceptionoccurs during the execution, it is thrown by the method.
This method provides a convenient way to publish a message to a subject in NATS JetStream without specifying a reply subject or any headers.
The publish method in the NatsJetStream class is responsible for publishing a message to a specified subject in the NATS messaging system.
It takes two parameters: subject (which represents the subject/topic to publish the message to) and body (which is the actual content of the message in the form of a byte array).
Internally, it calls the publishSyncInternal method to publish the message synchronously, passing along the subject and body parameters. It also provides null values for two optional parameters: options and messageId.
The publish method then returns a PublishAck object, which represents an acknowledgment of the message publishing process.
In case of any exceptions or errors during the publishing process, such as IO errors or JetStream API exceptions, the method throws IOException or JetStreamApiException respectively, allowing the caller to handle the errors appropriately.

private PublishAck publishSyncInternal(String subject, Headers headers, byte[] data, PublishOptions options) throws IOException, JetStreamApiException
private PublishAck publishSyncInternal(String subject, Headers headers, byte[] data, PublishOptions options) throws IOException, JetStreamApiException {
Headers merged = mergePublishOptions(headers, options);
if (jso.isPublishNoAck()) {
conn.publishInternal(subject, null, merged, data);
return null;
}
Duration timeout = options == null ? jso.getRequestTimeout() : options.getStreamTimeout();
Message resp = makeInternalRequestResponseRequired(subject, merged, data, timeout, CancelAction.COMPLETE);
return processPublishResponse(resp, options);
}The publishSyncInternal method is a function defined in the NatsJetStream class, located in the io.nats.client.impl package.
Here is a step-by-step description of what the method does:
- The method accepts four parameters:
subject(the subject to publish the message to),headers(the headers for the message),data(the message data as a byte array), andoptions(additional options for publishing). - The
mergePublishOptionsmethod is called to merge theheadersandoptionsparameters into a singleHeadersobject namedmerged. - The method then checks if the
PublishOptionsobject has thepublishNoAckflag set. If it does, it calls thepublishInternalmethod on theconnobject (which represents the NATS connection) to publish the message without waiting for an acknowledgement. Thesubject,nullfor reply subject,mergedfor headers, anddataare passed as parameters to thepublishInternalmethod. The method then returnsnullsince no acknowledgement is expected. - If the
publishNoAckflag is not set, the method proceeds to get the request timeout value from theoptionsobject (or uses the default request timeout fromjsoifoptionsis null) and assigns it to thetimeoutvariable of typeDuration. - The
makeInternalRequestResponseRequiredmethod is called with thesubject,mergedheaders,data,timeout, andCancelAction.COMPLETEparameters. This method is responsible for making the internal request and waiting for a response. It returns aMessageobject namedrespwhich represents the response received. - Finally, the
processPublishResponsemethod is called, passing therespobject and theoptionsobject as parameters. This method is responsible for processing the publish response, including handling any errors, and returns aPublishAckobject representing the acknowledgement of the publish operation. ThePublishAckobject is then returned by thepublishSyncInternalmethod.
The method publishSyncInternal is responsible for publishing a message synchronously to NATS JetStream.
It takes in the following parameters:
-
subject(String): The subject/topic to publish the message on. -
headers(Headers): The headers to be included with the message. -
data(byte[]): The actual data/message to be published. -
options(PublishOptions): Additional options for publishing.
First, the method merges the headers and options into a new header object called merged.
If the PublishOptions object's isPublishNoAck() method returns true, it means that the message should not expect an acknowledgement upon successful publishing. In this case, the publishInternal method is called on the conn object (which represents the NATS connection) to publish the message with the subject, null reply subject, merged headers, and data. The method then returns null.
If isPublishNoAck() returns false, it means that an acknowledgement is expected. The method then determines the timeout duration to wait for the response by checking the options object. If options is null, it uses the default request timeout from the jso (JetStreamOptions) object. Otherwise, it uses the StreamTimeout from the options object.
The makeInternalRequestResponseRequired method is called to make an internal request and wait for a response. It includes the subject, merged headers, data, timeout, and CancelAction.COMPLETE (which means the request is not cancellable). This method returns a Message object as the response.
Finally, the method processPublishResponse is called to process the resp (response) and return a PublishAck object, based on the provided options.
The method can throw IOException and JetStreamApiException, which indicates potential issues with the I/O operations or JetStream API calls.

private CompletableFuture publishAsyncInternal(String subject, Headers headers, byte[] data, PublishOptions options, Duration knownTimeout)
private CompletableFuture<PublishAck> publishAsyncInternal(String subject, Headers headers, byte[] data, PublishOptions options, Duration knownTimeout) {
Headers merged = mergePublishOptions(headers, options);
if (jso.isPublishNoAck()) {
conn.publishInternal(subject, null, merged, data);
return null;
}
CompletableFuture<Message> future = conn.requestFutureInternal(subject, merged, data, knownTimeout, CancelAction.COMPLETE);
return future.thenCompose(resp -> {
try {
responseRequired(resp);
return CompletableFuture.completedFuture(processPublishResponse(resp, options));
} catch (IOException | JetStreamApiException e) {
throw new RuntimeException(e);
}
});
}This method is defined in the class io.nats.client.impl.NatsJetStream and has the following signature:
private CompletableFuture<PublishAck> publishAsyncInternal(String subject, Headers headers, byte[] data, PublishOptions options, Duration knownTimeout)The publishAsyncInternal method is responsible for asynchronously publishing a message to Nats JetStream.
- Merge the
headersandoptionsto create a newHeadersobject namedmerged. - Check if the
jso.isPublishNoAck()flag is set. If it is, perform the following steps:- Call the
conn.publishInternalmethod to publish the message without acknowledgments. Pass thesubject,nullfor the reply subject,mergedheaders, and the messagedataas arguments. - Return
nullindicating that no acknowledgement is expected.
- Call the
- If the
jso.isPublishNoAck()flag is not set, proceed to the next step. - Call the
conn.requestFutureInternalmethod to send a request message and get aCompletableFuture<Message>object namedfuture. Pass thesubject,mergedheaders,data,knownTimeout, andCancelAction.COMPLETEas arguments. - Use the
future.thenComposemethod to chain further processing after the response is received. Perform the following steps:- Inside the
thenComposecallback function, check for any exceptions that occurred during the response processing. If an exception is caught, throw aRuntimeExceptionwith the caught exception as its cause. - If no exceptions occurred, call the
responseRequiredmethod to validate the response. - Call the
processPublishResponsemethod to process the publish response and obtain aPublishAckobject. Pass theresp(response) andoptionsas arguments. - Wrap the
PublishAckobject in a completedCompletableFutureusingCompletableFuture.completedFuture.
- Inside the
- Return the
CompletableFuture<PublishAck>object obtained from step 5.
Note: The method assumes that the necessary imports and declarations are present in the class.
The publishAsyncInternal method is used to asynchronously publish a message to a subject in NATS JetStream.
Here's a breakdown of what this method does:
-
It takes several parameters:
subject(the subject to publish to),headers(optional headers of the message),data(the message payload as a byte array),options(optional publish options), andknownTimeout(timeout duration). -
It merges the
headersandoptionsinto a newHeadersobject calledmerged, using themergePublishOptionsmethod. -
If the publish option
isPublishNoAckis true, it calls thepublishInternalmethod on the connection object (conn) to publish the message without waiting for an acknowledgement. It then returns null. -
If the publish option
isPublishNoAckis false, it creates aCompletableFuturecalledfutureby calling therequestFutureInternalmethod on the connection object (conn). This sends the message to the subject and expects a response from the server within the specifiedknownTimeout. -
It then uses the
thenComposemethod on thefutureto handle the response from the server. This is where the logic for processing the response and handling any errors is implemented. -
Inside the
thenComposeblock, it checks if a response is required (using theresponseRequiredmethod), and if so, it processes the response using theprocessPublishResponsemethod and returns a completedCompletableFuturewith the result. -
If any
IOExceptionorJetStreamApiExceptionis caught during the processing of the response, it throws aRuntimeException.
In summary, the publishAsyncInternal method asynchronously publishes a message to a subject in NATS JetStream and handles the response from the server based on the specified publish options and timeout.

private PublishAck processPublishResponse(Message resp, PublishOptions options) throws IOException, JetStreamApiException
private PublishAck processPublishResponse(Message resp, PublishOptions options) throws IOException, JetStreamApiException {
if (resp.isStatusMessage()) {
throw new IOException("Error Publishing: " + resp.getStatus().getMessageWithCode());
}
PublishAck ack = new PublishAck(resp);
String ackStream = ack.getStream();
String pubStream = options == null ? null : options.getStream();
// stream specified in options but different from ack should not happen but...
if (pubStream != null && !pubStream.equals(ackStream)) {
throw new IOException("Expected ack from stream " + pubStream + ", received from: " + ackStream);
}
return ack;
}The processPublishResponse method in the NatsJetStream class is responsible for processing the response received after publishing a message. Here is a step-by-step description of what the method does:
- The method takes two parameters: a
Messageobject representing the response received and aPublishOptionsobject containing the options used for publishing the message. - The method first checks if the response is a status message. If it is, an
IOExceptionis thrown with an error message including the status message code and description. - If the response is not a status message, the method proceeds to create a new
PublishAckobject using the response. ThisPublishAckobject represents the acknowledgment received for the published message. - The method then retrieves the stream name from the
PublishAckobject and assigns it to the variableackStream. - Next, the method checks if the
PublishOptionsobject is null. If it is, the variablepubStreamis set to null. - If the
PublishOptionsobject is not null, the method retrieves the stream name from thePublishOptionsobject and assigns it to the variablepubStream. - The method then checks if the
pubStreamis not null and if it is different from theackStream. If they are different, anIOExceptionis thrown with an error message indicating the expected stream from thepubStreamvariable and the actual stream received from theackStreamvariable. - Finally, if none of the above conditions are met, the method returns the
PublishAckobject.
In summary, the processPublishResponse method handles the response received after publishing a message by checking for status messages, creating a PublishAck object, and performing stream comparison checks before returning the PublishAck object.
The processPublishResponse method in the NatsJetStream class is responsible for processing the response received after publishing a message.
The method takes two parameters - resp (representing the response message) and options (representing the options used for publishing). The method returns a PublishAck object.
If the response message indicates an error (status message), an IOException is thrown with the corresponding error message.
The method creates a PublishAck object using the response message, which represents the acknowledgment received for the published message.
It then checks if the stream specified in the options is different from the stream mentioned in the acknowledgment. If there is a mismatch, an IOException is thrown.
Finally, the method returns the PublishAck object.
Overall, this method handles error cases during publishing and ensures that the received acknowledgment matches the specified stream.

private Headers mergePublishOptions(Headers headers, PublishOptions opts) {
// never touch the user's original headers
Headers merged = headers == null ? null : new Headers(headers);
if (opts != null) {
merged = mergeNum(merged, EXPECTED_LAST_SEQ_HDR, opts.getExpectedLastSequence());
merged = mergeNum(merged, EXPECTED_LAST_SUB_SEQ_HDR, opts.getExpectedLastSubjectSequence());
merged = mergeString(merged, EXPECTED_LAST_MSG_ID_HDR, opts.getExpectedLastMsgId());
merged = mergeString(merged, EXPECTED_STREAM_HDR, opts.getExpectedStream());
merged = mergeString(merged, MSG_ID_HDR, opts.getMessageId());
}
return merged;
}The mergePublishOptions method in the NatsJetStream class is used to merge the provided headers and PublishOptions object.
Here is a step-by-step description of what the method does:
-
It takes two parameters:
headersandoptsof typesHeadersandPublishOptionsrespectively. -
It creates a new
Headersobject calledmergedby cloning the originalheadersprovided as a parameter. Ifheadersisnull,mergedwill also benull. -
It checks if the
optsobject is notnull. -
It calls the
mergeNummethod to merge theEXPECTED_LAST_SEQ_HDR(a constant value) header intomergedwith the value retrieved fromopts.getExpectedLastSequence(). -
It calls the
mergeNummethod to merge theEXPECTED_LAST_SUB_SEQ_HDR(a constant value) header intomergedwith the value retrieved fromopts.getExpectedLastSubjectSequence(). -
It calls the
mergeStringmethod to merge theEXPECTED_LAST_MSG_ID_HDR(a constant value) header intomergedwith the value retrieved fromopts.getExpectedLastMsgId(). -
It calls the
mergeStringmethod to merge theEXPECTED_STREAM_HDR(a constant value) header intomergedwith the value retrieved fromopts.getExpectedStream(). -
It calls the
mergeStringmethod to merge theMSG_ID_HDR(a constant value) header intomergedwith the value retrieved fromopts.getMessageId(). -
It returns the
mergedHeadersobject.
Note: The mergeNum and mergeString methods are not defined in the provided code snippet. They are assumed to be helper methods that merge the provided key-value pair into the Headers object.
The method mergePublishOptions in the NatsJetStream class merges the given headers with the opts (PublishOptions) to create a new set of merged headers.
The original headers are never modified to ensure they remain intact. If headers is null, the merged headers will be null as well.
The method then proceeds to merge specific elements of opts into the merged headers using helper methods like mergeNum and mergeString. These elements include expectedLastSequence, expectedLastSubjectSequence, expectedLastMsgId, expectedStream, and messageId.
Finally, the merged headers are returned as the result of the method.

private Headers _mergeNum(Headers h, String key, String value) {
if (h == null) {
h = new Headers();
}
return h.add(key, value);
}The _mergeNum method is a private method defined in the io.nats.client.impl.NatsJetStream class. This method takes three parameters:
-
Headers h: An instance of theHeadersclass, which represents the headers for a NatsJetStream request. -
String key: The key of the header to be added or modified. -
String value: The value of the header to be added or modified.
The purpose of the _mergeNum method is to merge a new header or modify an existing header in the Headers object. Here is a step-by-step description of what the method does:
- Check if the
hparameter isnull. - If
hisnull, create a new instance of theHeadersclass and assign it toh. - Add or modify a header in the
Headersobject identified by thekeyparameter and set its value to thevalueparameter. - Return the modified
Headersobject.
The _mergeNum method ensures that a new header is added or an existing header is modified in the Headers object, which is then returned.
The _mergeNum method is a private method defined in the io.nats.client.impl.NatsJetStream class. This method takes three parameters: h, key, and value.
The purpose of this method is to merge the provided key-value pair into the given Headers object h. If the h object is null, a new Headers object is created. The method then adds the key-value pair to the Headers object using the add method.
Overall, the _mergeNum method ensures that the provided key-value pair is included in the Headers object, even if it is initially null, and returns the updated Headers object.

JetStreamSubscription createSubscription(String subject, String queueName, NatsDispatcher dispatcher, MessageHandler userHandler, boolean isAutoAck, PushSubscribeOptions pushSubscribeOptions, PullSubscribeOptions pullSubscribeOptions) throws IOException, JetStreamApiException
JetStreamSubscription createSubscription(String subject, String queueName, NatsDispatcher dispatcher, MessageHandler userHandler, boolean isAutoAck, PushSubscribeOptions pushSubscribeOptions, PullSubscribeOptions pullSubscribeOptions) throws IOException, JetStreamApiException {
// 1. Prepare for all the validation
boolean isPullMode = pullSubscribeOptions != null;
SubscribeOptions so;
String stream;
String qgroup;
ConsumerConfiguration userCC;
if (isPullMode) {
// options must have already been checked to be non-null
so = pullSubscribeOptions;
stream = pullSubscribeOptions.getStream();
userCC = so.getConsumerConfiguration();
// just to make compiler happy both paths set variable
qgroup = null;
validateNotSupplied(userCC.getDeliverGroup(), JsSubPullCantHaveDeliverGroup);
validateNotSupplied(userCC.getDeliverSubject(), JsSubPullCantHaveDeliverSubject);
} else {
so = pushSubscribeOptions == null ? DEFAULT_PUSH_OPTS : pushSubscribeOptions;
// might be null, that's ok (see directBind)
stream = so.getStream();
userCC = so.getConsumerConfiguration();
if (userCC.maxPullWaitingWasSet()) {
throw JsSubPushCantHaveMaxPullWaiting.instance();
}
if (userCC.maxBatchWasSet()) {
throw JsSubPushCantHaveMaxBatch.instance();
}
if (userCC.maxBytesWasSet()) {
throw JsSubPushCantHaveMaxBytes.instance();
}
// figure out the queue name
qgroup = validateMustMatchIfBothSupplied(userCC.getDeliverGroup(), queueName, JsSubQueueDeliverGroupMismatch);
if (so.isOrdered() && qgroup != null) {
throw JsSubOrderedNotAllowOnQueues.instance();
}
if (dispatcher != null && (so.getPendingMessageLimit() != Consumer.DEFAULT_MAX_MESSAGES || so.getPendingByteLimit() != Consumer.DEFAULT_MAX_BYTES)) {
throw JsSubPushAsyncCantSetPending.instance();
}
}
// 2A. Flow Control / heartbeat not always valid
if (userCC.getIdleHeartbeat() != null && userCC.getIdleHeartbeat().toMillis() > 0) {
if (isPullMode) {
throw JsSubFcHbNotValidPull.instance();
}
if (qgroup != null) {
throw JsSubFcHbNotValidQueue.instance();
}
}
// 2B. Did they tell me what stream? No? look it up.
final String fnlStream;
if (stream == null) {
fnlStream = lookupStreamBySubject(subject);
if (fnlStream == null) {
throw JsSubNoMatchingStreamForSubject.instance();
}
} else {
fnlStream = stream;
}
ConsumerConfiguration serverCC = null;
String consumerName = userCC.getDurable();
if (consumerName == null) {
consumerName = userCC.getName();
}
String inboxDeliver = userCC.getDeliverSubject();
// 3. Does this consumer already exist?
if (consumerName != null) {
ConsumerInfo serverInfo = lookupConsumerInfo(fnlStream, consumerName);
if (serverInfo != null) {
// the consumer for that durable already exists
serverCC = serverInfo.getConsumerConfiguration();
// check to see if the user sent a different version than the server has
// because modifications are not allowed during create subscription
ConsumerConfigurationComparer userCCC = new ConsumerConfigurationComparer(userCC);
List<String> changes = userCCC.getChanges(serverCC);
if (changes.size() > 0) {
throw JsSubExistingConsumerCannotBeModified.instance("Changed fields: " + changes);
}
// deliver subject must be null/empty for pull, defined for push
if (isPullMode) {
if (!nullOrEmpty(serverCC.getDeliverSubject())) {
throw JsSubConsumerAlreadyConfiguredAsPush.instance();
}
} else if (nullOrEmpty(serverCC.getDeliverSubject())) {
throw JsSubConsumerAlreadyConfiguredAsPull.instance();
}
if (serverCC.getDeliverGroup() == null) {
// lookedUp was null, means existing consumer is not a queue consumer
if (qgroup == null) {
// ok fine, no queue requested and the existing consumer is also not a queue consumer
// we must check if the consumer is in use though
if (serverInfo.isPushBound()) {
throw JsSubConsumerAlreadyBound.instance();
}
} else {
// else they requested a queue but this durable was not configured as queue
throw JsSubExistingConsumerNotQueue.instance();
}
} else if (qgroup == null) {
throw JsSubExistingConsumerIsQueue.instance();
} else if (!serverCC.getDeliverGroup().equals(qgroup)) {
throw JsSubExistingQueueDoesNotMatchRequestedQueue.instance();
}
// durable already exists, make sure the filter subject matches
if (nullOrEmpty(subject)) {
// allowed if they had given both stream and durable
subject = userCC.getFilterSubject();
} else if (!isFilterMatch(subject, serverCC.getFilterSubject(), fnlStream)) {
throw JsSubSubjectDoesNotMatchFilter.instance();
}
// use the deliver subject as the inbox. It may be null, that's ok, we'll fix that later
inboxDeliver = serverCC.getDeliverSubject();
} else if (so.isBind()) {
throw JsSubConsumerNotFoundRequiredInBind.instance();
}
}
// 4. If pull or no deliver subject (inbox) provided or found, make an inbox.
final String fnlInboxDeliver;
if (isPullMode) {
fnlInboxDeliver = conn.createInbox() + ".*";
} else if (inboxDeliver == null) {
fnlInboxDeliver = conn.createInbox();
} else {
fnlInboxDeliver = inboxDeliver;
}
// 5. If consumer does not exist, create and settle on the config. Name will have to wait
// If the consumer exists, I know what the settled info is
final String settledConsumerName;
final ConsumerConfiguration settledServerCC;
if (serverCC == null) {
ConsumerConfiguration.Builder ccBuilder = ConsumerConfiguration.builder(userCC);
// Pull mode doesn't maintain a deliver subject. It's actually an error if we send it.
if (!isPullMode) {
ccBuilder.deliverSubject(fnlInboxDeliver);
}
if (userCC.getFilterSubject() == null) {
ccBuilder.filterSubject(subject);
}
ccBuilder.deliverGroup(qgroup);
settledServerCC = ccBuilder.build();
settledConsumerName = null;
} else {
settledServerCC = serverCC;
settledConsumerName = consumerName;
}
// 6. create the subscription. lambda needs final or effectively final vars
final MessageManager mm;
final NatsSubscriptionFactory subFactory;
if (isPullMode) {
MessageManagerFactory mmFactory = so.isOrdered() ? _pullOrderedMessageManagerFactory : _pullMessageManagerFactory;
mm = mmFactory.createMessageManager(conn, this, fnlStream, so, settledServerCC, false, dispatcher == null);
subFactory = (sid, lSubject, lQgroup, lConn, lDispatcher) -> new NatsJetStreamPullSubscription(sid, lSubject, lConn, lDispatcher, this, fnlStream, settledConsumerName, mm);
} else {
MessageManagerFactory mmFactory = so.isOrdered() ? _pushOrderedMessageManagerFactory : _pushMessageManagerFactory;
mm = mmFactory.createMessageManager(conn, this, fnlStream, so, settledServerCC, false, dispatcher == null);
subFactory = (sid, lSubject, lQgroup, lConn, lDispatcher) -> {
NatsJetStreamSubscription nsub = new NatsJetStreamSubscription(sid, lSubject, lQgroup, lConn, lDispatcher, this, fnlStream, settledConsumerName, mm);
if (lDispatcher == null) {
nsub.setPendingLimits(so.getPendingMessageLimit(), so.getPendingByteLimit());
}
return nsub;
};
}
NatsJetStreamSubscription sub;
if (dispatcher == null) {
sub = (NatsJetStreamSubscription) conn.createSubscription(fnlInboxDeliver, qgroup, null, subFactory);
} else {
AsyncMessageHandler handler = new AsyncMessageHandler(mm, userHandler, isAutoAck, settledServerCC);
sub = (NatsJetStreamSubscription) dispatcher.subscribeImplJetStream(fnlInboxDeliver, qgroup, handler, subFactory);
}
// 7. The consumer might need to be created, do it here
if (settledConsumerName == null) {
_createConsumerUnsubscribeOnException(fnlStream, settledServerCC, sub);
}
return sub;
}This method is defined in the io.nats.client.impl.NatsJetStream class. It is used to create a JetStream subscription. The method takes various parameters and returns a JetStreamSubscription.
JetStreamSubscription createSubscription(String subject, String queueName, NatsDispatcher dispatcher, MessageHandler userHandler, boolean isAutoAck, PushSubscribeOptions pushSubscribeOptions, PullSubscribeOptions pullSubscribeOptions) throws IOException, JetStreamApiException-
subject: The subject for the subscription. -
queueName: The name of the queue (optional). -
dispatcher: The message dispatcher for the subscription (optional). -
userHandler: The user-defined message handler. -
isAutoAck: A boolean flag indicating whether the messages should be auto-acknowledged. -
pushSubscribeOptions: Options for push subscriptions (optional). -
pullSubscribeOptions: Options for pull subscriptions (optional).
-
Prepare for validation
- Check if the subscription is in pull mode or push mode.
- Set the appropriate options and configurations based on the mode.
- Validate that certain options are not supplied in pull mode.
- Validate that certain options are not supplied in push mode.
-
Flow Control / Heartbeat validation
- If an idle heartbeat is set and greater than zero:
- Throw an exception if the subscription is in pull mode.
- Throw an exception if a consumer group is specified.
- If an idle heartbeat is set and greater than zero:
-
Stream lookup
- If the stream is not provided:
- Look up the stream based on the subject.
- Throw an exception if no matching stream is found.
- If the stream is not provided:
-
Consumer existence check
- If a consumer name is provided:
- Look up the consumer information in the server.
- If the consumer exists:
- Get the server-side consumer configuration.
- Compare the user configuration with the server configuration.
- Throw an exception if there are any changes in the configurations.
- Validate that the deliver subject is appropriate for the subscription mode.
- Validate that the consumer is not already bound or configured as a push/pull consumer.
- If the consumer does not exist and binding is required:
- Throw an exception indicating that the consumer is not found.
- If a consumer name is provided:
-
Inbox creation
- If the subscription is in pull mode or no deliver subject is provided:
- Create an inbox for message delivery.
- Otherwise, use the provided deliver subject.
- If the subscription is in pull mode or no deliver subject is provided:
-
Consumer configuration and creation
- If the consumer does not exist:
- Build the consumer configuration based on the user configuration.
- Set the deliver subject if not in pull mode.
- Set the filter subject if it is not already set.
- Set the deliver group.
- Otherwise, use the settled server-side configuration and consumer name.
- Create the message manager based on the subscription mode and other parameters.
- Set the subscription factory based on the mode and message manager.
- Create the JetStream subscription based on the provided parameters and subscription factory.
- Set the pending limits if the message dispatcher is not provided.
- Create and associate the asynchronous message handler if the dispatcher is provided.
- Call the appropriate subscription creation method based on the presence of the dispatcher.
- If the consumer does not exist:
-
Consumer creation
- If the consumer was not found, create the consumer on the server and handle unsubscribe on exception.
-
Return the created subscription
Note: The method throws IOException and JetStreamApiException in case of any errors.
The createSubscription method in the NatsJetStream class is used to create a subscription for consuming messages from a specified subject in JetStream.
Here is a high-level overview of what the method does:
- It performs validation based on the subscription options provided, such as whether it's a push or pull subscription and if any conflicting options are present.
- It checks if the consumer configuration already exists, and if it does, verifies that the configuration matches the provided options.
- If the consumer does not exist or if it is a new consumer, it creates a settled configuration based on the provided options.
- It determines the deliver subject for the subscription, either by creating a new inbox or using the existing deliver subject.
- Based on whether it is a push or pull subscription, it creates the appropriate type of subscription with the necessary message manager implementation.
- If a dispatcher is provided, it subscribes to the deliver subject using the dispatcher's
subscribeImplJetStreammethod. Otherwise, it directly creates a subscription using the connection'screateSubscriptionmethod. - If a new consumer was created, it calls the
_createConsumerUnsubscribeOnExceptionmethod to handle any errors and unsubscribe from the subscription in case of an exception. - Finally, it returns the created subscription object.
Please note that this is a high-level description and the method may have more intricate details and mechanisms not covered in this summary.
private boolean isFilterMatch(String subscribeSubject, String filterSubject, String stream) throws IOException, JetStreamApiException
private boolean isFilterMatch(String subscribeSubject, String filterSubject, String stream) throws IOException, JetStreamApiException {
// subscribeSubject guaranteed to not be null
// filterSubject may be null or empty or have value
if (subscribeSubject.equals(filterSubject)) {
return true;
}
if (nullOrEmpty(filterSubject) || filterSubject.equals(">")) {
// lookup stream subject returns null if there is not exactly one subject
String streamSubject = lookupStreamSubject(stream);
return subscribeSubject.equals(streamSubject);
}
return false;
}This method is used to determine if a filter subject matches with a given subscribe subject. It takes three parameters - subscribeSubject, filterSubject, and stream.
-
Check if
subscribeSubjectis equal tofilterSubject:- If true, return
true(indicating a match). - If false, proceed to the next step.
- If true, return
-
Check if
filterSubjectis either null, empty, or equals to>.- If true, perform a lookup of the stream subject by calling the
lookupStreamSubjectmethod with thestreamparameter.- If the lookup returns null (indicating there is not exactly one subject), return
false(indicating there is no match). - If the lookup returns a non-null value, compare it with
subscribeSubject.- If they are equal, return
true(indicating a match). - If they are not equal, return
false(indicating no match).
- If they are equal, return
- If the lookup returns null (indicating there is not exactly one subject), return
- If false, return
false(indicating no match).
- If true, perform a lookup of the stream subject by calling the
-
Return
falseas a default fallback (indicating no match).
The isFilterMatch method is a private method defined in the NatsJetStream class. It takes three parameters: subscribeSubject, filterSubject, and stream. The method checks whether the subscribeSubject is a match for the filterSubject based on certain conditions.
First, it checks if the subscribeSubject is equal to the filterSubject. If they are equal, it returns true.
Next, it checks if the filterSubject is null, empty, or has the value ">". If any of these conditions is true, it calls the lookupStreamSubject method to get the streamSubject and then checks if the subscribeSubject is equal to the streamSubject. If they are equal, it returns true.
If none of the above conditions are met, it returns false.
This method is used to determine whether a given subject should be filtered based on the subscription subject and the filter subject in the context of the NatsJetStream implementation.

public JetStreamSubscription subscribe(String subject, String queue, PushSubscribeOptions options) throws IOException, JetStreamApiException
@Override
public JetStreamSubscription subscribe(String subject, String queue, PushSubscribeOptions options) throws IOException, JetStreamApiException {
validateSubject(subject, isSubjectRequired(options));
queue = emptyAsNull(validateQueueName(queue, false));
return createSubscription(subject, queue, null, null, false, options, null);
}The subscribe method in the NatsJetStream class is used to create a subscription to a subject in the NATS JetStream server.
Here is a step-by-step description of what the method does based on its code:
- Validates the
subjectparameter to ensure it is not null or empty, and checks if it is required based on theoptionsparameter. - Validates the
queueparameter to ensure it is not empty and converts any empty strings tonullif allowed. - Calls the
createSubscriptionmethod with the following parameters:-
subject: The subject to subscribe to. -
queue: The queue to receive messages from (can benull). -
null(placeholder): Placeholder parameter for thedurableNameparameter that is not used in this method. -
null(placeholder): Placeholder parameter for thedeliverStartTimeparameter that is not used in this method. -
false: ThemanualAcksparameter that specifies whether to manually acknowledge messages. -
options: ThePushSubscribeOptionsobject that contains additional options for the subscription. -
null(placeholder): Placeholder parameter for theflowControlparameter that is not used in this method.
-
- Returns the
JetStreamSubscriptionobject representing the subscription.
This method handles exceptions such as IOException and JetStreamApiException that can be thrown during the subscription process.
The subscribe method in the NatsJetStream class is used to create a subscription for consuming messages from a NATS JetStream.
This method takes three parameters: subject (the subject to subscribe to), queue (an optional queue group name for load balancing), and options (options for configuring the subscription).
Firstly, the method validates the subject and checks if it is required based on the options. If the queue parameter is empty, it is set to null. Then, the method calls the createSubscription method, passing the subject, queue, and other necessary parameters to create the subscription. Finally, the method returns a JetStreamSubscription object representing the created subscription.
If any exceptions occur during the execution of this method, such as an IOException or a JetStreamApiException, they are thrown.

public JetStreamSubscription subscribe(String subject, Dispatcher dispatcher, MessageHandler handler, boolean autoAck) throws IOException, JetStreamApiException
@Override
public JetStreamSubscription subscribe(String subject, Dispatcher dispatcher, MessageHandler handler, boolean autoAck) throws IOException, JetStreamApiException {
validateSubject(subject, true);
validateNotNull(dispatcher, "Dispatcher");
validateNotNull(handler, "Handler");
return createSubscription(subject, null, (NatsDispatcher) dispatcher, handler, autoAck, null, null);
}The subscribe method in the NatsJetStream class is used to create a JetStream subscription on a specified subject. It takes in the following parameters:
-
subject(String): The subject to subscribe to. -
dispatcher(Dispatcher): An instance of a dispatcher responsible for delivering messages to the handler. -
handler(MessageHandler): An instance of the message handler responsible for processing incoming messages. -
autoAck(boolean): A flag indicating whether messages should be automatically acknowledged.
The method performs the following steps:
- Validate the subject to ensure it is not null or empty. If it fails validation, throw an
IllegalArgumentException. - Validate the dispatcher to ensure it is not null. If it fails validation, throw an
IllegalArgumentException. - Validate the message handler to ensure it is not null. If it fails validation, throw an
IllegalArgumentException. - Create a new subscription by invoking the
createSubscriptionmethod and passing the subject, null, the dispatcher casted toNatsDispatcher, the handler, the autoAck flag, and two null values as arguments. - Return the newly created subscription.
Note: The implementation of createSubscription is not provided in the given snippet, so the exact details of how the subscription is created are not available.
The subscribe method is used to create a subscription for consuming messages from a subject in the NATS JetStream messaging system.
It takes the following parameters:
-
subject: The subject to subscribe to. -
dispatcher: A dispatcher object responsible for dispatching incoming messages to the appropriate handler. -
handler: A message handler object that processes incoming messages. -
autoAck: A boolean flag indicating whether the subscription should automatically acknowledge messages after they have been successfully processed.
The method first performs some validation checks on the subject, dispatcher, and handler parameters to ensure they are not null. Then, it calls the createSubscription method internally to create the actual subscription object and returns it.
Overall, the method provides a convenient way to create a subscription for consuming messages from a subject in the NATS JetStream system, by specifying the subject, dispatcher, handler, and auto-acknowledgment settings.

public JetStreamSubscription subscribe(String subject, Dispatcher dispatcher, MessageHandler handler, boolean autoAck, PushSubscribeOptions options) throws IOException, JetStreamApiException
@Override
public JetStreamSubscription subscribe(String subject, Dispatcher dispatcher, MessageHandler handler, boolean autoAck, PushSubscribeOptions options) throws IOException, JetStreamApiException {
validateSubject(subject, isSubjectRequired(options));
validateNotNull(dispatcher, "Dispatcher");
validateNotNull(handler, "Handler");
return createSubscription(subject, null, (NatsDispatcher) dispatcher, handler, autoAck, options, null);
}This method is defined in the io.nats.client.impl.NatsJetStream class. It takes in the following parameters:
-
subject(String): The subject to subscribe to. -
dispatcher(Dispatcher): The dispatcher to use for message handling. -
handler(MessageHandler): The handler for processing received messages. -
autoAck(boolean): Flag indicating whether messages should be automatically acknowledged. -
options(PushSubscribeOptions): Optional options for configuring the subscription.
The method performs the following steps:
-
Validate the
subjectparameter by calling thevalidateSubjectmethod, passing in the subject and the result of theisSubjectRequiredmethod called with theoptionsparameter. This ensures that the subject is valid and meets any configured requirements. -
Validate that the
dispatcherparameter is not null by calling thevalidateNotNullmethod with the dispatcher and the string "Dispatcher" as arguments. This ensures that a valid dispatcher is provided for message handling. -
Validate that the
handlerparameter is not null by calling thevalidateNotNullmethod with the handler and the string "Handler" as arguments. This ensures that a valid message handler is provided for processing received messages. -
Call the
createSubscriptionmethod, passing in thesubject, null for thequeueGroupparameter, thedispatchercast toNatsDispatcher, thehandler, theautoAckflag, theoptions, and null for thejsSubparameter. This method creates and returns aJetStreamSubscriptionobject based on the provided parameters. -
Return the
JetStreamSubscriptionobject created in the previous step.
Note: This method can throw an IOException or a JetStreamApiException, which may occur during the validation or subscription creation process.
The subscribe() method in the NatsJetStream class is used to subscribe to a subject in NATS JetStream.
It takes in the following parameters:
-
subject(String): The subject to subscribe to. -
dispatcher(Dispatcher): An object that dispatches incoming messages to the appropriate handler. -
handler(MessageHandler): An object that handles incoming messages. -
autoAck(boolean): A flag indicating whether the subscription should automatically acknowledge the messages. -
options(PushSubscribeOptions): Additional options for the subscription.
The method first validates the subject and the dispatcher to ensure they are not null. It then creates a subscription using the createSubscription() method, passing in the subject, null for the interest, the dispatcher, the handler, autoAck flag, options, and null for the flow control options.
The method returns a JetStreamSubscription object representing the subscription.

public JetStreamSubscription subscribe(String subject, String queue, Dispatcher dispatcher, MessageHandler handler, boolean autoAck, PushSubscribeOptions options) throws IOException, JetStreamApiException
@Override
public JetStreamSubscription subscribe(String subject, String queue, Dispatcher dispatcher, MessageHandler handler, boolean autoAck, PushSubscribeOptions options) throws IOException, JetStreamApiException {
validateSubject(subject, isSubjectRequired(options));
queue = emptyAsNull(validateQueueName(queue, false));
validateNotNull(dispatcher, "Dispatcher");
validateNotNull(handler, "Handler");
return createSubscription(subject, queue, (NatsDispatcher) dispatcher, handler, autoAck, options, null);
}The subscribe method in the NatsJetStream class is used to create a JetStream subscription for consuming messages from a subject. Here is a step-by-step description of what the method does based on its body:
- Validates the
subjectparameter to ensure it is not null and meets the requirements specified by theoptionsparameter. - Validates the
queueparameter, which represents the name of the consumer group queue, to ensure it is not empty and returnsnullif it is empty. - Validates the
dispatcherparameter, which is responsible for dispatching messages to the handler, to ensure it is not null. - Validates the
handlerparameter, which is responsible for handling incoming messages, to ensure it is not null. - Calls the
createSubscriptionmethod with the validated parameters to create a JetStream subscription. - Returns the created JetStream subscription.
The subscribe method throws an IOException if there is an error during the subscription creation process and a JetStreamApiException if there is an error with the JetStream API.
The subscribe method in the io.nats.client.impl.NatsJetStream class is used to create a subscription to a JetStream in NATS.
It takes the following parameters:
-
subject: The subject to subscribe to. -
queue: The optional queue group name for the subscription. -
dispatcher: The dispatcher used for message dispatching. -
handler: The handler for processing received messages. -
autoAck: A flag indicating if the messages should be automatically acknowledged. -
options: Additional options for the subscription.
The method begins by validating the subject and queue parameters. It ensures that the subject is not empty and checks if the queue name is provided or not.
Next, it checks that both the dispatcher and handler objects are not null, as they are required to process the received messages.
Finally, it calls the createSubscription method with the validated parameters to actually create the subscription and returns the resulting JetStreamSubscription object.
If any errors occur during the subscription creation, such as IO exceptions or JetStream API exceptions, they are caught and thrown.

public JetStreamSubscription subscribe(String subject, PullSubscribeOptions options) throws IOException, JetStreamApiException
@Override
public JetStreamSubscription subscribe(String subject, PullSubscribeOptions options) throws IOException, JetStreamApiException {
validateSubject(subject, isSubjectRequired(options));
validateNotNull(options, "Pull Subscribe Options");
return createSubscription(subject, null, null, null, false, null, options);
}The subscribe method in the NatsJetStream class is responsible for creating a JetStream subscription. Here is a step-by-step breakdown of what the method does based on the provided body:
- The method overrides the
subscribemethod from the parent class. - The method takes two parameters:
subject(the subject to subscribe to) andoptions(the pull subscription options). - Before proceeding, the method validates the
subjectby calling thevalidateSubjectmethod, passing the subject and a check if the subject is required based on the options. - The method then validates that the
optionsare not null, using thevalidateNotNullmethod, passing the options and a descriptive error message. - Finally, the method calls the
createSubscriptionmethod, passing various parameters:- The
subjectparameter provided to thesubscribemethod. -
nullfor theQueueparameter. -
nullfor theDurableparameter. -
nullfor theDeliverSubjectparameter. -
falsefor theNoAckparameter. -
nullfor theAckWaitparameter. - The
optionsparameter provided to thesubscribemethod.
- The
- The method returns the result of the
createSubscriptionmethod, which is aJetStreamSubscription.
Overall, the subscribe method validates the subject and options parameters, and then calls another method to create and return a JetStream subscription based on the provided parameters.
The subscribe method in the NatsJetStream class, defined in io.nats.client.impl, is used to create a subscription to consume messages from a JetStream subject using pull-based consumption.
The method takes two parameters:
-
subject: The subject (topic) to subscribe to. -
options: The pull subscription options, which customize the behavior of the subscription.
The method first performs some input validation, ensuring that the subject is valid and that the options are not null. If the validation passes, it then calls the createSubscription method internally to create and return a JetStreamSubscription object.
The returned JetStreamSubscription is the subscription that can be used to receive messages from the specified subject using pull-based consumption.
Possible exceptions that can be thrown by the method include IOException (in case of an I/O error) and JetStreamApiException (if there is an error related to JetStream API).
Note that the provided code only shows the method body, and the implementation of the createSubscription method is not provided.

public JetStreamSubscription subscribe(String subject, Dispatcher dispatcher, MessageHandler handler, PullSubscribeOptions options) throws IOException, JetStreamApiException
@Override
public JetStreamSubscription subscribe(String subject, Dispatcher dispatcher, MessageHandler handler, PullSubscribeOptions options) throws IOException, JetStreamApiException {
validateSubject(subject, isSubjectRequired(options));
validateNotNull(dispatcher, "Dispatcher");
validateNotNull(handler, "Handler");
validateNotNull(options, "Pull Subscribe Options");
return createSubscription(subject, null, (NatsDispatcher) dispatcher, handler, false, null, options);
}The subscribe method is defined in the class io.nats.client.impl.NatsJetStream. It takes four parameters: subject, dispatcher, handler, and options.
- The method first calls the
validateSubjectmethod to ensure that thesubjectparameter is valid. It checks if the subject is required based on theoptionsparameter by calling theisSubjectRequiredmethod. - It then calls the
validateNotNullmethod for thedispatcher,handler, andoptionsparameters to ensure that they are not null. - Finally, it calls the
createSubscriptionmethod to create a JetStream subscription. It passes thesubject, null, thedispatchercasted toNatsDispatcher, thehandlerand theoptionsparameters to thecreateSubscriptionmethod. The last two parameters in thecreateSubscriptionmethod call arefalseandnull.
The method returns a JetStreamSubscription object obtained from the createSubscription method call.
The subscribe method in class NatsJetStream is used to create a subscription to a given subject in the NATS JetStream messaging system.
It takes in the following parameters:
-
subject(string): The subject to subscribe to. -
dispatcher(Dispatcher): The dispatcher to use for delivering messages. -
handler(MessageHandler): The handler to process incoming messages. -
options(PullSubscribeOptions): The options for the subscription.
This method first performs some validations on the subject, dispatcher, handler, and options to ensure they are not null.
Then, it calls the createSubscription method to create the subscription. It passes the subject, dispatcher, handler, and options to the createSubscription method, along with some additional parameters.
Finally, it returns the created JetStreamSubscription object.

public ConsumerContext consumerContext(String streamName, String consumerName) throws IOException, JetStreamApiException
@Override
public ConsumerContext consumerContext(String streamName, String consumerName) throws IOException, JetStreamApiException {
Validator.validateStreamName(streamName, true);
Validator.required(consumerName, "Consumer Name");
return getNatsStreamContext(streamName).consumerContext(consumerName);
}The consumerContext method in the NatsJetStream class is used to retrieve a ConsumerContext object based on the provided streamName and consumerName. Here is a step-by-step description of what this method does:
-
The method is declared with the
@Overrideannotation, indicating that it overrides a method in a superclass or interface. -
The
validateStreamNamemethod from theValidatorclass is called with thestreamNameas an argument to check if it is valid. Thetrueboolean value indicates that an exception should be thrown if the stream name is invalid. -
The
requiredmethod from theValidatorclass is called with theconsumerNameand a string value "Consumer Name" as arguments to check if the consumer name is not null or empty. If it is null or empty, an exception is thrown. -
The
getNatsStreamContextmethod is called with thestreamNameas an argument to retrieve the correspondingNatsStreamContextobject. -
The
consumerContextmethod is called on theNatsStreamContextobject returned in the previous step with theconsumerNameas an argument. This method retrieves theConsumerContextobject associated with the specified consumer name. -
The retrieved
ConsumerContextobject is returned as the result of the method.
Note: The method includes throws declarations for IOException and JetStreamApiException, indicating that it can throw these exceptions during its execution.
The consumerContext method is a part of the NatsJetStream class in the io.nats.client.impl package. It takes two parameters streamName and consumerName, both of type String.
The method first validates the streamName parameter using the validateStreamName method from the Validator class, ensuring that it is not empty or null.
Next, it checks that the consumerName parameter is not empty or null, using the required method from the Validator class.
Finally, it calls the consumerContext method on the NatsStreamContext object obtained from the getNatsStreamContext method. This method returns a ConsumerContext object, which is then returned by the consumerContext method.
Overall, this method is responsible for validating the input parameters and returning the consumer context for the specified stream and consumer.

StreamInfoReader
The StreamInfoReader class is responsible for reading information about a stream. It provides methods to retrieve various details such as the stream's name, duration, and format. This class acts as a utility for obtaining relevant information about a stream in a convenient manner.
void process(Message msg) throws JetStreamApiException {
engine = new ListRequestEngine(msg);
StreamInfo si = new StreamInfo(msg);
if (streamInfo == null) {
streamInfo = si;
} else {
streamInfo.getStreamState().getSubjects().addAll(si.getStreamState().getSubjects());
}
}This method is defined in the class io.nats.client.impl.StreamInfoReader. It is responsible for processing a message and updating the stream information.
-
msg: The message to be processed.
- Create a new
ListRequestEngineobject namedengineby passing themsgas a parameter. - Create a new
StreamInfoobject namedsiby passing themsgas a parameter. - Check if the variable
streamInfois null.- If it is null, assign the value of
sitostreamInfo. - If it is not null, add all the subjects from
si.getStreamState().getSubjects()tostreamInfo.getStreamState().getSubjects().
- If it is null, assign the value of
The process method in the io.nats.client.impl.StreamInfoReader class is responsible for processing a message received by the reader.
- First, a new
ListRequestEngineis created using the received message. - Then, a
StreamInfoobject is created based on the received message. - If the
streamInfoobject is currently null, thestreamInfovariable is set to the newly createdsiobject. - If the
streamInfoobject is not null, the subjects from thesiobject's stream state are added to the subjects of the existingstreamInfoobject.
In summary, the process method processes a message by creating a ListRequestEngine and a StreamInfo object, and updates the streamInfo object with the received message's stream state subjects.

byte[] nextJson(StreamInfoOptions options) {
StringBuilder sb = beginJson();
addField(sb, "offset", engine.nextOffset());
if (options != null) {
addField(sb, SUBJECTS_FILTER, options.getSubjectsFilter());
addFldWhenTrue(sb, DELETED_DETAILS, options.isDeletedDetails());
}
return endJson(sb).toString().getBytes();
}The nextJson method in the StreamInfoReader class is used to return the JSON representation of the next stream information entry based on the given options.
Here is a step-by-step description of what the method does:
- Initialize a
StringBuildernamedsbby calling thebeginJsonmethod. - Add the "offset" field to the
sbwith the value obtained from calling thenextOffsetmethod on theengineobject. - Check if the
optionsparameter is not null. - If
optionsis not null:- Add the "subjects_filter" field to the
sbwith the value obtained from calling thegetSubjectsFiltermethod on theoptionsobject. - Check if the
isDeletedDetailsmethod on theoptionsobject returns true. - If it does, add the "deleted_details" field to the
sb.
- Add the "subjects_filter" field to the
- Call the
endJsonmethod on thesbto finalize the JSON representation. - Convert the
sbto aStringand then get the corresponding byte representation as abyte[]. - Return the byte array.
The nextJson method in the StreamInfoReader class is responsible for generating a JSON representation of the next stream information.
Here is a brief description of what this method does:
- Creates a
StringBuilderto build the JSON string. - Adds the "offset" field to the JSON string by calling the
nextOffsetmethod of the underlying engine. - Checks if the
optionsparameter is not null:- If not null, adds the "subjectsFilter" field to the JSON string by calling the
getSubjectsFiltermethod of theoptionsparameter. - Adds the "deletedDetails" field to the JSON string by calling the
isDeletedDetailsmethod of theoptionsparameter.
- If not null, adds the "subjectsFilter" field to the JSON string by calling the
- Returns the JSON string representation as a byte array.
This method is used to get the next JSON representation of the stream information, including offset, subjects filter, and deleted details if specified by the options parameter.

FileAuthHandler
The FileAuthHandler class is an implementation of the AuthHandler interface. It provides functionality for handling authentication in a file-based system.
private char[] extract(CharBuffer data, int headers) {
CharBuffer buff = CharBuffer.allocate(data.length());
boolean skipLine = false;
int headerCount = 0;
int linePos = -1;
while (data.length() > 0) {
char c = data.get();
linePos++;
// End of line, either we got it, or we should keep reading the new line
if (c == '\n' || c == '\r') {
if (buff.position() > 0) {
// we wrote something
break;
}
skipLine = false;
// so we can start right up
linePos = -1;
continue;
}
// skip to the new line
if (skipLine) {
continue;
}
// Ignore whitespace
if (Character.isWhitespace(c)) {
continue;
}
// If we are on a - skip that line, bump the header count
if (c == '-' && linePos == 0) {
skipLine = true;
headerCount++;
continue;
}
// Skip the line, or add to buff
if (!skipLine && headerCount == headers) {
buff.put(c);
}
}
// check for naked value
if (buff.position() == 0 && headers == 1) {
data.position(0);
while (data.length() > 0) {
char c = data.get();
if (c == '\n' || c == '\r' || Character.isWhitespace(c)) {
if (buff.position() > 0) {
// we wrote something
break;
}
continue;
}
buff.put(c);
}
buff.flip();
} else {
buff.flip();
}
char[] retVal = new char[buff.length()];
buff.get(retVal);
buff.clear();
for (int i = 0; i < buff.capacity(); i++) {
buff.put('\0');
}
return retVal;
}
```### private char[] readKeyChars() throws IOException
```java
private char[] readKeyChars() throws IOException {
char[] keyChars = null;
if (this.credsFile != null) {
byte[] data = Files.readAllBytes(Paths.get(this.credsFile));
ByteBuffer bb = ByteBuffer.wrap(data);
CharBuffer chars = StandardCharsets.UTF_8.decode(bb);
// we are 2nd so 3 headers
keyChars = this.extract(chars, 3);
// Clear things up as best we can
chars.clear();
for (int i = 0; i < chars.capacity(); i++) {
chars.put('\0');
}
for (int i = 0; i < data.length; i++) {
data[i] = 0;
}
} else {
byte[] data = Files.readAllBytes(Paths.get(this.nkeyFile));
ByteBuffer bb = ByteBuffer.wrap(data);
CharBuffer chars = StandardCharsets.UTF_8.decode(bb);
keyChars = this.extract(chars, 1);
// Clear things up as best we can
chars.clear();
for (int i = 0; i < chars.capacity(); i++) {
chars.put('\0');
}
for (int i = 0; i < data.length; i++) {
data[i] = 0;
}
}
return keyChars;
}
```### public byte[] sign(byte[] nonce)
```java
public byte[] sign(byte[] nonce) {
try {
char[] keyChars = this.readKeyChars();
NKey nkey = NKey.fromSeed(keyChars);
byte[] sig = nkey.sign(nonce);
nkey.clear();
return sig;
} catch (Exception exp) {
throw new IllegalStateException("problem signing nonce", exp);
}
}The sign method in the class io.nats.client.impl.FileAuthHandler is used to sign a given byte array using a secret key stored in a file.
Here is a step-by-step description of what the method does:
-
Reads the secret key stored in the file as characters and stores them in a char array called
keyChars. -
Converts the
keyCharsarray into anNKeyobject callednkeyusing thefromSeedmethod of theNKeyclass. ThefromSeedmethod takes the characters of the secret key and creates aNKeyobject, which represents a cryptographic key derived from the seed. -
The
nkey.sign(nonce)method is called to sign the givennoncebyte array using the key stored in thenkeyobject. This method internally uses a cryptographic algorithm to generate a digital signature based on the key and the nonce. -
The
nkey.clear()method is called to clear the internal state of thenkeyobject. This is done to remove any sensitive information from memory after the signing process. -
The generated signature is returned as a byte array.
-
If any exception occurs during the signing process, an
IllegalStateExceptionis thrown with the message "problem signing nonce". The original exception is wrapped and included as the cause of theIllegalStateException.
The sign method in the FileAuthHandler class is responsible for signing the provided nonce bytes using a private key stored in a file.
First, the method reads the private key characters from the file using the readKeyChars method.
Then, it creates an NKey object from the private key characters.
Next, it uses the sign method of the NKey object to sign the nonce bytes.
After signing, it clears the private key from memory using the clear method of the NKey object.
Finally, it returns the signature as a byte array.
If any exception occurs during the signing process, it throws an IllegalStateException with an appropriate error message.

NatsSubscription
The NatsSubscription class extends the NatsConsumer class and implements the Subscription interface. It represents a subscription to a NATS messaging system. This class provides functionality to subscribe to topics and receive messages from the NATS server. Additionally, it inherits properties and methods from the NatsConsumer class, allowing for efficient message consumption and processing.
void reSubscribe(String newDeliverSubject) {
connection.sendUnsub(this, 0);
if (dispatcher == null) {
connection.remove(this);
sid = connection.reSubscribe(this, newDeliverSubject, queueName);
} else {
MessageHandler handler = dispatcher.getSubscriptionHandlers().get(sid);
dispatcher.remove(this);
sid = dispatcher.reSubscribe(this, newDeliverSubject, queueName, handler);
}
subject = newDeliverSubject;
}The reSubscribe method in the NatsSubscription class is responsible for resubscribing the subscription to a new deliver subject.
Here is a step-by-step description of what the method does:
-
The method starts by sending an unsubscribe request using the
sendUnsubmethod of the connection associated with the subscription. This unsubscribes the subscription from the current subject it was subscribed to. -
After unsubscribing, the method checks if the subscription has a dispatcher assigned to it. The dispatcher is responsible for handling incoming messages for the subscription.
-
If the subscription does not have a dispatcher, it means it is a single-subscription and not part of a subscription group. In this case, the subscription is removed from the connection using the
removemethod. -
The method then proceeds to re-subscribe the subscription to the new deliver subject using the
reSubscribemethod of the connection. This method takes the subscription, the new deliver subject, and the existing queue name (if any) as parameters. ThereSubscribemethod returns the new subscription ID (sid). -
If the subscription has a dispatcher assigned to it, it means it is part of a subscription group. In this case, the method retrieves the message handler associated with the subscription using the
getSubscriptionHandlersmethod of the dispatcher. -
The method then removes the subscription from the dispatcher using the
removemethod. -
Finally, the method re-subscribes the subscription to the new deliver subject using the
reSubscribemethod of the dispatcher. This method takes the subscription, the new deliver subject, the existing queue name (if any), and the message handler as parameters. ThereSubscribemethod returns the new subscription ID (sid). -
The method updates the subscription's subject with the new deliver subject.
That concludes the step-by-step description of the reSubscribe method.
The reSubscribe method in the NatsSubscription class is used to resubscribe the subscription to a new deliver subject.
It first sends an unsubscribe message to the NATS server for the current subscription using the connection.sendUnsub method.
If the dispatcher object is null, it means that the subscription is standalone and not part of a dispatcher. In this case, it removes the subscription from the connection's list of active subscriptions using connection.remove, and then resubscribes the subscription to the new deliver subject by calling connection.reSubscribe method with the new deliver subject and the existing queue name.
If the dispatcher object is not null, it means that the subscription is part of a dispatcher. In this case, it looks up the message handler associated with the subscription using dispatcher.getSubscriptionHandlers().get(sid). Then it removes the subscription from the dispatcher's list of active subscriptions using dispatcher.remove, and finally resubscribes the subscription to the new deliver subject by calling dispatcher.reSubscribe with the new deliver subject, the existing queue name, and the retrieved message handler.
After the resubscription, the subject instance variable is updated with the new deliver subject.

void invalidate() {
if (this.incoming != null) {
this.incoming.pause();
}
this.dispatcher = null;
this.incoming = null;
}This method is defined in the class io.nats.client.impl.NatsSubscription and is responsible for invalidating the subscription.
-
Check if the
incomingobject is not null:- If
incomingis not null, proceed to the next step. - If
incomingis null, skip to step 4.
- If
-
Call the
pause()method on theincomingobject:-
incoming.pause()is called to pause the incoming messages on the subscription.
-
-
Set both the
dispatcherandincomingobjects to null:-
dispatcheris set to null so that no further dispatching of messages can occur. -
incomingis set to null so that the subscription no longer holds a reference to incoming messages.
-
-
The method execution is completed.
The invalidate method in the NatsSubscription class, defined in the io.nats.client.impl package, is used to mark the subscription as invalid.
When called, the method checks if the incoming field is not null. If it is not null, it pauses the incoming object, which is most likely some kind of message or data receiver for the subscription.
Next, it sets both the dispatcher and incoming fields to null thereby invalidating the subscription. This means that any further messages or data for this subscription will be ignored.
Overall, the invalidate method is responsible for pausing incoming messages and invalidating the subscription by setting relevant fields to null.

protected NatsMessage nextMessageInternal(Duration timeout) throws InterruptedException {
if (this.dispatcher != null) {
throw new IllegalStateException("Subscriptions that belong to a dispatcher cannot respond to nextMessage directly.");
} else if (this.incoming == null) {
throw new IllegalStateException("This subscription is inactive.");
}
NatsMessage msg = incoming.pop(timeout);
if (this.incoming == null || !this.incoming.isRunning()) {
// We were unsubscribed while waiting
throw new IllegalStateException("This subscription became inactive.");
}
if (msg != null) {
this.incrementDeliveredCount();
}
if (this.reachedUnsubLimit()) {
this.connection.invalidate(this);
}
return msg;
}The method nextMessageInternal is part of the class io.nats.client.impl.NatsSubscription and is used to retrieve the next message from the subscription's incoming queue. Here is a step-by-step description of what this method does:
-
Check if the subscription has a dispatcher assigned to it. If it does, it means that the subscription belongs to a dispatcher and cannot respond to
nextMessagedirectly. If this condition is true, anIllegalStateExceptionis thrown. -
Check if the
incomingqueue is null. If it is, it means that this subscription is inactive and cannot retrieve the next message. If this condition is true, anIllegalStateExceptionis thrown. -
Use the
popmethod of theincomingqueue to retrieve the next message. Thepopmethod blocks until a message is available or until the specifiedtimeoutduration has passed. -
Check if the
incomingqueue is null or not running anymore. If it is null or not running, it means that the subscription was unsubscribed while waiting for the next message. If this condition is true, anIllegalStateExceptionis thrown. -
If a message was retrieved successfully (i.e.,
msgis not null), increment the delivered count of the subscription. -
Check if the subscription has reached the unsubscribe limit. If it has, it means that the subscription should be invalidated by the connection. The connection's
invalidatemethod is called with the current subscription as a parameter. -
Finally, return the retrieved message (
msg) to the caller.
Note: The timeout parameter is used to specify the maximum time to wait for a message. It is of type Duration, which represents a duration-based amount of time. If no message is available within the specified timeout, the pop method will return null.
The nextMessageInternal method in the NatsSubscription class is used to retrieve the next message that has been received by the subscription.
Here's a breakdown of what the method does:
-
It first checks if the subscription belongs to a dispatcher. If it does, an
IllegalStateExceptionis thrown because subscriptions belonging to a dispatcher cannot respond tonextMessagedirectly. -
It then checks if the subscription is active. If it is not, an
IllegalStateExceptionis thrown indicating that the subscription is inactive. -
The method then waits for the next message to be received, using the specified timeout duration.
-
After waiting for a message, it checks if the subscription is still active. If it has become inactive during the waiting period, an
IllegalStateExceptionis thrown. -
If a message is received, the method increments the delivered count of the subscription.
-
It then checks if the subscription has reached the unsubscribe limit. If it has, the associated connection is invalidated.
-
Finally, the method returns the received message.
Overall, the nextMessageInternal method ensures that the subscription is active, waits for the next message, handles subscription and connection state changes, and returns the received message.

public void unsubscribe()
@Override
public void unsubscribe() {
if (this.dispatcher != null) {
throw new IllegalStateException("Subscriptions that belong to a dispatcher cannot respond to unsubscribe directly.");
} else if (this.incoming == null) {
throw new IllegalStateException("This subscription is inactive.");
}
if (isDraining()) {
// No op while draining
return;
}
this.connection.unsubscribe(this, -1);
}The unsubscribe method in the class io.nats.client.impl.NatsSubscription is responsible for unsubscribing a subscription from a NATS server.
Here's a step-by-step description of what this method does based on its body:
-
Check if the
dispatcherobject of the subscription is not null. If it is not null, it means that the subscription belongs to a dispatcher, and it is not allowed to respond to unsubscribe directly. In such a case, throw anIllegalStateExceptionwith the message "Subscriptions that belong to a dispatcher cannot respond to unsubscribe directly." -
If the
incomingobject of the subscription is null, it means that the subscription is inactive and cannot be unsubscribed. In this case, throw anIllegalStateExceptionwith the message "This subscription is inactive." -
Check if the subscription is currently in the process of draining messages. If it is, then there is no need to perform any action, so return without doing anything.
-
If none of the above conditions are met, call the
unsubscribemethod of theconnectionobject (which represents the NATS server connection) with the subscription object and -1 as parameters. This effectively unsubscribes the subscription from the NATS server.
That's the step-by-step description of what the unsubscribe method does based on its body.
The unsubscribe method in the NatsSubscription class is used to unsubscribe from a NATS subscription.
Here's a breakdown of what the method does:
-
It first checks if the subscription is associated with a dispatcher. If it is, it throws an
IllegalStateExceptionwith the message that subscriptions belonging to a dispatcher cannot directly respond to unsubscribe requests. -
It then checks if the subscription is inactive by checking if the
incomingfield is null. If it is, it throws anIllegalStateExceptionwith the message that the subscription is inactive. -
Next, it checks if the subscription is currently being drained. If it is, it simply returns and does nothing. This implies that the unsubscription request is disregarded while the subscription is being drained.
-
If none of the above conditions are met, it proceeds to call the
unsubscribemethod on the underlying NATS connection, passing in the subscription instance and a timeout value of -1, indicating an immediate unsubscribe request.

public Subscription unsubscribe(int after)
@Override
public Subscription unsubscribe(int after) {
if (this.dispatcher != null) {
throw new IllegalStateException("Subscriptions that belong to a dispatcher cannot respond to unsubscribe directly.");
} else if (this.incoming == null) {
throw new IllegalStateException("This subscription is inactive.");
}
if (isDraining()) {
// No op while draining
return this;
}
this.connection.unsubscribe(this, after);
return this;
}The unsubscribe method in the NatsSubscription class is used to unsubscribe from a NATS subscription.
Step-by-step description of the method:
-
Check if the subscription has a dispatcher assigned to it:
- If a dispatcher is assigned, it means that the subscription belongs to a dispatcher and cannot directly respond to an unsubscribe. In this case, an
IllegalStateExceptionis thrown with the error message "Subscriptions that belong to a dispatcher cannot respond to unsubscribe directly."
- If a dispatcher is assigned, it means that the subscription belongs to a dispatcher and cannot directly respond to an unsubscribe. In this case, an
-
Check if the subscription is inactive:
- If the
incomingfield isnull, it means that the subscription is inactive and cannot be unsubscribed. In this case, anIllegalStateExceptionis thrown with the error message "This subscription is inactive."
- If the
-
Check if the subscription is currently draining:
- If the subscription is in the process of draining, meaning that it is in the process of shutting down, no additional unsubscribe operations can be performed. In this case, the method returns the current instance of the
NatsSubscriptionobject without performing any further action.
- If the subscription is in the process of draining, meaning that it is in the process of shutting down, no additional unsubscribe operations can be performed. In this case, the method returns the current instance of the
-
If none of the above conditions are met, perform the unsubscribe operation:
- The
connectionobject associated with the subscription is used to perform the actual unsubscribe operation by invoking theunsubscribemethod on it. Theafterparameter specified in the method call determines the number of server replies to wait for before considering the unsubscribe successful.
- The
-
Finally, return the current instance of the
NatsSubscriptionobject after performing the unsubscribe operation.
Please note that this description is based on the provided code snippet and may not capture all the possible scenarios or interactions with other parts of the codebase.
The unsubscribe method is used to unsubscribe from a NATS subscription. It takes an integer parameter after, which specifies the number of messages to allow before automatically unsubscribing.
If the subscription belongs to a dispatcher, it throws an IllegalStateException since subscriptions that belong to a dispatcher cannot directly respond to unsubscribe.
If the subscription is inactive (i.e., there is no incoming message), it also throws an IllegalStateException.
If the subscription is currently being drained (i.e., actively unsubscribing), the method returns itself (this) without performing any operation.
Otherwise, it calls the unsubscribe method on the connection associated with the subscription, passing in the subscription instance and the after parameter. Lastly, it returns itself (this).

NatsServerPool
The NatsServerPool class is a public implementation of the ServerPool interface.
public void initialize(Options opts) {
// 1. Hold on to options as we need them for settings
options = opts;
// 2. maxConnectAttempts accounts for the first connect attempt and also reconnect attempts
maxConnectAttempts = options.getMaxReconnect() < 0 ? Integer.MAX_VALUE : options.getMaxReconnect() + 1;
// 3. Add all the bootstrap to the server list and prepare list for next
// FYI bootstrap will always have at least the default url
synchronized (listLock) {
entryList = new ArrayList<>();
for (NatsUri nuri : options.getNatsServerUris()) {
// 1. If item is not found in the list being built, add to the list
boolean notAlreadyInList = true;
for (ServerPoolEntry entry : entryList) {
if (nuri.equivalent(entry.nuri)) {
notAlreadyInList = false;
break;
}
}
if (notAlreadyInList) {
if (defaultScheme == null && !nuri.getScheme().equals(NatsConstants.NATS_PROTOCOL)) {
defaultScheme = nuri.getScheme();
}
entryList.add(new ServerPoolEntry(nuri, false));
}
}
// 6. prepare list for next
afterListChanged();
}
}The initialize method in the NatsServerPool class is responsible for setting up the server pool based on the provided options.
-
Hold on to options as we need them for settings:
- The
optionsvariable is assigned the value of theoptsparameter, which holds the options for configuring the server pool.
- The
-
Calculate the maximum number of connect attempts:
- The
maxConnectAttemptsvariable is assigned a value based on themaxReconnectoption from theoptionsobject. - If
maxReconnectis less than 0,maxConnectAttemptsis set toInteger.MAX_VALUE, indicating an infinite number of reconnect attempts. - Otherwise,
maxConnectAttemptsis set tomaxReconnect + 1.
- The
-
Add all the bootstrap to the server list:
- A synchronized block is used to ensure thread safety when modifying the
entryListvariable. - The
entryListvariable is initialized as a newArrayList. - For each
NatsUriin thenatsServerUrisoption from theoptionsobject:- Check if the item is not already in the list being built.
- If it is not already in the list, create a new
ServerPoolEntrywith theNatsUriand add it to theentryList. - Additionally, if the
defaultSchemeis null and the currentNatsUrihas a scheme different from theNatsConstants.NATS_PROTOCOL, assign the scheme of theNatsUrito thedefaultScheme.
- A synchronized block is used to ensure thread safety when modifying the
-
Prepare list for the next operation:
- Call the
afterListChangedmethod to handle any necessary updates or adjustments after theentryListhas been modified.
- Call the
This method initializes the server pool by storing the options, determining the maximum number of connect attempts, building the initial list of server entries based on the provided URIs, and performing any required preparations for subsequent operations.
The initialize method in the NatsServerPool class initializes the NATS server pool using the provided options. Here is a breakdown of what the method does:
- Stores the provided options internally for later use.
- Calculates the maximum number of allowed connection attempts based on the maximum reconnect value from the options.
- Populates the server list by adding all the bootstrap servers specified in the options. If a server is already in the list, it won't be added again.
- If a defaultScheme hasn't been set yet and the first server in the list has a non-default scheme, the defaultScheme is set to that scheme.
- Each server in the list is wrapped in a
ServerPoolEntryobject and added to theentryList. - Once the server list has been updated, any necessary operations for handling the change in the list are performed.

public boolean acceptDiscoveredUrls(List discoveredServers)
@Override
public boolean acceptDiscoveredUrls(List<String> discoveredServers) {
// 1. If ignored discovered servers, don't do anything b/c never want
// anything but the explicit, which is already loaded.
// 2. return false == no new servers discovered
if (options.isIgnoreDiscoveredServers()) {
return false;
}
synchronized (listLock) {
// 2. Build a list for discovered
// - since we'll need the NatsUris later
// - and to have a list to use to prune removed gossiped servers
List<NatsUri> discovered = new ArrayList<>();
for (String d : discoveredServers) {
try {
discovered.add(new NatsUri(d, defaultScheme));
} catch (URISyntaxException ignore) {
// should never actually happen
}
}
// 3. Start a new server list, loading in current order from the current list, and keeping
// - the last connected
// - all non-gossiped
// - any found in the new discovered list
// - for any new discovered, we also remove them from
// that list so step there are no dupes for step #4
// - This also maintains the Srv state of an already known discovered
List<ServerPoolEntry> newEntryList = new ArrayList<>();
for (ServerPoolEntry entry : entryList) {
int ix = findEquivalent(discovered, entry.nuri);
if (ix != -1 || entry.nuri.equals(lastConnected) || !entry.isGossiped) {
newEntryList.add(entry);
if (ix != -1) {
discovered.remove(ix);
}
}
}
// 4. Add all left over from the new discovered list
boolean discoveryContainedUnknowns = false;
if (discovered.size() > 0) {
discoveryContainedUnknowns = true;
for (NatsUri d : discovered) {
newEntryList.add(new ServerPoolEntry(d, true));
}
}
// 5. replace the list with the new one
entryList = newEntryList;
// 6. prepare list for next
afterListChanged();
// 7.
return discoveryContainedUnknowns;
}
}The acceptDiscoveredUrls method in the NatsServerPool class is responsible for updating the server pool based on the newly discovered NATS server URLs.
Here is a step-by-step description of what the method does:
-
It first checks if the option to ignore discovered servers is enabled. If it is, the method returns false, indicating that no new servers have been discovered.
-
If the ignore option is not enabled, the method proceeds to update the server pool.
-
Inside a synchronized block, the method creates a new list called
discovered, which will be used to store the newly discovered server URIs asNatsUriobjects (using thedefaultScheme). -
For each URL in the
discoveredServerslist, the method attempts to create a newNatsUriobject. If the URL is invalid and results in aURISyntaxException, the exception is ignored. -
After creating the
discoveredlist, the method starts building a new list callednewEntryList, which will replace the existing server pool list. -
For each entry in the existing server pool list (
entryList), the method checks if the entry matches any of the discovered server URIs or the last connected URI, or if the entry is not gossiped. -
If a match is found or the entry is not gossiped, the entry is added to the
newEntryList. If a match is found in the discovered list (discovered), that entry is removed from the discovered list to prevent duplicates in the next step. -
After iterating through all entries in the existing server pool list, the method checks if there are any leftover entries in the discovered list. If there are, it means that these entries were not matched with any existing entries and are considered newly discovered servers.
-
For each remaining entry in the discovered list, a new
ServerPoolEntryis created and added to thenewEntryList. -
After adding all the necessary entries to the
newEntryList, the method replaces the existing server pool list (entryList) with thenewEntryList. -
The
afterListChangedmethod is then called to perform any necessary operations after the server pool list has been updated. -
Finally, the method returns a boolean value, indicating whether any unknown servers were discovered during the process. This value is determined by checking if the size of the discovered list is greater than zero.
That's the step-by-step description of what the acceptDiscoveredUrls method does based on its body.
The acceptDiscoveredUrls method is used to update the list of servers in the NATS server pool based on new discovered URLs.
Here's a step-by-step breakdown of what the method does:
-
If the option to ignore discovered servers is enabled, the method does nothing and returns
false. -
If the ignore option is not enabled, the method proceeds and creates a synchronized block.
-
Within the synchronized block, the method builds a new list of
NatsUriobjects based on the discovered server URLs provided as input. -
It then creates a new list called
newEntryListto store the updated server pool entries. It iterates over the existingentryListand adds the entries that match the following conditions:- The entry has an equivalent
NatsUriin thediscoveredlist. - The entry is the last connected server.
- The entry is a non-gossiped server.
- The entry has an equivalent
-
After adding the relevant entries from the existing list, the method checks if there are any remaining
NatsUriobjects in thediscoveredlist. If there are, it iterates over them and adds newServerPoolEntryobjects to thenewEntryListwith theisGossipedflag set totrue. -
The
entryListis then replaced with the updatednewEntryList. -
The
afterListChanged()method is called to perform any necessary actions after the server list has been updated. -
Finally, the method returns
trueif thediscoveredlist contained any unknown server URLs, indicating that new servers were added to the server pool. Otherwise, it returnsfalse.
Overall, the acceptDiscoveredUrls method is responsible for accepting new discovered server URLs and updating the server pool accordingly.

private void afterListChanged() {
// 1. randomize if needed and allowed
if (entryList.size() > 1 && !options.isNoRandomize()) {
Collections.shuffle(entryList, ThreadLocalRandom.current());
}
// 2. calculate hasSecureServer and find the index of lastConnected
hasSecureServer = false;
int lastConnectedIx = -1;
for (int ix = 0; ix < entryList.size(); ix++) {
NatsUri nuri = entryList.get(ix).nuri;
hasSecureServer |= nuri.isSecure();
if (nuri.equals(lastConnected)) {
lastConnectedIx = ix;
}
}
// C. put the last connected server at the end of the list
if (lastConnectedIx != -1) {
entryList.add(entryList.remove(lastConnectedIx));
}
}The afterListChanged method in the NatsServerPool class performs the following steps:
-
Check if the
entryListhas more than one element and if thenoRandomizeoption is not set. If both conditions are met, randomize the order of the elements in theentryListusingCollections.shufflemethod and a ThreadLocalRandom generator. -
Calculate the
hasSecureServerflag by iterating through each element in theentryList. Get thenuri(NatsUri) from the element and sethasSecureServerto true if any of thenuriis secure (isSecure()method returns true). Additionally, find the index of thelastConnectedNatsUri in theentryListand store it in thelastConnectedIxvariable. -
Reorder the
entryListsuch that the element at thelastConnectedIxposition is moved to the end of the list. This is done using theaddandremovemethods ofentryList, ensuring that the last connected server is now at the end of the list.
The method afterListChanged is responsible for performing some operations after the list of server entries is changed. Here's a breakdown of what the method does:
-
Randomize: If the entryList has more than one element and the options do not specify to not randomize, the method shuffles the elements in the entryList using
Collections.shuffle()with the help ofThreadLocalRandom.current(). This step ensures that the order of the servers is randomized. -
Calculate
hasSecureServerand find the index oflastConnected: The method iterates through each element in the entryList and checks if the server URL (NatsUri) is secure. It sets thehasSecureServerflag to true if any of the server URLs are secure. It also finds the index of the last connected server by comparing the server's URL with thelastConnectedvariable. -
Put the last connected server at the end of the list: If a last connected server was found (i.e.,
lastConnectedIxis not -1), the method removes the element atlastConnectedIxfrom the entryList and adds it back at the end of the list. This step ensures that the last connected server is always at the end of the list.
Overall, this method is responsible for randomizing the server list, checking for secure servers, and ensuring the last connected server is moved to the end of the list.

public NatsUri peekNextServer()
@Override
public NatsUri peekNextServer() {
synchronized (listLock) {
return entryList.size() > 0 ? entryList.get(0).nuri : null;
}
}The peekNextServer method in the io.nats.client.impl.NatsServerPool class is used to retrieve the next server in a list of server entries. Below is a step-by-step description of what the method does:
- The method
peekNextServeris implemented and overrides the same method from the parent class or interface. - The method is declared to return an object of type
NatsUri. - The method is synchronized using the
synchronizedkeyword, which means only one thread can access this code block at a time. - Inside the synchronized block, the method checks if the
entryList(a list of server entries) has a size greater than 0. - If the
entryListhas at least one entry, the method returns thenuri(a field of the first entry in theentryList). - If the
entryListis empty, i.e., it doesn't have any entries, the method returnsnull.
The peekNextServer method in the NatsServerPool class returns the URI of the next server in the pool without removing it from the list. It achieves this by synchronizing access to the list of server entries and checking if the list is not empty. If it is not empty, it retrieves the first entry's URI from the list and returns it. Otherwise, it returns null.

public NatsUri nextServer()
@Override
public NatsUri nextServer() {
// 0. The list is already managed for qualified by connectFailed
// 1. Get the first item in the list, update it's time, add back to the end of list
synchronized (listLock) {
if (entryList.size() > 0) {
ServerPoolEntry entry = entryList.remove(0);
entry.lastAttempt = System.currentTimeMillis();
entryList.add(entry);
return entry.nuri;
}
return null;
}
}The nextServer() method is used to retrieve the next available server from the list of server entries in the NatsServerPool class. Here is a step-by-step description of what the method is doing:
- It first checks if the size of the entry list is greater than 0 to ensure there are available servers.
- If there are available servers, it retrieves the first server entry from the list using the
remove(0)method, which removes and returns the element at index 0. - It then updates the last attempt time of the server entry using
entry.lastAttempt = System.currentTimeMillis(), which sets the last attempt time to the current system time in milliseconds. - After updating the last attempt time, it adds the server entry back to the end of the list using
entryList.add(entry). - Finally, it returns the
NatsUri(URI of the server) from the retrieved server entry usingreturn entry.nuri.
If there are no available servers (i.e., the entry list is empty), it returns null.
Note: The code uses synchronization with the listLock object to ensure thread safety when accessing and modifying the entry list.
The nextServer method in the NatsServerPool class is used to retrieve the next available NatsUri from the server pool. Here's what the method does:
- It checks if the
entryList(which represents the list of available servers) is not empty. - If the
entryListis not empty, it removes the first server entry (ServerPoolEntry) from the list. - It updates the
lastAttempttimestamp of the removed server entry with the current system time in milliseconds. - It adds the updated server entry back to the end of the
entryList. - Finally, it returns the
NatsUriassociated with the removed server entry.
If the entryList is empty, it returns null, indicating that no servers are currently available in the pool.

public List resolveHostToIps(String host)
@Override
public List<String> resolveHostToIps(String host) {
// 1. if options.isNoResolveHostnames(), return empty list
if (options.isNoResolveHostnames()) {
return null;
}
// 2. else, try to resolve the hostname, adding results to list
List<String> results = new ArrayList<>();
try {
InetAddress[] addresses = InetAddress.getAllByName(host);
for (InetAddress a : addresses) {
results.add(a.getHostAddress());
}
} catch (UnknownHostException ignore) {
// A user might have supplied a bad host, but the server shouldn't.
// Either way, nothing much we can do.
}
// 3. no results, return null.
if (results.size() == 0) {
return null;
}
// 4. if results has more than 1 and allowed to randomize, shuffle the list
if (results.size() > 1 && !options.isNoRandomize()) {
Collections.shuffle(results, ThreadLocalRandom.current());
}
return results;
}The resolveHostToIps method in the io.nats.client.impl.NatsServerPool class is used to resolve a hostname to a list of IP addresses. Here is a step-by-step breakdown of what the method does:
- Check if the
options.isNoResolveHostnames()flag is set. If it is, return an empty list. - If the flag is not set:
- Create an empty list to store the IP addresses.
- Attempt to resolve the hostname using
InetAddress.getAllByName(host). - For each resolved
InetAddress, add its host address to the results list.
- If no IP addresses were resolved, return
null. - If more than one IP address is resolved and randomization is allowed (i.e.,
!options.isNoRandomize()), shuffle the results list usingCollections.shuffle()and the current thread's random number generator. - Return the list of resolved IP addresses.
Note: If an exception occurs during the hostname resolution process (e.g., UnknownHostException), it is caught and ignored. This is to handle cases where a user might have provided an invalid hostname, but the server should not be affected.
The resolveHostToIps method is used to resolve the IP addresses associated with a given hostname.
Here is a step-by-step breakdown of what the method does:
- Check if the
options.isNoResolveHostnames()flag is set. If so, return an empty list. - If the flag is not set, attempt to resolve the hostname using
InetAddress.getAllByName(host). This method returns an array ofInetAddressobjects, representing the resolved IP addresses. - Iterate over each
InetAddressobject in the array and add its host address to theresultslist. - If no IP addresses were resolved (i.e., the
resultslist is empty), returnnull. - If the
resultslist has more than one IP address and theoptions.isNoRandomize()flag is not set, shuffle theresultslist usingCollections.shuffle()and aThreadLocalRandomobject. - Finally, return the
resultslist, which contains the resolved IP addresses.
Overall, the resolveHostToIps method checks if hostname resolution is enabled, resolves the hostname to IP addresses, and applies optional randomization to the list of IP addresses before returning them.

public void connectSucceeded(NatsUri nuri)
@Override
public void connectSucceeded(NatsUri nuri) {
// 1. Work from the end because nextServer moved the one being tried to the end
// 2. If we find the server in the list...
// 2.1. remember it and
// 2.2. reset failed attempts
synchronized (listLock) {
for (int x = entryList.size() - 1; x >= 0; x--) {
ServerPoolEntry entry = entryList.get(x);
if (entry.nuri.equals(nuri)) {
lastConnected = nuri;
entry.failedAttempts = 0;
return;
}
}
}
}The connectSucceeded method in the NatsServerPool class is responsible for handling the successful connection to a server. Below is a step-by-step description of what this method does:
- Start by acquiring a lock on the
listLockobject to ensure thread safety. - Iterate over the
entryList, starting from the end and moving towards the beginning. - For each
ServerPoolEntryin theentryList, check if itsnuri(NatsUri) is equal to thenuripassed as a parameter to the method. - If a matching server is found:
- Set the
lastConnectedfield to thenurivalue, indicating that this server was the last successfully connected server. - Reset the
failedAttemptscounter for this server entry, since the connection was successful. - Exit the method.
- Set the
- Release the lock acquired in step 1.
It's worth noting that the lastConnected and failedAttempts fields are likely used for tracking the state of server connection attempts and are not directly related to the logic of the connectSucceeded method.
The connectSucceeded method in the NatsServerPool class handles the connection success event for a NATS server.
It starts by working from the end of the server list because the nextServer method moved the server being tried to the end.
Then, if it finds the server in the list, it performs the following steps:
- It stores the URI of the server that was successfully connected.
- It resets the failed attempts for that server to 0.
This method is synchronized to ensure thread safety when accessing and modifying the entryList.

public void connectFailed(NatsUri nuri)
@Override
public void connectFailed(NatsUri nuri) {
// 1. Work from the end because nextServer moved the one being tried to the end
// 2. If we find the server in the list...
// 2.1. increment failed attempts
// 2.2. if failed attempts reaches max, remove it from the list
synchronized (listLock) {
for (int x = entryList.size() - 1; x >= 0; x--) {
ServerPoolEntry entry = entryList.get(x);
if (entry.nuri.equals(nuri)) {
if (++entry.failedAttempts >= maxConnectAttempts) {
entryList.remove(x);
}
return;
}
}
}
}The connectFailed method in the NatsServerPool class is used to handle the case where a connection to a server has failed. Here is a step-by-step description of what the method does:
-
The method starts by synchronizing on the
listLock, ensuring that only one thread can execute this code block at a time. -
The method iterates over the
entryList, which is a list ofServerPoolEntryobjects representing the available NATS servers. -
The iteration starts from the end of the
entryListand moves towards the beginning. This is done because thenextServermethod (not shown in the provided code) moves the server being tried to the end of the list. -
For each
ServerPoolEntryobject in theentryList, the method checks if itsnuri(NATS URI) is equal to the failed NATS URI (nuri) passed as a parameter to theconnectFailedmethod. -
If the
nuriof aServerPoolEntrymatches the failed NATS URI, then it means that this server connection has failed. -
In this case, the method increments the
failedAttemptscounter of theServerPoolEntryobject. -
If the incremented
failedAttemptscounter reaches the maximum allowed connect attempts (maxConnectAttempts), then the server is removed from theentryListby invoking theremovemethod on theentryListwith the index (x) of the failed server. -
After removing the server, the method returns and completes its execution.
Note: The provided code does not handle the scenario where the failed server is not found in the entryList. If the failed server is not in the entryList, nothing happens, and the method returns without making any modifications.
The connectFailed method in the NatsServerPool class is used to handle the case when a connection to a NATS server fails.
In the method body, it iterates over the entries in the server pool list in reverse order, starting from the end. It checks if the current entry's NatsUri (representing the server's URI) matches the failed NatsUri. If a match is found, it increments the number of failed attempts for that server entry. If the number of failed attempts exceeds the maximum allowed connect attempts, the server entry is removed from the list.
The method is synchronized, ensuring that access to the server pool list is thread-safe.

private int findEquivalent(List<NatsUri> list, NatsUri toFind) {
for (int i = 0; i < list.size(); i++) {
NatsUri nuri = list.get(i);
if (nuri.equivalent(toFind)) {
return i;
}
}
return -1;
}The findEquivalent method in the NatsServerPool class is a private method that takes in two parameters: a List of NatsUri objects called list and a NatsUri object called toFind. The purpose of this method is to find the index of a NatsUri object in the given list that is equivalent to the toFind object.
Here is a step-by-step description of the method:
- Initialize a for loop that iterates over the
listfrom index0tolist.size() - 1. - On each iteration, get the
NatsUriobject at the current index (i) from thelistand assign it to the variablenuri. - Check if the
nuriobject is equivalent to thetoFindobject by invoking theequivalentmethod onnuri. This method likely compares the relevant properties of the twoNatsUriobjects and returns a boolean indicating if they are equivalent. - If the
nuriobject is equivalent to thetoFindobject, return the current index (i) as the result. - If no equivalent
NatsUriobject is found in the list, the loop completes without returning and the method proceeds to the next step. - Return
-1to indicate that no equivalent object was found in the list.
In summary, the findEquivalent method searches for a NatsUri object in the given list that is equivalent to the toFind object and returns its index. If no equivalent object is found, it returns -1.
The findEquivalent method, which is defined in the NatsServerPool class in the io.nats.client.impl package, searches for an equivalent NatsUri object within a given list.
It iterates through the list using a for loop, comparing each NatsUri object in the list to the toFind parameter. If it finds an equivalent NatsUri object, it returns the index of that object in the list. If no equivalent object is found, it returns -1.
This method can be used to quickly find the index of a specific NatsUri object within a list of NatsUri objects.
NatsStreamContext
The NatsStreamContext is a class that implements the StreamContext interface. It is a simplified experimental class that may undergo changes in future versions.
public IterableConsumer orderedConsume(OrderedConsumerConfig config, ConsumeOptions consumeOptions) throws IOException, JetStreamApiException
@Override
public IterableConsumer orderedConsume(OrderedConsumerConfig config, ConsumeOptions consumeOptions) throws IOException, JetStreamApiException {
Validator.required(config, "Ordered Consumer Config");
Validator.required(consumeOptions, "Consume Options");
ConsumerConfiguration cc = getBackingConsumerConfiguration(config);
PullSubscribeOptions pso = new OrderedPullSubscribeOptionsBuilder(streamName, cc).build();
return new NatsIterableConsumer(new SimplifiedSubscriptionMaker(js, pso, cc.getFilterSubject()), consumeOptions, null);
}The orderedConsume method is defined in the NatsStreamContext class from the package io.nats.client.impl.
This method requires two parameters: config of type OrderedConsumerConfig and consumeOptions of type ConsumeOptions. It also throws two exceptions: IOException and JetStreamApiException.
Here is a step-by-step description of what the orderedConsume method does based on its body:
-
Validate that the
configparameter is not null using theValidator.requiredmethod. If it is null, throw an exception with the message "Ordered Consumer Config is required." -
Validate that the
consumeOptionsparameter is not null using theValidator.requiredmethod. If it is null, throw an exception with the message "Consume Options is required." -
Retrieve the consumer configuration object for the given
configusing thegetBackingConsumerConfigurationmethod. -
Create a
PullSubscribeOptionsobject namedpsoby using theOrderedPullSubscribeOptionsBuilderwith thestreamName(which is assumed to be a class field) and theccconsumer configuration object. -
Create a new instance of the
NatsIterableConsumerclass, passing the following parameters:- An instance of the
SimplifiedSubscriptionMakerclass, initialized with thejsobject (which is assumed to be a class field), thepsopull subscribe options, and the filter subject from the consumer configuration. - The
consumeOptionsparameter. -
nullfor the last parameter.
- An instance of the
-
Return the created instance of the
NatsIterableConsumerclass as anIterableConsumer.
Overall, this method validates the input parameters, creates a PullSubscribeOptions object, and then creates and returns an instance of the NatsIterableConsumer class with the provided options and configuration.
The orderedConsume method is used to create an iterable consumer for consuming messages in a specific order from a NATS stream.
The method takes in two parameters: config, which is an object of type OrderedConsumerConfig, and consumeOptions, which is an object of type ConsumeOptions.
First, the method checks if both config and consumeOptions are not null; if either of them is null, it throws an IOException or JetStreamApiException.
Next, it creates a ConsumerConfiguration object cc by calling the getBackingConsumerConfiguration method with the config parameter.
Then, it builds a PullSubscribeOptions object pso using the streamName and cc objects.
Finally, it returns a new instance of NatsIterableConsumer class, initialized with a SimplifiedSubscriptionMaker object created using the js, pso, and cc.getFilterSubject() objects, along with the consumeOptions parameter and a null value for an optional context parameter.

public MessageConsumer orderedConsume(OrderedConsumerConfig config, MessageHandler handler, ConsumeOptions consumeOptions) throws IOException, JetStreamApiException
@Override
public MessageConsumer orderedConsume(OrderedConsumerConfig config, MessageHandler handler, ConsumeOptions consumeOptions) throws IOException, JetStreamApiException {
Validator.required(config, "Ordered Consumer Config");
Validator.required(handler, "Message Handler");
Validator.required(consumeOptions, "Consume Options");
ConsumerConfiguration cc = getBackingConsumerConfiguration(config);
PullSubscribeOptions pso = new OrderedPullSubscribeOptionsBuilder(streamName, cc).build();
return new NatsMessageConsumer(new SimplifiedSubscriptionMaker(js, pso, cc.getFilterSubject()), handler, consumeOptions, null);
}The method orderedConsume is a method override in the class io.nats.client.impl.NatsStreamContext. It returns a MessageConsumer object. Here is a step-by-step description of what the method does based on its body:
- It takes three parameters:
config,handler, andconsumeOptions. - It validates that all three parameters are not null using the
Validator.requiredmethod. If any of the parameters are null, it will throw anIllegalArgumentExceptionwith a descriptive error message. - It creates a
ConsumerConfigurationobject by calling thegetBackingConsumerConfigurationmethod with theconfigparameter. - It creates a
PullSubscribeOptionsobject by calling the constructor ofOrderedPullSubscribeOptionsBuilderwith thestreamNameandccparameters. - It creates a new instance of
NatsMessageConsumerby passing the following arguments:- A
SimplifiedSubscriptionMakerobject with thejs,pso, andcc.getFilterSubject()parameters. - The
handlerparameter. - The
consumeOptionsparameter. -
nullfor an optionalexecutorparameter.
- A
- It returns the created
NatsMessageConsumerobject as the result of the method.
That's the step-by-step description of what the orderedConsume method does based on its body.
The method orderedConsume in the NatsStreamContext class is used to create an ordered message consumer for a specified stream and configures it based on the provided parameters.
The method takes in an OrderedConsumerConfig object, a MessageHandler object, and ConsumeOptions object as input parameters. These parameters are used to configure the consumer.
Internally, the method checks that the provided parameters are not null using the Validator.required method.
It then creates a ConsumerConfiguration object based on the provided OrderedConsumerConfig using the getBackingConsumerConfiguration method.
Next, it creates a PullSubscribeOptions object using the OrderedPullSubscribeOptionsBuilder with the stream name and the previously created ConsumerConfiguration.
Finally, it creates a new NatsMessageConsumer object passing in a SimplifiedSubscriptionMaker with the provided parameters along with consumeOptions and null for the last two required parameters.
In summary, the orderedConsume method creates and configures an ordered message consumer for a specified stream using the provided parameters and returns an instance of NatsMessageConsumer.

MessageManager
The MessageManager class is an abstract class that provides functionality for managing messages. It serves as a base class for other classes that specialize in handling different types of messages.
protected void trackJsMessage(Message msg) {
synchronized (stateChangeLock) {
NatsJetStreamMetaData meta = msg.metaData();
lastStreamSeq = meta.streamSequence();
lastConsumerSeq++;
}
}The trackJsMessage method, defined in the io.nats.client.impl.MessageManager class, performs the following steps based on its body:
- Acquire the lock associated with the
stateChangeLockobject for synchronization purposes. - Retrieve the metadata associated with the provided
Messageobject,msg, by calling themetaData()method on it. The retrieved metadata is stored in themetavariable. - Extract and assign the stream sequence number from the retrieved metadata to the
lastStreamSeqvariable. - Increment the
lastConsumerSeqvariable by one. - Release the lock associated with the
stateChangeLockobject.
The trackJsMessage method, defined in the MessageManager class of the io.nats.client.impl package, is used to track the state of a JavaScript message in the NATS messaging system.
In detail, this method takes a Message object as input and performs the following actions:
- It ensures that the execution of this method is thread-safe by using synchronization on the
stateChangeLockvariable. - It extracts the metadata of the message using the
metaData()method and assigns it to themetavariable of typeNatsJetStreamMetaData. - It updates the value of the
lastStreamSeqvariable to match thestreamSequence()value from the message metadata. This represents the last sequence number received from the stream. - It increments the value of the
lastConsumerSeqvariable by 1. This represents the last consumed sequence number by the consumer.
Overall, the trackJsMessage method helps maintain and update the state of JavaScript messages in a NATS system, specifically keeping track of the last received sequence number from the stream and the last consumed sequence number by the consumer.

protected void configureIdleHeartbeat(Duration configIdleHeartbeat, long configMessageAlarmTime) {
synchronized (stateChangeLock) {
idleHeartbeatSetting = configIdleHeartbeat == null ? 0 : configIdleHeartbeat.toMillis();
if (idleHeartbeatSetting <= 0) {
alarmPeriodSetting = 0;
hb = false;
} else {
if (configMessageAlarmTime < idleHeartbeatSetting) {
alarmPeriodSetting = idleHeartbeatSetting * THRESHOLD;
} else {
alarmPeriodSetting = configMessageAlarmTime;
}
hb = true;
}
}
}The method configureIdleHeartbeat in class io.nats.client.impl.MessageManager is responsible for configuring the idle heartbeat settings and the message alarm time. Here is a step-by-step description of what this method does:
-
Start synchronized block: The method starts by entering a synchronized block using
stateChangeLockas the lock object. This ensures that the state changes made by this method are thread-safe. -
Configure the idle heartbeat setting: The method starts by setting the
idleHeartbeatSettingbased on the providedconfigIdleHeartbeat. IfconfigIdleHeartbeatisnull,idleHeartbeatSettingis set to 0. Otherwise, it is set to the duration in milliseconds represented byconfigIdleHeartbeat. -
Check idle heartbeat setting: The method checks if the
idleHeartbeatSettingis less than or equal to 0. If it is, it means that the idle heartbeat should be disabled. In this case, thealarmPeriodSettingis set to 0 andhb(heartbeat setting) is set tofalse. -
Configure alarm period setting: If the
idleHeartbeatSettingis greater than 0, it means that the idle heartbeat should be enabled. In this case, the method checks if the providedconfigMessageAlarmTimeis less than theidleHeartbeatSetting. If it is, thealarmPeriodSettingis set to theidleHeartbeatSettingmultiplied by a threshold value (THRESHOLD). This threshold value determines the interval between message alarms. -
Configure heartbeat setting: Finally, the method sets the
hb(heartbeat setting) totrueindicating that the heartbeat should be enabled. -
End synchronized block: The synchronized block ends, allowing other threads to access the shared state.
Please note that this description is based on the provided code snippet and may not cover the full context or functionality of the class.
The configureIdleHeartbeat method is used to set the idle heartbeat configuration and message alarm time for a message manager.
The method takes two parameters: configIdleHeartbeat and configMessageAlarmTime.
Inside the method, the idle heartbeat setting is configured based on the value of configIdleHeartbeat. If configIdleHeartbeat is null or less than or equal to 0, the idle heartbeat setting is set to 0 and the alarm period setting is also set to 0, indicating that the heartbeat functionality is disabled.
If configIdleHeartbeat is greater than 0, the method checks if configMessageAlarmTime is less than configIdleHeartbeat. If it is, the alarm period setting is set to configIdleHeartbeat multiplied by a constant THRESHOLD. Otherwise, the alarm period setting is set to configMessageAlarmTime.
Finally, the hb flag is set to true if the idle heartbeat setting is greater than 0, indicating that the heartbeat functionality is enabled. The configuration is performed in a synchronized block to ensure thread safety.

protected void initOrResetHeartbeatTimer() {
synchronized (stateChangeLock) {
shutdownHeartbeatTimer();
heartbeatTimer = new Timer();
heartbeatTimerTask = new TimerTask() {
@Override
public void run() {
long sinceLast = System.currentTimeMillis() - lastMsgReceived;
if (sinceLast > alarmPeriodSetting) {
handleHeartbeatError();
}
}
};
heartbeatTimer.schedule(heartbeatTimerTask, alarmPeriodSetting, alarmPeriodSetting);
}
}The initOrResetHeartbeatTimer method in the MessageManager class is used to initialize or reset a heartbeat timer.
-
First, the method acquires a lock on the
stateChangeLockobject to ensure thread safety. -
Then, it calls the
shutdownHeartbeatTimermethod to stop any existing heartbeat timer. -
Next, it creates a new instance of the
Timerclass and assigns it to theheartbeatTimermember variable. -
It also creates a new instance of the
TimerTaskanonymous inner class, which overrides therunmethod. -
Inside the
runmethod, it calculates the time intervalsinceLastby subtracting the current time in milliseconds from thelastMsgReceivedvariable. -
If the
sinceLastinterval exceeds thealarmPeriodSetting, it calls thehandleHeartbeatErrormethod to handle the heartbeat error. -
Finally, it schedules the
heartbeatTimerTaskto run repeatedly at a fixed rate specified by thealarmPeriodSetting.
Note: The method uses a synchronized block to ensure that only one thread can access and modify the timer and timer task objects at a time. This helps prevent race conditions and conflicts between multiple threads.
The initOrResetHeartbeatTimer method, defined in the io.nats.client.impl.MessageManager class, is responsible for initializing or resetting a heartbeat timer.
The method first acquires a lock to ensure thread safety. It then shuts down any existing heartbeat timer by invoking the shutdownHeartbeatTimer method.
A new Timer is created, along with a TimerTask that defines the task to be executed by the timer. In this case, the task checks the time elapsed since the last message was received. If the elapsed time exceeds a predefined alarm period, the handleHeartbeatError method is called.
Finally, the timer task is scheduled to run at regular intervals specified by the alarmPeriodSetting parameter.

protected void shutdownHeartbeatTimer() {
synchronized (stateChangeLock) {
if (heartbeatTimer != null) {
heartbeatTimer.cancel();
heartbeatTimer = null;
}
}
}The method shutdownHeartbeatTimer() defined in class io.nats.client.impl.MessageManager is responsible for stopping and shutting down the heartbeat timer. The step-by-step description is as follows:
- Acquire the lock on the
stateChangeLockobject to ensure thread safety. - Check if the
heartbeatTimerobject is not null (indicating that the timer is currently running). - If the
heartbeatTimeris not null, cancel the timer by calling itscancel()method. - Set the value of
heartbeatTimerto null, indicating that the timer has been stopped and shut down. - Release the lock on the
stateChangeLockobject.
The method essentially stops and shuts down the heartbeat timer by canceling it and setting the heartbeatTimer variable to null.
The shutdownHeartbeatTimer method defined in the io.nats.client.impl.MessageManager class is responsible for shutting down the heartbeat timer.
It does this by acquiring a lock on the stateChangeLock object to ensure thread safety. It then checks if the heartbeatTimer object is not null. If it is not null, it cancels the timer and sets it to null, effectively shutting it down.
This method ensures that the heartbeat timer is properly stopped, preventing any unwanted timers from running and consuming system resources.

ConsumerListReader
This ConsumerListReader class is an extension of the AbstractListReader class. It is responsible for reading and processing consumer lists.
protected void processItems(List items)
@Override
protected void processItems(List<JsonValue> items) {
for (JsonValue v : items) {
consumers.add(new ConsumerInfo(v));
}
}The processItems method, defined in the ConsumerListReader class within the io.nats.client.impl package, is responsible for populating a list of ConsumerInfo objects based on the input list of JsonValue items.
Here is a step-by-step description of the method:
-
The method is marked with the
@Overrideannotation, indicating that it overrides a method from a superclass or interface. -
The method is declared with a protected access modifier, making it accessible only within the same package and subclasses.
-
The method takes a single parameter,
items, of typeList<JsonValue>. This parameter represents the input list ofJsonValueitems that need to be processed. -
Inside the method, a for-each loop is used to iterate over each
JsonValueobject in theitemslist. -
Within the loop, a new instance of
ConsumerInfois created using the currentJsonValueobject (v), and it is added to theconsumerslist. Theaddmethod is called on theconsumerslist, indicating that a newConsumerInfoobject is being added to the list. -
Once the for-each loop finishes iterating over all the
items, the method execution is complete.
Please note that without further context or the definition of the ConsumerInfo class, it is not possible to provide a more detailed explanation of what happens inside the ConsumerInfo constructor or how the consumers list is used after the method execution.
The processItems method, which is defined in the ConsumerListReader class in the io.nats.client.impl package, is responsible for processing a list of JsonValue items.
In the method body, it iterates over each JsonValue item in the items list. For each item, it creates a new ConsumerInfo instance using the JsonValue item and adds it to the consumers list.
Overall, the purpose of this method is to parse a list of JsonValue items and convert them into ConsumerInfo objects, adding them to the consumers list for further processing or storage.

NatsMessage
The NatsMessage class is a public class that implements the Message interface. It represents a message in the NATS messaging system. This class provides functionality for creating and handling NATS messages.
protected void finishConstruct() {
int replyToLen = replyTo == null ? 0 : replyTo.length();
if (headers != null && !headers.isEmpty()) {
headerLen = headers.serializedLength();
} else {
headerLen = 0;
}
int headerAndDataLen = headerLen + dataLen;
// initialize the builder with a reasonable length, preventing resize in 99.9% of the cases
// 32 for misc + subject length doubled in case of utf8 mode + replyToLen + totLen (headerLen + dataLen)
ByteArrayBuilder bab = new ByteArrayBuilder(32 + (subject.length() * 2) + replyToLen + headerAndDataLen);
// protocol come first
if (headerLen > 0) {
bab.append(HPUB_SP_BYTES, 0, HPUB_SP_BYTES_LEN);
} else {
bab.append(PUB_SP_BYTES, 0, PUB_SP_BYTES_LEN);
}
// next comes the subject
bab.append(subject.getBytes(UTF_8)).append(SP);
// reply to if it's there
if (replyToLen > 0) {
bab.append(replyTo.getBytes(UTF_8)).append(SP);
}
// header length if there are headers
if (headerLen > 0) {
bab.append(Integer.toString(headerLen).getBytes(US_ASCII)).append(SP);
}
// payload length
bab.append(Integer.toString(headerAndDataLen).getBytes(US_ASCII));
protocolBab = bab;
// One CRLF. This is just how controlLineLength is defined.
controlLineLength = protocolBab.length() + 2;
// The 2nd CRLFs
sizeInBytes = controlLineLength + headerAndDataLen + 2;
}The finishConstruct method is responsible for finalizing the construction of the NatsMessage object by setting the appropriate values for various variables based on the provided BODY.
Here is a step-by-step description of what the method does:
- It calculates the length of the
replyTostring, assigning it to thereplyToLenvariable. - It checks if the
headersmap is not null and not empty. If it is not null and not empty, it calculates the serialized length of theheadersand assigns it to theheaderLenvariable. Otherwise, it setsheaderLento 0. - It calculates the total length of the headers and data, assigning it to the
headerAndDataLenvariable. - It initializes a
ByteArrayBuilderobject calledbabwith an initial capacity calculated based on the lengths of different strings and variables. - It appends the appropriate protocol bytes (
HPUB_SP_BYTESorPUB_SP_BYTES) depending on the value ofheaderLen. - It appends the subject string converted to bytes using UTF-8 encoding, followed by a space character.
- It appends the
replyTostring converted to bytes using UTF-8 encoding, followed by a space character, ifreplyToLenis greater than 0. - It appends the length of the headers (
headerLen) as a string converted to bytes using US-ASCII encoding, followed by a space character, ifheaderLenis greater than 0. - It appends the total length of the headers and data (
headerAndDataLen) as a string converted to bytes using US-ASCII encoding. - It assigns the
babobject to theprotocolBabvariable. - It calculates the length of the
protocolBabobject plus 2 (for a CRLF), assigning it to thecontrolLineLengthvariable. - It calculates the size of the message in bytes by adding
controlLineLength,headerAndDataLen, and 2 (for an additional CRLF), assigning it to thesizeInBytesvariable.
This completes the finalization process of the NatsMessage object based on the provided BODY.
The finishConstruct method in the NatsMessage class is responsible for finalizing the construction of a NATS message.
Here are the steps performed by the method:
- It calculates the length of the replyTo string.
- It checks if there are any headers present and calculates the length of the serialized headers.
- It calculates the total length of the headers and data.
- It initializes a
ByteArrayBuilderwith a length calculated based on the various components of the message. - It appends the appropriate protocol bytes to the
ByteArrayBuilderdepending on whether headers are present or not. - It appends the subject bytes to the
ByteArrayBuilder, followed by a space. - If a replyTo string is present, it appends the replyTo bytes to the
ByteArrayBuilder, followed by a space. - If headers are present, it appends the serialized length of the headers to the
ByteArrayBuilder, followed by a space. - It appends the payload length to the
ByteArrayBuilder. - It assigns the
ByteArrayBuilderto theprotocolBabfield of theNatsMessageobject. - It calculates the controlLineLength by adding 2 to the length of the
protocolBab. - It calculates the
sizeInBytesby adding thecontrolLineLengthto theheaderAndDataLen, both plus 2.
In summary, the finishConstruct method calculates and builds the protocol representation of a NATS message, including the subject, replyTo, headers, and payload length.

int copyNotEmptyHeaders(int destPosition, byte[] dest) {
if (headers != null && !headers.isEmpty()) {
return headers.serializeToArray(destPosition, dest);
}
return 0;
}The method copyNotEmptyHeaders is defined in the class io.nats.client.impl.NatsMessage and it takes two parameters: destPosition and dest, which represent the position in the destination array where the headers should be copied and the destination array itself, respectively.
The method first checks whether the variable headers is not null and not empty. The headers variable refers to a collection of headers associated with the message.
If headers is not null and not empty, the method calls the serializeToArray method on the headers object, passing in the destPosition and dest parameters. This method serializes the headers into a byte array and copies them into the dest array, starting at the specified position. The serializeToArray method returns the number of bytes copied.
If headers is null or empty, the method returns 0, indicating that no headers were copied.
In summary, the copyNotEmptyHeaders method copies non-empty headers associated with the message into a byte array at the specified position. If there are no headers or they are empty, the method returns 0.
The copyNotEmptyHeaders method defined in the NatsMessage class of the io.nats.client.impl package is used to copy any non-empty headers of a message into a destination byte array starting from a specified position.
Here is what the method does:
-
If the
headersfield of theNatsMessageobject is not null and is not empty:- It calls the
serializeToArraymethod on theheadersobject and passes thedestPositionanddestas parameters. - This method serializes the headers into a byte array starting from the specified position, and returns the number of bytes written.
- The number of bytes written is then returned by the
copyNotEmptyHeadersmethod.
- It calls the
-
If the
headersfield is null or empty:- The method simply returns 0 indicating that no headers were copied.
Overall, the copyNotEmptyHeaders method is used to serialize non-empty headers of a NatsMessage object into a byte array at a specified position.

private String dataToString() {
if (data.length == 0) {
return "<no data>";
}
String s = new String(data, UTF_8);
int at = s.indexOf("io.nats.jetstream.api");
if (at == -1) {
return s.length() > 27 ? s.substring(0, 27) + "..." : s;
}
int at2 = s.indexOf('"', at);
return s.substring(at, at2);
}The dataToString method in the NatsMessage class is used to convert the data property of the instance to a String representation.
Here is a step-by-step description of what the method does based on its implementation:
-
If the length of the
dataarray is 0, the method returns the string "". This means that if there is no data present, the method will just return this string. -
Otherwise, it creates a new String
sby decoding thedataarray using the UTF-8 character set. -
It then searches for the occurrence of the string "io.nats.jetstream.api" in
s. If this string is not found, it proceeds to step 6. -
If the string is found, it searches for the position of the next double quote character (
") starting from the position of the found string. This is done by calling theindexOfmethod onswith the starting position set as the found index. The result is stored in theat2variable. -
It extracts a substring from
sstarting from the found string position (at) and ending at the previous double quote position (at2). This substring represents a portion of the originaldatathat is enclosed within double quotes. This substring is then returned as the result of the method. -
If the string "io.nats.jetstream.api" is not found in
s, it checks if the length ofsis greater than 27. If it is, it extracts the first 27 characters ofsand appends "..." to it. If it is not greater than 27, it simply returnssas is.
In summary, the dataToString method converts the data property to a string representation with specific formatting rules based on the content of the data.
The dataToString method in the NatsMessage class is used to convert the byte array data of a message to a string representation.
If the data array is empty, it returns "<no data>". Otherwise, it converts the byte array to a string using the UTF-8 encoding.
It then checks if the resulting string contains the substring "io.nats.jetstream.api". If it does not, it returns a substring of the string up to the first 27 characters (or the whole string if it is less than 27 characters).
If the string does contain the substring "io.nats.jetstream.api", it finds the index of the next double quote character (") after the substring and returns a substring from the index of "io.nats.jetstream.api" up to the index of the next double quote character.

MemoryAuthHandler
The MemoryAuthHandler class is a public class that implements the AuthHandler interface. It provides authentication handling functionality.
private char[] extract(CharBuffer data, int headers) {
CharBuffer buff = CharBuffer.allocate(data.length());
boolean skipLine = false;
int headerCount = 0;
int linePos = -1;
while (data.length() > 0) {
char c = data.get();
linePos++;
// End of line, either we got it, or we should keep reading the new line
if (c == '\n' || c == '\r') {
if (buff.position() > 0) {
// we wrote something
break;
}
skipLine = false;
// so we can start right up
linePos = -1;
continue;
}
// skip to the new line
if (skipLine) {
continue;
}
// Ignore whitespace
if (Character.isWhitespace(c)) {
continue;
}
// If we are on a - skip that line, bump the header count
if (c == '-' && linePos == 0) {
skipLine = true;
headerCount++;
continue;
}
// Skip the line, or add to buff
if (headerCount == headers) {
buff.put(c);
}
}
// check for naked value
if (buff.position() == 0 && headers == 1) {
data.position(0);
while (data.length() > 0) {
char c = data.get();
if (c == '\n' || c == '\r' || Character.isWhitespace(c)) {
if (buff.position() > 0) {
// we wrote something
break;
}
continue;
}
buff.put(c);
}
buff.flip();
} else {
buff.flip();
}
char[] retVal = new char[buff.length()];
buff.get(retVal);
buff.clear();
for (int i = 0; i < buff.capacity(); i++) {
buff.put('\0');
}
return retVal;
}
```## NatsFetchConsumer
**NatsFetchConsumer**
`NatsFetchConsumer` is a class that extends `NatsMessageConsumerBase` and implements the `FetchConsumer` interface. It is used for consuming messages from a NATS messaging system. This class provides the necessary functionalities to fetch messages from the NATS server and allows for easy integration and handling of message consumption in software applications.
### @Override
public Message nextMessage() throws InterruptedException, JetStreamStatusCheckedException
```java
@Override
public Message nextMessage() throws InterruptedException, JetStreamStatusCheckedException {
try {
if (startNanos == -1) {
startNanos = System.nanoTime();
}
long timeLeftMillis = (maxWaitNanos - (System.nanoTime() - startNanos)) / 1_000_000;
// if the manager thinks it has received everything in the pull, it means
// that all the messages are already in the internal queue and there is
// no waiting necessary
if (timeLeftMillis < 1 | pmm.pendingMessages < 1 || (pmm.trackingBytes && pmm.pendingBytes < 1)) {
// null means don't wait
return sub._nextUnmanagedNoWait(pullSubject);
}
return sub._nextUnmanaged(timeLeftMillis, pullSubject);
} catch (JetStreamStatusException e) {
throw new JetStreamStatusCheckedException(e);
} catch (IllegalStateException i) {
// this happens if the consumer is stopped, since it is
// drained/unsubscribed, so don't pass it on if it's expected
return null;
}
}This method is defined in the NatsFetchConsumer class located in the package io.nats.client.impl. It overrides the method nextMessage() and throws two exceptions InterruptedException and JetStreamStatusCheckedException.
-
Inside the method, there is a check to see if
startNanosvariable is -1. If it is, the current time in nanoseconds is assigned tostartNanos. -
The variable
timeLeftMillisis calculated by subtracting the difference betweenmaxWaitNanosand the difference between the current time in nanoseconds andstartNanos, and then dividing the result by 1,000,000. -
There is a conditional statement that checks if the value of
timeLeftMillisis less than 1 orpmm.pendingMessagesis less than 1 or (pmm.trackingBytesistrueandpmm.pendingBytesis less than 1). -
If the above condition is
true, the methodsub._nextUnmanagedNoWait(pullSubject)is called withpullSubjectas an argument and the result is returned. This means that there is no waiting required because all the messages are already in the internal queue. -
If the above condition is
false, the methodsub._nextUnmanaged(timeLeftMillis, pullSubject)is called withtimeLeftMillisandpullSubjectas arguments and the result is returned. This means that waiting is required for the next message to be available. -
If a
JetStreamStatusExceptionis caught during the execution, it is wrapped inside aJetStreamStatusCheckedExceptionand re-thrown. -
If an
IllegalStateExceptionis caught during the execution, it means that the consumer has been stopped, sonullis returned.
Note: This step-by-step description assumes that there are relevant definitions and imports available in the code that have not been provided.
The nextMessage method is an overridden method in the NatsFetchConsumer class that is used to retrieve the next message from a NATS server.
Here is a brief description of what the method does:
- It checks if the start nanoseconds value is set, and if not, it sets it to the current system nanoseconds value.
- It calculates the remaining time in milliseconds based on the maximum wait nanoseconds and the elapsed time since the start.
- It checks if there is no time left for waiting, or if there are no pending messages or pending bytes to receive. If any of these conditions are true, it returns the next unmanaged message without waiting.
- If there is still time left and there are pending messages or pending bytes, it calls the
_nextUnmanagedmethod with the calculated time left in milliseconds and the pull subject. - If any
JetStreamStatusExceptionoccurs during the process, it throws aJetStreamStatusCheckedException. - If an
IllegalStateExceptionoccurs, which means the consumer is stopped, it returnsnull.
Overall, this method retrieves the next message from the NATS server, either immediately or after waiting for a certain period of time.

NatsKeyValue
NatsKeyValue is a public class that extends the NatsFeatureBase class and implements the KeyValue interface. It provides functionality related to key value storage and retrieval using the NATS messaging system.
KeyValueEntry _get(String key, long revision) throws IOException, JetStreamApiException {
MessageInfo mi = _getBySeq(revision);
if (mi != null) {
KeyValueEntry kve = new KeyValueEntry(mi);
if (key.equals(kve.getKey())) {
return kve;
}
}
return null;
}The _get method is defined in the NatsKeyValue class within the io.nats.client.impl package. This method takes two parameters: key, which represents the key to be retrieved, and revision, which represents the revision number.
The purpose of this method is to retrieve a KeyValueEntry object from the key-value store based on the given key and revision number.
Here is a step-by-step description of what the _get method does:
-
Initialize a local variable
miof typeMessageInfoand assign it the result of the_getBySeqmethod call passing therevisionparameter. The_getBySeqmethod returns aMessageInfoobject corresponding to the given revision number. -
Check if the
miobject is not null, indicating that a validMessageInfoobject has been retrieved. -
If the
miobject is not null, create a newKeyValueEntryobject namedkveand pass themiobject as a parameter to its constructor. TheKeyValueEntryclass encapsulates the key-value pair information. -
Check if the given
keyis equal to the key value stored in thekveobject using theequalsmethod. -
If the given key matches the key stored in the
kveobject, return thekveobject. This means that a validKeyValueEntryobject for the given key and revision has been found. -
If the key does not match or if the
miobject is null, returnnull. This means that noKeyValueEntryobject was found for the given key and revision.
Note: This method can potentially throw two exceptions: IOException and JetStreamApiException. However, the exceptions are not handled within the method itself and are instead declared in the method signature, indicating that the caller of this method is responsible for handling these exceptions.
The _get method in the NatsKeyValue class is used to retrieve a key-value entry based on a given key and revision.
It first calls the _getBySeq method to retrieve the message info for the specified revision. If the message info is not null, it creates a KeyValueEntry object using the retrieved message info. Then it checks if the key of the KeyValueEntry object matches the given key. If it does, the method returns the KeyValueEntry object.
If the key does not match or if the message info is null, the method returns null.

public long create(String key, byte[] value) throws IOException, JetStreamApiException
@Override
public long create(String key, byte[] value) throws IOException, JetStreamApiException {
validateNonWildcardKvKeyRequired(key);
try {
return update(key, value, 0);
} catch (JetStreamApiException e) {
if (e.getApiErrorCode() == JS_WRONG_LAST_SEQUENCE) {
// must check if the last message for this subject is a delete or purge
KeyValueEntry kve = _get(key);
if (kve != null && kve.getOperation() != KeyValueOperation.PUT) {
return update(key, value, kve.getRevision());
}
}
throw e;
}
}The create method in the NatsKeyValue class is responsible for creating a key-value entry in the NATS Key-Value store.
Here is a step-by-step description of what the method does:
-
The method begins by overriding the
createmethod and defining its signature to accept aStringkey and abyte[]value. It also declares that it can throwIOExceptionandJetStreamApiException. -
The method first calls the
validateNonWildcardKvKeyRequiredmethod and passes thekeyas an argument. This method is responsible for validating that thekeyis not a wildcard pattern, ensuring that it can be used in the Key-Value store. If thekeyis invalid, an exception will be thrown. -
If the
keyis valid, the method tries to update the key-value entry by calling theupdatemethod with thekey,value, and0as arguments. Theupdatemethod is responsible for creating or updating the entry. -
If the
updatemethod throws aJetStreamApiExceptionwith the error codeJS_WRONG_LAST_SEQUENCE, it means that the last operation for this key was not a regular update. In this case, the method retrieves the current key-value entry for thekeyby calling the_getmethod. -
If a key-value entry exists for the
keyand the operation is notPUT(i.e., it is a delete or a purge operation), the method calls theupdatemethod again with thekey,value, and the revision obtained from the key-value entry. This ensures that the new value will have the correct revision. -
If the
JetStreamApiExceptiondoes not have the error codeJS_WRONG_LAST_SEQUENCEor there is no existing key-value entry for thekey, the originalJetStreamApiExceptionis re-thrown. -
Finally, if the
createmethod successfully creates or updates the key-value entry, it returns the revision of the entry.
The create method in the NatsKeyValue class allows you to create a new key-value entry in a NATS Key-Value Store.
The method takes a key and value as parameters. It first performs a validation check on the key to ensure that it doesn't contain any wildcard characters.
Then, it calls the update method with the key, value, and a revision of 0 to create the entry. If the update method throws a JetStreamApiException with an apiErrorCode of JS_WRONG_LAST_SEQUENCE, it means that the last message for the given key is a delete or purge operation. In this case, the method checks if the entry exists and its operation is not a PUT. If these conditions are met, it tries to update the entry again with the key, value, and the revision of the existing entry.
If any other type of JetStreamApiException occurs, it is rethrown to the caller.
In summary, the create method creates a new key-value entry in the NATS Key-Value Store, allowing for conditional updates if necessary.

public long update(String key, byte[] value, long expectedRevision) throws IOException, JetStreamApiException
@Override
public long update(String key, byte[] value, long expectedRevision) throws IOException, JetStreamApiException {
validateNonWildcardKvKeyRequired(key);
Headers h = new Headers().add(EXPECTED_LAST_SUB_SEQ_HDR, Long.toString(expectedRevision));
return _write(key, value, h).getSeqno();
}The update method in the io.nats.client.impl.NatsKeyValue class is used to update the value associated with a given key in a key-value store.
Here is a step-by-step description of what the method does based on its body:
-
It overrides the
updatemethod from the superclass, indicating that this implementation provides a specific behavior for updating a key-value pair. -
It takes four parameters as input:
-
key: The key for the value to be updated in the key-value store. -
value: The new value to be associated with the key. -
expectedRevision: The expected revision of the key-value pair being updated.
-
-
It first validates that the
keyprovided is not a wildcard key, as wildcard keys are not allowed for this operation. -
It creates a new
Headersobject, which is used to include client-defined metadata in the message headers. In this case, it adds a header with the keyEXPECTED_LAST_SUB_SEQ_HDRand the value set as the string representation ofexpectedRevision. -
It calls the
_writemethod, passing thekey,value, andHeadersobject as parameters. This method is responsible for writing the updated value to the key-value store. -
It then retrieves the sequence number (
seqno) of the message that was written using the_writemethod, which represents the position of the message in the server's history. -
Finally, it returns the
seqnoas the result of theupdatemethod.
In summary, the update method in the NatsKeyValue class validates the key, creates a header with the expected revision, writes the updated value to the key-value store, and returns the sequence number of the written message.
The update method in the NatsKeyValue class is used to update a value associated with a specific key in a key-value store.
It takes in three parameters:
-
key- the key of the value to be updated -
value- the new value to be associated with the key -
expectedRevision- the expected revision of the value, which is used for concurrency control
First, the method validates that a non-wildcard key is required. It then creates a new Headers object and adds the EXPECTED_LAST_SUB_SEQ_HDR (expected revision) header with the value converted to a string.
Next, the _write method is called with the key, value, and headers. This method is responsible for actually writing the updated value to the key-value store.
Finally, the method returns the sequence number (getSeqno()) of the write operation, indicating the position of the updated value in the store.

public NatsKeyValueWatchSubscription watch(String key, KeyValueWatcher watcher, KeyValueWatchOption... watchOptions) throws IOException, JetStreamApiException, InterruptedException
@Override
public NatsKeyValueWatchSubscription watch(String key, KeyValueWatcher watcher, KeyValueWatchOption... watchOptions) throws IOException, JetStreamApiException, InterruptedException {
validateKvKeyWildcardAllowedRequired(key);
validateNotNull(watcher, "Watcher is required");
return new NatsKeyValueWatchSubscription(this, key, watcher, watchOptions);
}The watch method in the NatsKeyValue class is used to create a watch subscription for a specific key in the Nats key-value store. Here is a step-by-step breakdown of what the method is doing:
-
The method is marked with the
@Overrideannotation, indicating that it overrides a method from a parent class or interface. -
The method signature includes the return type, which is
NatsKeyValueWatchSubscription. This is the type of object that will be returned by the method. -
The method takes several parameters:
-
keyis a string parameter representing the key for which the watch subscription is being created. -
watcheris an implementation of theKeyValueWatcherinterface. This object will be notified of changes to the watched key. -
watchOptionsis a variable number ofKeyValueWatchOptionobjects. These options allow customization of the watch subscription.
-
-
The method starts by calling the
validateKvKeyWildcardAllowedRequiredmethod with thekeyparameter. This method ensures that the key is valid and throws an exception if it contains a wildcard character that is not allowed. -
Next, the method calls the
validateNotNullmethod with thewatcherparameter. This method checks if thewatcherobject is not null and throws an exception if it is. -
Finally, the method creates a new
NatsKeyValueWatchSubscriptionobject, passingthis(the current instance ofNatsKeyValue),key,watcher, andwatchOptionsas arguments. This subscription object is then returned by the method.
Overall, the watch method validates the parameters, creates a watch subscription object, and returns it to the caller. This allows the caller to receive notifications about changes to the watched key.
The watch method in the io.nats.client.impl.NatsKeyValue class allows a software engineer to create a subscription for watching changes to a specific key in a key-value store.
This method takes in the following parameters:
-
key: The key to watch. -
watcher: TheKeyValueWatcherimplementation that will handle the incoming changes. -
watchOptions(optional): Additional options for the watch operation.
Before creating the subscription, the method performs some validation checks:
- It verifies that the provided
keyis allowed and not a wildcard. - It ensures that the
watcherparameter is not null.
Finally, the method returns a new instance of NatsKeyValueWatchSubscription that encapsulates the watch subscription for the given key, watcher, and options.

public List keys() throws IOException, JetStreamApiException, InterruptedException
@Override
public List<String> keys() throws IOException, JetStreamApiException, InterruptedException {
List<String> list = new ArrayList<>();
visitSubject(readSubject(">"), DeliverPolicy.LastPerSubject, true, false, m -> {
KeyValueOperation op = getOperation(m.getHeaders());
if (op == KeyValueOperation.PUT) {
list.add(new BucketAndKey(m).key);
}
});
return list;
}The keys() method defined in the io.nats.client.impl.NatsKeyValue class retrieves a list of keys.
Here is a step-by-step description of what the method does based on its body:
- The method overrides the
keys()method from its parent class. - It declares a new
ArrayListnamedlistto store the keys. - It calls the
visitSubject()method with the following parameters:- The result of the
readSubject(">")method call, which retrieves the subject to read from. - The
DeliverPolicy.LastPerSubjectenum value, which specifies the delivery policy. - The
trueboolean value, which indicates that the subject is a generic bucket. - The
falseboolean value, which indicates that it should not fetch the values. - A lambda expression that is executed for each message returned by the
visitSubject()method.- The lambda expression retrieves the operation specified in the message's headers.
- If the operation is a
PUToperation, it adds the key from the message to thelist.
- The result of the
- The method returns the
listcontaining the keys.
This method essentially visits a subject, retrieves messages, and extracts keys from messages that have a PUT operation specified in their headers.
The keys() method in the NatsKeyValue class is used to retrieve a list of keys stored in a KeyValue store using the NATS messaging system.
The method starts by creating an empty list to store the keys. It then visits the NATS subject corresponding to the readSubject(">") method, retrieves the last message for that subject, and processes it.
Inside the processing logic, it checks the operation type associated with the message headers using the getOperation() method. If the operation is a PUT operation, it extracts the key from the message and adds it to the list.
Finally, the method returns the list of keys.

public List history(String key) throws IOException, JetStreamApiException, InterruptedException
@Override
public List<KeyValueEntry> history(String key) throws IOException, JetStreamApiException, InterruptedException {
validateNonWildcardKvKeyRequired(key);
List<KeyValueEntry> list = new ArrayList<>();
visitSubject(readSubject(key), DeliverPolicy.All, false, true, m -> list.add(new KeyValueEntry(m)));
return list;
}The history method in the NatsKeyValue class is used to retrieve the history of values for a given key. It takes a key as a parameter and returns a List of KeyValueEntry objects.
Here is a step-by-step description of what the method is doing:
-
First, it validates that a non-wildcard key is required by calling the
validateNonWildcardKvKeyRequiredmethod with thekeyparameter. This ensures that the key is not a wildcard value, such as "*", and throws an exception if it is. -
Next, a new empty
ArrayListcalledlistis created to store theKeyValueEntryobjects. -
The
readSubjectmethod is called with thekeyparameter to determine the NATS subject to read the history from. -
The
visitSubjectmethod is called with the following parameters:- The NATS subject to visit, obtained from the
readSubjectmethod. - The
DeliverPolicy.Allflag, indicating that all messages should be delivered. - The
falseflag, indicating that a history call is being made (as opposed to a read or delete call). - The
trueflag, indicating that only the metadata of the messages should be retrieved. - A lambda expression that adds a new
KeyValueEntryobject to thelistfor each message visited.
The
visitSubjectmethod is responsible for visiting the messages in the NATS subject and performing the specified action on each message. In this case, it adds a newKeyValueEntryobject to thelistfor each message. - The NATS subject to visit, obtained from the
-
Finally, the method returns the
listcontaining theKeyValueEntryobjects, which represents the history of values for the given key.
That's it! This is a high-level overview of what the history method in the NatsKeyValue class is doing based on its body.
The history method in the NatsKeyValue class allows the retrieval of the historical entries for a given key.
Here is a brief description of what this method does:
- It first validates that a non-wildcard key is required, throwing an exception if a wildcard key is provided.
- It initializes an empty list to store the historical entries.
- It visits the subject related to the given key, uses the
DeliverPolicy.Allpolicy to ensure that all historical messages are retrieved. - It iterates over the messages received and adds them to the list as
KeyValueEntryinstances. - Finally, it returns the populated list of historical entries.

public void purgeDeletes(KeyValuePurgeOptions options) throws IOException, JetStreamApiException, InterruptedException
@Override
public void purgeDeletes(KeyValuePurgeOptions options) throws IOException, JetStreamApiException, InterruptedException {
long dmThresh = options == null ? KeyValuePurgeOptions.DEFAULT_THRESHOLD_MILLIS : options.getDeleteMarkersThresholdMillis();
ZonedDateTime limit;
if (dmThresh < 0) {
// long enough in the future to clear all
limit = DateTimeUtils.fromNow(600000);
} else if (dmThresh == 0) {
limit = DateTimeUtils.fromNow(KeyValuePurgeOptions.DEFAULT_THRESHOLD_MILLIS);
} else {
limit = DateTimeUtils.fromNow(-dmThresh);
}
List<String> keep0List = new ArrayList<>();
List<String> keep1List = new ArrayList<>();
visitSubject(streamSubject, DeliverPolicy.LastPerSubject, true, false, m -> {
KeyValueEntry kve = new KeyValueEntry(m);
if (kve.getOperation() != KeyValueOperation.PUT) {
if (kve.getCreated().isAfter(limit)) {
keep1List.add(new BucketAndKey(m).key);
} else {
keep0List.add(new BucketAndKey(m).key);
}
}
});
for (String key : keep0List) {
jsm.purgeStream(streamName, PurgeOptions.subject(readSubject(key)));
}
for (String key : keep1List) {
PurgeOptions po = PurgeOptions.builder().subject(readSubject(key)).keep(1).build();
jsm.purgeStream(streamName, po);
}
}The purgeDeletes method in the NatsKeyValue class is used to remove delete markers from the key-value store based on the specified options.
Here is a step-by-step description of what the method is doing:
-
The method starts by checking the provided
KeyValuePurgeOptionsparameter. If it isnull, it assigns the default delete markers threshold value (KeyValuePurgeOptions.DEFAULT_THRESHOLD_MILLIS) todmThreshvariable. Otherwise, it assigns the value from the options object. -
It then calculates the
limitvariable based on the value ofdmThresh. IfdmThreshis less than 0, it setslimitto a future date-time that is far enough in the future to clear all delete markers. IfdmThreshis 0, it setslimitto a date-time that is calculated by subtracting the default delete markers threshold from the current date-time. Otherwise, it setslimitto a date-time that is calculated by subtracting the absolute value ofdmThreshfrom the current date-time. -
Two empty lists,
keep0Listandkeep1List, are created to store the keys of the entries that need to be kept. -
The
visitSubjectmethod is called with thestreamSubjectparameter,DeliverPolicy.LastPerSubject,true, andfalseas arguments. This method visits all the messages in the NATS stream subject and executes the provided lambda functionm -> {...}for each message. -
Inside the lambda function, a new
KeyValueEntryobjectkveis created using the messagem. -
If the operation of the
kveobject is notKeyValueOperation.PUT, it means it is a delete marker. In this case, it checks if the created timestamp of the delete marker is after thelimitdate-time. If it is, it adds the key of the delete marker tokeep1List. Otherwise, it adds the key tokeep0List. -
After all messages have been visited, the method iterates over the
keep0Listand calls thepurgeStreammethod of thejsmobject (JetStreamManager) to purge the stream with the stream name using thePurgeOptions.subjectmethod with thereadSubjectmethod called on each key as the subject. -
Next, it iterates over the
keep1Listand creates a newPurgeOptionsobjectpousing thePurgeOptions.builder()method. It sets the subject ofpousing thereadSubjectmethod called on each key and sets thekeepproperty ofpoto 1. Finally, it calls thepurgeStreammethod of thejsmobject with the stream name andpoas arguments to purge the stream.
That's the step-by-step description of the purgeDeletes method in the NatsKeyValue class.
The purgeDeletes method is responsible for purging delete markers in the NatsKeyValue system based on the provided options.
Firstly, it retrieves the delete markers threshold from the options parameter. If no options are provided, it uses the default threshold from KeyValuePurgeOptions.
Next, it calculates the limit based on the threshold. If the threshold is less than 0, it sets the limit to a future time (600000ms from now) to clear all delete markers. If the threshold is 0, it sets the limit based on the default threshold. Otherwise, it sets the limit based on the negative threshold value.
Then, it iterates over the entries in the stream subject and processes them. For each entry, it converts it to a KeyValueEntry object and checks if the operation is not a PUT operation. If the entry's creation time is after the limit, it adds the entry's key to the keep1List. Otherwise, it adds the key to the keep0List.
After processing all entries, it performs two purging operations. Firstly, it purges the stream by iterating over the keys in the keep0List and using jsm.purgeStream() with a PurgeOptions object created from the corresponding key's read subject. Secondly, it purges the stream again by iterating over the keys in the keep1List and using jsm.purgeStream() with a PurgeOptions object created with the subject from the corresponding key and specifying to keep only one message.
Overall, this method is used to remove delete markers from the NatsKeyValue system based on the specified options and thresholds.

PullOrderedMessageManager
The PullOrderedMessageManager class extends the PullMessageManager class and is responsible for managing the retrieval of ordered messages.
protected ManageResult manage(Message msg)
@Override
protected ManageResult manage(Message msg) {
if (!msg.getSID().equals(targetSid.get())) {
// wrong sid is throwaway from previous consumer that errored
return STATUS_HANDLED;
}
if (msg.isJetStream()) {
long receivedConsumerSeq = msg.metaData().consumerSequence();
if (expectedExternalConsumerSeq != receivedConsumerSeq) {
handleErrorCondition();
return STATUS_HANDLED;
}
trackJsMessage(msg);
expectedExternalConsumerSeq++;
return MESSAGE;
}
return manageStatus(msg);
}The manage() method in the PullOrderedMessageManager class is responsible for handling a message received by a consumer. Here is a step-by-step description of what the method does based on its body:
-
The method starts with an
@Overrideannotation, indicating that it overrides a method defined in a superclass or interface. -
The method has a parameter of type
Messagenamedmsg. This parameter represents the message received by the consumer. -
The method first checks if the
SID(Session ID) of the message is not equal to thetargetSidvalue. -
If the
SIDdoes not match, it means that the message is from a previous consumer that encountered an error. In this case, the method returnsSTATUS_HANDLED, indicating that the message has been successfully handled and can be discarded. -
If the
SIDmatches, the method continues to the next condition. -
The method checks if the message is a JetStream message by calling the
isJetStream()method on the message object. -
If the message is a JetStream message, the method retrieves the consumer sequence number from the message's metadata by calling the
consumerSequence()method. It compares this value to the expected external consumer sequence number (expectedExternalConsumerSeq). -
If the obtained consumer sequence number does not match the expected value, the method calls a
handleErrorCondition()method to handle the error condition and returnsSTATUS_HANDLED. -
If the consumer sequence number matches, it means that the message is in the correct order. The method tracks the JetStream message by calling the
trackJsMessage()method. -
The method increments the
expectedExternalConsumerSeqby 1, indicating that the next expected message should have a higher consumer sequence number. -
Finally, if the message is not a JetStream message, the method calls another method called
manageStatus(msg)to handle the message and return the appropriate status.
In summary, the manage() method in the PullOrderedMessageManager class handles messages received by a consumer by ensuring that the messages are in the correct order based on their consumer sequence numbers. If a message is from a previous consumer or if it has an incorrect sequence number, the method handles the error condition. Otherwise, it tracks the JetStream message and updates the expected sequence number for the next message.
The manage method in the PullOrderedMessageManager class is responsible for managing a received message.
Here's a breakdown of what it does:
-
First, it checks if the message's session ID (
SID) is not the same as the target session ID stored in the class. If they are not equal, it means that the message is from a previous consumer that encountered an error and can be discarded. In this case, it returnsSTATUS_HANDLED. -
If the message is marked as a JetStream message (indicated by the
isJetStreamflag), it performs the following steps:-
Checks if the consumer sequence of the message matches the expected external consumer sequence. If they do not match, it indicates that there is a handling error and the
handleErrorConditionmethod is called. Then, it returnsSTATUS_HANDLED. -
Tracks the JetStream message by calling the
trackJsMessagemethod. -
Increments the expected external consumer sequence.
-
Finally, it returns
MESSAGE.
-
-
If the message is not a JetStream message, it calls the
manageStatusmethod to handle the message status and returns its result.
Overall, the manage method ensures that the received message is correctly handled according to its type (JetStream or non-JetStream) and handles any error conditions that may occur.

private void handleErrorCondition() {
try {
targetSid.set(null);
// consumer always starts with consumer sequence 1
expectedExternalConsumerSeq = 1;
// 1. shutdown the manager, for instance stops heartbeat timers
shutdown();
// 2. re-subscribe. This means kill the sub then make a new one
// New sub needs a new deliverSubject
String newDeliverSubject = sub.connection.createInbox();
sub.reSubscribe(newDeliverSubject);
targetSid.set(sub.getSID());
// 3. make a new consumer using the same deliver subject but
// with a new starting point
ConsumerConfiguration userCC = ConsumerConfiguration.builder(originalCc).name(ConsumerUtils.generateConsumerName()).deliverPolicy(DeliverPolicy.ByStartSequence).deliverSubject(newDeliverSubject).startSequence(Math.max(1, lastStreamSeq + 1)).startTime(// clear start time in case it was originally set
null).build();
js._createConsumerUnsubscribeOnException(stream, userCC, sub);
// 4. restart the manager.
startup(sub);
} catch (Exception e) {
IllegalStateException ise = new IllegalStateException("Ordered subscription fatal error.", e);
js.conn.processException(ise);
if (syncMode) {
throw ise;
}
}
}The handleErrorCondition method defined in the io.nats.client.impl.PullOrderedMessageManager class handles error conditions. Here is a step-by-step description of what the method does:
- The
targetSidvariable is set tonull. This variable is used to store the subscription ID of the consumer. - The
expectedExternalConsumerSeqvariable is set to1. This variable keeps track of the expected external consumer sequence number. - The
shutdownmethod is called. This method shuts down the manager, which includes stopping heartbeat timers. - The
subobject, which represents a subscription, is re-subscribed. This means that the current subscription is killed and a new one is created. The new subscription needs a new deliver subject, which is obtained by callingsub.connection.createInbox(). - The
targetSidvariable is set to the subscription ID of the new subscription. - A new consumer is created using the same deliver subject as the new subscription, but with a new starting point. The starting point is set to the maximum of 1 and the last stream sequence number plus 1.
- The
js._createConsumerUnsubscribeOnExceptionmethod is called to create a new consumer and unsubscribe the previous one in case of an exception. This method takes the stream name, the user-defined consumer configuration, and the subscription as arguments. - The
startupmethod is called to restart the manager. This method performs any necessary initialization steps for the manager. - If any exception occurs in the try block, an
IllegalStateExceptionis created with an error message and the exception as arguments. - The
processExceptionmethod of the connection associated with thejsobject is called to handle the exception. - If the
syncModeflag is true, theIllegalStateExceptionis re-thrown. This allows the caller of the method to handle the exception synchronously.
This method handles errors in ordered subscriptions by shutting down the manager, re-subscribing with a new deliver subject and starting point, creating a new consumer, and restarting the manager. If any exception occurs during this process, it is processed and potentially re-thrown.
The handleErrorCondition method in the PullOrderedMessageManager class handles error conditions during an ordered subscription.
- First, it resets the target subscription ID and sets the expected external consumer sequence to 1.
- Then, it shuts down the manager, which includes stopping heartbeat timers.
- Next, it re-subscribes by creating a new deliver subject and subscribing to it.
- After that, it sets the new target subscription ID and creates a new consumer using the same deliver subject but with a new starting point.
- It then restarts the manager to resume normal operation.
- If any exception occurs during this process, it creates an
IllegalStateExceptionwith an appropriate error message and propagates it to the parent process. If the method is called in a synchronous mode, it throws the exception.
Overall, this method handles error conditions during an ordered subscription by resetting and re-subscribing to recover from the error.

PullMessageManager
The PullMessageManager class extends the MessageManager class and is responsible for managing and handling pull messages. It provides functionality to retrieve messages from a source and process them accordingly.
protected void startPullRequest(String pullSubject, PullRequestOptions pro, boolean raiseStatusWarnings, TrackPendingListener trackPendingListener)
@Override
protected void startPullRequest(String pullSubject, PullRequestOptions pro, boolean raiseStatusWarnings, TrackPendingListener trackPendingListener) {
synchronized (stateChangeLock) {
this.raiseStatusWarnings = raiseStatusWarnings;
this.trackPendingListener = trackPendingListener;
pendingMessages += pro.getBatchSize();
pendingBytes += pro.getMaxBytes();
trackingBytes = (pendingBytes > 0);
configureIdleHeartbeat(pro.getIdleHeartbeat(), -1);
if (hb) {
initOrResetHeartbeatTimer();
} else {
shutdownHeartbeatTimer();
}
}
}The startPullRequest method is defined in the PullMessageManager class within the io.nats.client.impl package. This method is an overridden implementation of a protected method.
The method takes the following arguments:
-
pullSubject: A string representing the subject used for pulling the messages. -
pro: An object of typePullRequestOptionsthat contains various options for the pull request. -
raiseStatusWarnings: A boolean value indicating whether status warnings should be raised. -
trackPendingListener: An object implementing theTrackPendingListenerinterface, used for tracking pending messages.
The method performs the following steps:
- Acquires a lock on the
stateChangeLockobject to synchronize access to shared variables. - Sets the
raiseStatusWarningsvariable of the class to the value passed as the argument. - Sets the
trackPendingListenervariable of the class to the value passed as the argument. - Increments the
pendingMessagesvariable of the class by the value ofpro.getBatchSize(). - Increments the
pendingBytesvariable of the class by the value ofpro.getMaxBytes(). - Determines whether the
pendingBytesvariable is greater than zero and sets thetrackingBytesvariable accordingly. - Configures the idle heartbeat using the
pro.getIdleHeartbeat()value and sets-1as the maximum allowed number of heartbeats. - Checks the value of the
hbvariable and executes the following:- If
hbis true, calls theinitOrResetHeartbeatTimer()method to initialize or reset the heartbeat timer. - If
hbis false, calls theshutdownHeartbeatTimer()method to shutdown/cancel the heartbeat timer.
- If
Once the method completes, the lock on the stateChangeLock object is released.
Note: The purpose and implementation details of the initOrResetHeartbeatTimer() and shutdownHeartbeatTimer() methods are not mentioned in the provided code snippet.
The method startPullRequest is used to initiate a pull request for consuming messages from a NATS server.
The method takes in the following parameters:
-
pullSubject: The subject on which to pull messages. -
pro: An object of typePullRequestOptionsthat specifies various options for the pull request, such as batch size, maximum bytes, and idle heartbeat. -
raiseStatusWarnings: A boolean flag indicating whether to raise status warnings. -
trackPendingListener: An object of typeTrackPendingListenerthat is used to track pending messages.
Inside the method, a synchronized block is used to ensure thread safety. The method sets the raiseStatusWarnings flag and assigns the trackPendingListener object. It also updates the count of pending messages and pending bytes based on the options specified in pro.
The method then configures the idle heartbeat for the pull request, based on the IdleHeartbeat value in pro. If the idle heartbeat is enabled (hb is true), it initializes or resets the heartbeat timer. Otherwise, it shuts down the heartbeat timer.

private void trackPending(int m, long b) {
synchronized (stateChangeLock) {
pendingMessages -= m;
boolean zero = pendingMessages < 1;
if (trackingBytes) {
pendingBytes -= b;
zero |= pendingBytes < 1;
}
if (zero) {
pendingMessages = 0;
pendingBytes = 0;
trackingBytes = false;
if (hb) {
shutdownHeartbeatTimer();
}
}
if (trackPendingListener != null) {
trackPendingListener.track(pendingMessages, pendingBytes, trackingBytes);
}
}
}The trackPending method, defined in the PullMessageManager class within the io.nats.client.impl package, performs the following steps based on its input:
- Acquire a lock on the
stateChangeLockobject to ensure thread safety. - Decrement the
pendingMessagesvariable by the value ofm. - Check if
pendingMessagesis less than 1 and assign the result to thezerovariable. - If the
trackingBytesflag is enabled, decrement thependingBytesvariable by the value ofband update thezerovariable totrueifpendingBytesis less than 1. - If the
zerovariable istrue, indicating that bothpendingMessagesandpendingBytesare now zero, perform the following steps:- Set
pendingMessagesandpendingBytesto 0. - Set
trackingBytestofalse. - If the
hbflag is enabled, call theshutdownHeartbeatTimermethod.
- Set
- If the
trackPendingListenerobject is notnull, call itstrackmethod passing the current values ofpendingMessages,pendingBytes, andtrackingBytes. - Release the lock on the
stateChangeLockobject.
Note: It is assumed that the pendingMessages, pendingBytes, trackingBytes, hb, trackPendingListener, and stateChangeLock variables are instance variables of the PullMessageManager class.
The trackPending method in the PullMessageManager class is responsible for tracking the number of pending messages and bytes. This method takes two parameters: m, which represents the number of messages to be tracked, and b, which represents the number of bytes to be tracked.
Within this method, the pending message count and byte count are updated based on the provided parameters. If the tracking of bytes is enabled (trackingBytes is set to true), the pending byte count is also updated. If either the pending message count or byte count becomes zero or less, the tracking is stopped and the counters and tracking status are reset.
Additionally, if a trackPendingListener is registered, the track method of the listener is invoked to provide the current values of the pending message count, pending byte count, and tracking status.
Overall, this method is used to keep track of the number of pending messages and bytes in the PullMessageManager class and communicate any changes to a listener if present.

protected Boolean beforeQueueProcessorImpl(NatsMessage msg)
@Override
protected Boolean beforeQueueProcessorImpl(NatsMessage msg) {
// record message time. Used for heartbeat tracking
messageReceived();
Status status = msg.getStatus();
// normal js message
if (status == null) {
trackPending(1, msg.consumeByteCount());
return true;
}
// heartbeat just needed to be recorded
if (status.isHeartbeat()) {
return false;
}
Headers h = msg.getHeaders();
if (h != null) {
String s = h.getFirst(NATS_PENDING_MESSAGES);
if (s != null) {
try {
int m = Integer.parseInt(s);
long b = Long.parseLong(h.getFirst(NATS_PENDING_BYTES));
trackPending(m, b);
}// shouldn't happen but don't fail
catch (NumberFormatException ignore) {
}
}
}
return true;
}The beforeQueueProcessorImpl method, defined in the PullMessageManager class in the io.nats.client.impl package, performs the following actions based on its body:
-
It overrides the
beforeQueueProcessorImplmethod and takes aNatsMessageobject as a parameter. -
It calls the
messageReceivedmethod to record the time at which the message is received. This time is used for heartbeat tracking. -
It gets the status of the message using the
getStatusmethod from theNatsMessageobject. -
If the status is
null, it means it is a normal JavaScript message. It tracks the pending message count and the byte count using thetrackPendingmethod and returnstrue. -
If the status is not
nulland is a heartbeat, it returnsfalsewithout performing any further actions. Heartbeats are only recorded and not processed. -
It gets the headers from the
NatsMessageobject using thegetHeadersmethod. -
If the headers exist, it tries to retrieve a specific header value. It gets the value associated with the
NATS_PENDING_MESSAGESkey using thegetFirstmethod of theHeadersobject. -
If the value is not
null, it converts the value to an integer and retrieves the corresponding value forNATS_PENDING_BYTESfrom the headers. -
It then invokes the
trackPendingmethod with the pending message count and byte count. -
If any exceptions occur during parsing the header values to integers, they are caught and ignored.
-
Finally, it returns
true.
This method is used to process incoming messages, track pending messages and bytes, and handle heartbeats in the NATS messaging system.
The beforeQueueProcessorImpl method is a protected method in the PullMessageManager class that is overridden with the @Override annotation. This method accepts a NatsMessage object as a parameter.
The purpose of this method is to process the NatsMessage before adding it to the message queue. It performs the following tasks:
- It records the time the message is received, which is used for heartbeat tracking.
- It checks the status of the message. If the status is null, it means it is a normal JavaScript message, and it proceeds to track the pending message count and consume byte count before returning
true. - If the status is a heartbeat, it simply returns
falsewithout doing any processing. - It retrieves the headers from the message and checks if there is a header with the name "NATS_PENDING_MESSAGES". If such a header exists, it parses the value as an integer and retrieves the corresponding "NATS_PENDING_BYTES" header. It then tracks the pending message count and bytes based on these values.
- Finally, it returns
true, indicating that the message has been processed successfully and can be added to the message queue.
Note that if any exceptions occur during parsing of the pending message count or bytes, they are caught and ignored to prevent the method from failing.

protected ManageResult manage(Message msg)
@Override
protected ManageResult manage(Message msg) {
// normal js message
if (msg.getStatus() == null) {
trackJsMessage(msg);
return MESSAGE;
}
return manageStatus(msg);
}The manage method in the PullMessageManager class in the io.nats.client.impl package performs certain actions based on the provided Message object.
Here is a step-by-step description of what the manage method does:
- The method overrides the
managemethod defined in the superclass. - The method takes a
Messageobject as a parameter. - It first checks if the status of the message is
null. - If the status is
null, it means that it is a normal JavaScript message. - In this case, the method calls the
trackJsMessagemethod, which likely performs some internal tracking or processing of the message. - After tracking the message, the method returns a
ManageResultof typeMESSAGE. - If the status is not
null, it means that the message has a non-null status. - In this case, the method calls the
manageStatusmethod, which is not shown in the provided code snippet. - The
manageStatusmethod likely handles the message according to its status and returns aManageResultobject based on that handling. - The method returns the
ManageResultobject returned by themanageStatusmethod.
It is important to note that without the implementation of the trackJsMessage and manageStatus methods, it is unclear what specific actions are being taken for normal JavaScript messages and messages with a status. The provided code snippet only provides the high-level logic of the manage method.
The manage method in the PullMessageManager class, defined in the io.nats.client.impl package, is an overridden method that takes in a Message object as a parameter and returns a ManageResult object.
The method first checks if the status of the Message is null. If it is, it means that the message is a normal JavaScript message. In this case, the method calls the trackJsMessage function to handle the message and then returns a ManageResult with the value MESSAGE.
If the status of the Message is not null, the method calls the manageStatus function to handle the message, and the result of this function call is returned as the ManageResult.
Overall, the manage method in the PullMessageManager class is responsible for managing messages received by the client, distinguishing between normal JavaScript messages and messages with a status, and handling them accordingly.

protected ManageResult manageStatus(Message msg) {
Status status = msg.getStatus();
switch(status.getCode()) {
case NOT_FOUND_CODE:
case REQUEST_TIMEOUT_CODE:
if (raiseStatusWarnings) {
conn.executeCallback((c, el) -> el.pullStatusWarning(c, sub, status));
}
return STATUS_TERMINUS;
case CONFLICT_CODE:
// sometimes just a warning
String statMsg = status.getMessage();
if (statMsg.startsWith("Exceeded Max")) {
if (raiseStatusWarnings) {
conn.executeCallback((c, el) -> el.pullStatusWarning(c, sub, status));
}
return STATUS_HANDLED;
}
if (statMsg.equals(BATCH_COMPLETED) || statMsg.equals(MESSAGE_SIZE_EXCEEDS_MAX_BYTES)) {
return STATUS_TERMINUS;
}
break;
}
// fall through, all others are errors
conn.executeCallback((c, el) -> el.pullStatusError(c, sub, status));
return STATUS_ERROR;
}The manageStatus method, defined in the PullMessageManager class in the io.nats.client.impl package, takes a Message object as input and returns a ManageResult object.
Here is a step-by-step description of what the method does based on the provided code:
- Get the
Statusobject from themsgparameter. - Use a switch statement to check the code of the
Statusobject. - If the code is equal to
NOT_FOUND_CODEorREQUEST_TIMEOUT_CODE:- Check if
raiseStatusWarningsis true. - If true, execute a callback function on the
connobject, passing theconn,el, andsubvariables, and calling thepullStatusWarningmethod onelwith thec,sub, andstatusparameters. - Return
STATUS_TERMINUS.
- Check if
- If the code is equal to
CONFLICT_CODE:- Get the message from the
Statusobject. - If the message starts with "Exceeded Max":
- Check if
raiseStatusWarningsis true. - If true, execute a callback function on the
connobject, passing theconn,el, andsubvariables, and calling thepullStatusWarningmethod onelwith thec,sub, andstatusparameters. - Return
STATUS_HANDLED.
- Check if
- If the message is equal to
BATCH_COMPLETEDorMESSAGE_SIZE_EXCEEDS_MAX_BYTES:- Return
STATUS_TERMINUS.
- Return
- Get the message from the
- If none of the above cases match, execute a callback function on the
connobject, passing theconn,el, andsubvariables, and calling thepullStatusErrormethod onelwith thec,sub, andstatusparameters. - Return
STATUS_ERROR.
The manageStatus method in the PullMessageManager class is responsible for handling the status of a message received from a NATS server.
Here is a breakdown of what the method does:
- It takes a
Messageobject as input. - It gets the
Statusobject from theMessage. - It checks the code of the status using a switch statement.
- If the code is
NOT_FOUND_CODEorREQUEST_TIMEOUT_CODE, it checks ifraiseStatusWarningsis true. If it is, it executes a callback functionpullStatusWarningand returnsSTATUS_TERMINUS. - If the code is
CONFLICT_CODE, it checks the message content. If it starts with "Exceeded Max", it checks ifraiseStatusWarningsis true. If it is, it executes a callback functionpullStatusWarningand returnsSTATUS_HANDLED. - If the message content is "BATCH_COMPLETED" or "MESSAGE_SIZE_EXCEEDS_MAX_BYTES", it returns
STATUS_TERMINUS. - If none of the above cases match, it executes a callback function
pullStatusErrorand returnsSTATUS_ERROR.
Overall, the method handles different status codes and performs specific actions based on the code and the content of the status message.
Headers
An object that represents a map of keys (headers) to a list of values. This class does not accept null or invalid keys. It ignores null values, accepts empty strings as a value, and rejects invalid values. It is important to note that this class is not thread-safe.
public Headers add(String key, String... values) {
if (values != null) {
_add(key, Arrays.asList(values));
}
return this;
}The add method in class io.nats.client.impl.Headers is used to add a key-value pair to the existing headers. It accepts a key parameter which represents the header name, and a values parameter which represents the values to be associated with the header.
Here is the step-by-step description of what the add method does based on its body:
-
Check if the
valuesparameter is not null. If it is null, then there is nothing to add, so the method just returns the current instance ofHeaders. -
If the
valuesparameter is not null, it means that there are values to be added to the header. -
Call the
_addmethod, which is a private method defined in the same class, to actually add the key-value pair to the headers. The_addmethod takes thekeyandvaluesparameters as arguments. -
Convert the
valuesarray to aListusing theArrays.asListmethod. This is done to make it easier to work with the values. -
Pass the converted list of values to the
_addmethod along with thekey. -
After the
_addmethod has added the key-value pair to the headers, theaddmethod returns the current instance ofHeaders. This allows for method chaining, where multiple calls toaddcan be made in a single statement.
The add method defined in the io.nats.client.impl.Headers class is used to add key-value pairs to the headers. It takes a key and one or more values as parameters. If the values parameter is not null, the method calls the internal _add method to add the key-value pair to the headers. The method then returns the updated headers object.

private void _add(String key, Collection values)
// the add delegate
private void _add(String key, Collection<String> values) {
if (values != null) {
Checker checked = new Checker(key, values);
if (checked.hasValues()) {
// get values by key or compute empty if absent
// update the data length with the additional len
// update the lengthMap for the key to the old length plus the new length
List<String> currentSet = valuesMap.computeIfAbsent(key, k -> new ArrayList<>());
currentSet.addAll(checked.list);
dataLength += checked.len;
int oldLen = lengthMap.getOrDefault(key, 0);
lengthMap.put(key, oldLen + checked.len);
// since the data changed, clear this so it's rebuilt
serialized = null;
}
}
}This method is defined in the Headers class of the io.nats.client.impl package. It is a private method that takes two parameters: a String key and a Collection of Strings values.
The method first checks if the passed values parameter is not null.
If values is not null, the method initializes a Checker object with the key and values. The Checker is responsible for validating and processing the values.
The method calls the hasValues() method of the Checker object to determine if there are valid values to process. If checked.hasValues() returns true, it means that there are valid values.
The method retrieves the current set of values associated with the given key from the valuesMap using the computeIfAbsent() method. If there is no existing set of values, a new ArrayList is created and associated with the key.
The method then adds all the validated values from the Checker object to the current set of values.
The dataLength is updated by adding the length of the validated values.
The oldLen is retrieved from the lengthMap using the getOrDefault() method. If there is no existing length for the key, a default value of 0 is used.
The lengthMap is then updated by adding the new length of the values to the existing length.
Finally, since the data has changed, the method sets the serialized variable to null, indicating that the serialized data needs to be rebuilt.
It is important to note that this method is private, indicating that it is not intended to be accessed or called directly by other classes. It is called internally within the Headers class.
The method _add in the class io.nats.client.impl.Headers is used to add key-value pairs to the headers. It takes in a key and a collection of values as parameters.
First, it checks if the collection of values is not null. Then, it creates a Checker object to validate the key and values. If the Checker determines that there are valid values, the method performs the following steps:
-
It retrieves the current set of values associated with the key from the
valuesMap. If there are no values, it creates a new empty ArrayList. -
It adds all the validated values from the Checker object to the current set of values.
-
It updates the
dataLengthvariable by adding the length of the values. -
It gets the old length associated with the key from the
lengthMapand adds the length of the values to it. -
It sets the
serializedvariable to null, indicating that the data has changed and needs to be rebuilt.
In summary, this method allows you to add key-value pairs to the headers, keeping track of the length of the data and updating internal data structures accordingly.

public Headers put(String key, String... values) {
if (values != null) {
_put(key, Arrays.asList(values));
}
return this;
}The put method in the Headers class, defined in the package io.nats.client.impl, is used to add or update a header entry with the provided key and values.
Here is a step-by-step description of what this method does:
-
Start by checking if the
valuesparameter is not null. -
If
valuesis not null, proceed to the next step. Otherwise, skip the following steps and return the current instance ofHeaders. -
Call the
_putmethod of theHeadersclass, passing thekeyandvaluesas arguments. -
The
_putmethod is responsible for adding or updating the header entry. It takes thekeyandvaluesas parameters. -
Convert the
valuesarray to aListusing theArrays.asListmethod. -
Add or update the entry in the underlying data structure of the
Headersclass, using thekeyand theListofvalues. -
After updating the header entry, return the current instance of
Headers.
Following this step-by-step process, the put method allows you to add or update the header entry with the provided key and values in the Headers class.
The put method in the Headers class is used to add or update the values associated with a particular header key. The method takes a key and a variable number of values as input.
First, the method checks if the values array is not null. If it is not null, the method calls the _put method, which internally updates the header values map with the given key and values.
Finally, the method returns the updated Headers object, allowing for method chaining. This allows multiple put calls to be chained together to add or update multiple headers in a single line of code.

public Headers put(Map<String, List<String>> map) {
for (String key : map.keySet()) {
put(key, map.get(key));
}
return this;
}The put method in the Headers class is used to add multiple key-value pairs to the existing headers.
-
The
putmethod takes a parametermapof typeMap<String, List<String>>which represents the key-value pairs to be added to the headers. -
It loops over each key in the
mapusing a for-each loop. -
For each key, it calls the
putmethod (overloaded) of theHeadersclass and passes the key along with the corresponding value from themap. -
After adding all the key-value pairs from the
mapto the headers, theputmethod returns the updatedHeadersobject.
Map<String, List<String>> map = new HashMap<>();
map.put("Content-Type", Arrays.asList("application/json"));
map.put("Authorization", Arrays.asList("Bearer token"));
Headers headers = new Headers();
headers.put(map);The put method in the Headers class is used to add or update headers in the headers map of a NATS message. The method takes a Map object as input, where the keys are strings representing the header names, and the values are lists of strings representing the header values.
The method iterates over the keys in the input map and calls the put method (presumably another overloaded method in the Headers class) to add or update each header in the headers map. Finally, it returns the Headers object itself, allowing method chaining.
Essentially, this method provides a convenient way to add multiple headers to the headers map of a NATS message at once.

private void _put(String key, Collection values)
// the put delegate that all puts call
private void _put(String key, Collection<String> values) {
if (key == null || key.length() == 0) {
throw new IllegalArgumentException("Key cannot be null or empty.");
}
if (values != null) {
Checker checked = new Checker(key, values);
if (checked.hasValues()) {
// update the data length removing the old length adding the new length
// put for the key
dataLength = dataLength - lengthMap.getOrDefault(key, 0) + checked.len;
valuesMap.put(key, checked.list);
lengthMap.put(key, checked.len);
// since the data changed, clear this so it's rebuilt
serialized = null;
}
}
}The _put method in the Headers class is responsible for adding or updating key-value pairs in the valuesMap and lengthMap fields.
Here is a step-by-step description of what the _put method does:
-
Check if the
keyparameter is null or empty.- If it is null or empty, throw an
IllegalArgumentExceptionwith the message "Key cannot be null or empty."
- If it is null or empty, throw an
-
Check if the
valuesparameter is not null.- If it is not null, proceed with the following steps.
-
Create a new instance of the
Checkerclass, passing thekeyandvaluesparameters.- The
Checkerclass is responsible for validating and processing the collection of values.
- The
-
Check if the
Checkerobject has values to be processed.- If it has values, proceed with the following steps.
-
Calculate the new data length:
- Subtract the old length associated with the
key(from thelengthMap) if it exists. - Add the new length associated with the
key(from theCheckerobject).
- Subtract the old length associated with the
-
Update the
valuesMap,lengthMap, andserializedfields:- Set the
keyin thevaluesMapto the list of values from theCheckerobject. - Set the
keyin thelengthMapto the new length from theCheckerobject. - Set the
serializedfield to null, indicating that it needs to be rebuilt.
- Set the
That's the step-by-step description of what the _put method in the Headers class does based on its body.
The _put method in the io.nats.client.impl.Headers class is responsible for adding or updating a key-value pair in the headers of a NATS message.
The method takes a key and a collection of values as parameters. It first checks if the key is null or empty, and throws an exception if it is.
If the values collection is not null, the method validates and checks the values using a Checker object. If the values are valid, the method updates the data length of the headers by subtracting the old length associated with the given key (if any) and adding the new length.
The method then updates the valuesMap, which stores the key-value pairs, with the new values and updates the lengthMap, which stores the lengths of the values associated with each key. Finally, it clears the serialized field, so that the headers are rebuilt the next time they are accessed.

public void remove(String... keys) {
for (String key : keys) {
_remove(key);
}
// since the data changed, clear this so it's rebuilt
serialized = null;
}This method is defined in the class io.nats.client.impl.Headers and is used to remove one or more keys from the headers.
Iterate through each key in the keys array.
Call the private method _remove(key) to remove the key from the headers.
Reset the value of the serialized variable to null. This is done to indicate that the data has changed and needs to be rebuilt.
Once the method is executed, the specified keys will be removed from the headers and the serialized variable will be reset to null.
The remove method in the io.nats.client.impl.Headers class is used to remove one or more keys from the header data. It accepts one or more key strings as parameter, iterates over each key, and calls the private method _remove to remove the corresponding header. After removing the header(s), it also clears the serialized variable, which indicates that the header data has changed and needs to be rebuilt.

public void remove(Collection<String> keys) {
for (String key : keys) {
_remove(key);
}
// since the data changed, clear this so it's rebuilt
serialized = null;
}The remove method removes the specified keys from the Headers object. It takes a collection of keys as input and iterates through each key to remove it from the headers.
- Start by looping through each key in the
keyscollection. - For each key, call the
_removemethod with the key as the parameter. This method will remove the key from the headers. - Once all keys have been removed, the
serializedfield is set tonull. This field holds the serialized representation of the headers and by setting it tonull, it ensures that the serialized version will be rebuilt the next time it is accessed.
Note: This method assumes that the _remove method is implemented properly to remove the specified key from the headers.
The remove method in the io.nats.client.impl.Headers class is used to remove multiple keys from the headers collection.
-
keys(Collection): A collection of strings representing the keys to be removed from the headers collection.
- The method iterates through each key in the
keyscollection and removes it from the headers collection by calling the_removemethod internally. - After removing the keys, the
serializedvariable, which stores the serialized version of the headers collection, is set to null. This ensures that the serialized version gets rebuilt if needed.
private void _remove(String key) {
// if the values had a key, then the data length had a length
if (valuesMap.remove(key) != null) {
dataLength -= lengthMap.remove(key);
}
}The _remove method is defined in the Headers class, located in the io.nats.client.impl package.
The purpose of the _remove method is to remove a specified key from the Headers object by removing its corresponding entry from the valuesMap and reducing the dataLength by the length of the removed value.
-
key: The key of the entry to be removed from theHeadersobject.
private void _remove(String key) {
if (valuesMap.remove(key) != null) {
dataLength -= lengthMap.remove(key);
}
}- The method begins by attempting to remove the entry identified by the
keyparameter from thevaluesMap. - The
removemethod returns the previous value associated with the specifiedkey, ornullif there was no mapping for thekey. - If the return value is not
null, indicating that an entry was successfully removed from thevaluesMap, the method proceeds to the next step. - The length of the removed value is obtained from the
lengthMapusing the samekeythat was used to remove the entry from thevaluesMap. - The length of the removed value is subtracted from the
dataLengthvariable to update its value. - The method execution completes.
-
Headers: The class that contains the_removemethod. -
valuesMap: A data structure that stores key-value pairs representing the headers. -
dataLength: A counter that keeps track of the total length of the data stored in theHeadersobject. -
lengthMap: A mapping of keys to their corresponding lengths in theHeadersobject.
The _remove method, defined in the io.nats.client.impl.Headers class, is used to remove a key-value pair from the headers of a NATS message.
The method takes a key as a parameter and performs the following steps:
- It checks if the
valuesMapcontains the specifiedkey. If thekeyexists, it removes the corresponding value from thevaluesMap. - Additionally, it removes the corresponding length value from the
lengthMapassociated with thekey. - If the removal was successful, it subtracts the length value from the
dataLengthvariable.
In summary, this method removes a key-value pair from the headers and updates the data length accordingly.

public void clear() {
valuesMap.clear();
lengthMap.clear();
dataLength = 0;
serialized = null;
}The clear() method, defined in the Headers class of the io.nats.client.impl package, is used to clear the state of the headers object. This method performs the following steps:
-
Clear the
valuesMap: This removes all key-value pairs stored in thevaluesMap, which is a map used to store the header name and its corresponding value. -
Clear the
lengthMap: This clears thelengthMap, which is a map used to store the length of the serialized version of each header. -
Reset the
dataLengthvariable: This sets thedataLengthvariable to 0, indicating that there is no data stored in the headers after clearing. -
Set the
serializedvariable to null: Theserializedvariable is a byte array that holds the serialized representation of the headers. Setting it to null indicates that there is no serialized data present after clearing the headers.
By executing these steps, the clear() method ensures that all data stored in the headers object is removed, effectively clearing its state and preparing it for further usage.
The clear method in the Headers class, defined in the io.nats.client.impl package, is used to clear the existing data in the headers object.
This method performs the following actions:
- Clears the
valuesMapandlengthMapdata structures, removing all key-value pairs. - Resets the
dataLengthvariable to 0. - Sets the
serializedfield tonull.
After calling the clear method, the headers object will have no data or state stored.

public boolean containsKeyIgnoreCase(String key) {
for (String k : valuesMap.keySet()) {
if (k.equalsIgnoreCase(key)) {
return true;
}
}
return false;
}The containsKeyIgnoreCase method defined in class io.nats.client.impl.Headers is used to check if the given key exists in the valuesMap ignoring the case sensitivity.
Here is a step-by-step description of how the method works:
-
Initialize the
forloop: Iterate over each key,k, in thevaluesMapusing afor-eachloop. -
Check for a case-insensitive match: Compare the key,
k, with the inputkeyignoring any differences in case. -
Case-insensitive match found: If
kmatcheskeyignoring case, returntrueto indicate that the key exists in thevaluesMap. -
Repeat for each key: Continue the
forloop, checking the next key in thevaluesMap. -
No match found: If the loop completes without finding a case-insensitive match for
keyin any of the keys invaluesMap, returnfalseto indicate that the key does not exist in thevaluesMap.
That's how the containsKeyIgnoreCase method functions to determine if a specified key is present in the valuesMap of the Headers class, regardless of case sensitivity.
The containsKeyIgnoreCase method, defined in the Headers class in the io.nats.client.impl package, determines whether the valuesMap contains a key that matches the provided key (case-insensitive).
It iterates through all the keys in the valuesMap and compares each key with the provided key using a case-insensitive comparison. If a match is found, the method returns true. Otherwise, if no match is found, the method returns false.

public Set<String> keySetIgnoreCase() {
HashSet<String> set = new HashSet<>();
for (String k : valuesMap.keySet()) {
set.add(k.toLowerCase());
}
return Collections.unmodifiableSet(set);
}This method keySetIgnoreCase is a member of the Headers class in the io.nats.client.impl package. It returns a Set of case-insensitive keys present in the valuesMap of the Headers object. The returned set is a read-only view and cannot be modified.
public Set<String> keySetIgnoreCase()public Set<String> keySetIgnoreCase() {
HashSet<String> set = new HashSet<>();
for (String k : valuesMap.keySet()) {
set.add(k.toLowerCase());
}
return Collections.unmodifiableSet(set);
}- Create an empty
HashSetnamedsetto store the case-insensitive keys. - Iterate over all the keys present in the
valuesMapusing a for-each loop. - Inside the loop, convert each key to lowercase using the
toLowerCase()method and add it to theset. - Once all keys have been processed, return the
setas an unmodifiable set using theCollections.unmodifiableSet()method.
- Create an empty
HashSetnamedsetto store the case-insensitive keys.
HashSet<String> set = new HashSet<>();- Iterate over all the keys present in the
valuesMapusing a for-each loop.
for (String k : valuesMap.keySet()) {- Inside the loop, convert each key to lowercase using the
toLowerCase()method and add it to theset.
set.add(k.toLowerCase());- Once all keys have been processed, return the
setas an unmodifiable set using theCollections.unmodifiableSet()method.
}
return Collections.unmodifiableSet(set);The keySetIgnoreCase method in the io.nats.client.impl.Headers class is used to retrieve a set of keys from the Headers object, ignoring the case of the keys.
It creates a new HashSet object to store the keys and then iterates over the existing keys in the valuesMap of the Headers object. For each key, it converts the key to lowercase and adds it to the set.
Finally, it returns an unmodifiable set of keys, ensuring that the original set cannot be modified.

public ByteArrayBuilder appendSerialized(ByteArrayBuilder bab)
@Deprecated
public ByteArrayBuilder appendSerialized(ByteArrayBuilder bab) {
bab.append(HEADER_VERSION_BYTES_PLUS_CRLF);
for (String key : valuesMap.keySet()) {
for (String value : valuesMap.get(key)) {
bab.append(key);
bab.append(COLON_BYTES);
bab.append(value);
bab.append(CRLF_BYTES);
}
}
bab.append(CRLF_BYTES);
return bab;
}The appendSerialized method in the Headers class, located in the io.nats.client.impl package, is used to append the serialized header values to a ByteArrayBuilder object. Here is a step-by-step description of what this method does based on its body:
-
Declare the method as
@Deprecated, indicating that it is no longer recommended for use. -
The method takes a
ByteArrayBuilderobject,bab, as a parameter. -
Append the characters represented by
HEADER_VERSION_BYTES_PLUS_CRLFto thebabobject. It is assumed that this constant variable contains the serialized representation of the version of the headers, followed by a carriage return and line feed. -
Iterate over the keys present in the
valuesMapmap. ThevaluesMapmap is assumed to be a mapping of header keys to a list of values. -
For each key in the
valuesMapmap, iterate over the values associated with that key. -
Append the key to the
babobject using theappendmethod ofByteArrayBuilder. -
Append the characters represented by
COLON_BYTESto thebabobject. It is assumed that this constant variable contains the serialized representation of the colon character. -
Append the value to the
babobject using theappendmethod ofByteArrayBuilder. -
Append the characters represented by
CRLF_BYTESto thebabobject. It is assumed that this constant variable contains the serialized representation of a carriage return and line feed. -
Repeat steps 6-9 for each value associated with the key.
-
Repeat steps 5-10 for each key in the
valuesMapmap. -
Append the characters represented by
CRLF_BYTESto thebabobject again. -
Return the
babobject.
Note: The specific details of the constant variables (HEADER_VERSION_BYTES_PLUS_CRLF, COLON_BYTES, and CRLF_BYTES) and how the valuesMap map is populated are not given in the provided code. These details may affect the behavior and purpose of the appendSerialized method.
The method appendSerialized in the class Headers is used to append serialized header values to a ByteArrayBuilder object.
It first appends a specific header version to the ByteArrayBuilder. Then, it iterates over each key in the valuesMap and for each key, it iterates over the corresponding list of values. For each value, it appends the key, a colon, the value, and a new line character to the ByteArrayBuilder.
After appending all the key-value pairs, it adds a new line character to the ByteArrayBuilder and returns it.

public int serializeToArray(int destPosition, byte[] dest) {
System.arraycopy(HEADER_VERSION_BYTES_PLUS_CRLF, 0, dest, destPosition, HVCRLF_BYTES);
destPosition += HVCRLF_BYTES;
for (Map.Entry<String, List<String>> entry : valuesMap.entrySet()) {
List<String> values = entry.getValue();
for (String value : values) {
byte[] bytes = entry.getKey().getBytes(US_ASCII);
System.arraycopy(bytes, 0, dest, destPosition, bytes.length);
destPosition += bytes.length;
dest[destPosition++] = COLON;
bytes = value.getBytes(US_ASCII);
System.arraycopy(bytes, 0, dest, destPosition, bytes.length);
destPosition += bytes.length;
dest[destPosition++] = CR;
dest[destPosition++] = LF;
}
}
dest[destPosition++] = CR;
dest[destPosition] = LF;
return serializedLength();
}The serializeToArray method in the Headers class is responsible for serializing the header values into a byte array.
Here is a step-by-step description of what the method is doing:
-
It starts by copying the
HEADER_VERSION_BYTES_PLUS_CRLFbyte array to thedestbyte array starting from thedestPosition. TheHEADER_VERSION_BYTES_PLUS_CRLFconsists of the header version followed by a carriage return (CR) and line feed (LF) characters. -
The
destPositionis incremented byHVCRLF_BYTES, which is the length ofHEADER_VERSION_BYTES_PLUS_CRLF. -
The method then iterates over the key-value pairs in the
valuesMap. -
For each key-value pair, it gets the list of values associated with the key.
-
It loops over each value in the list.
-
It converts the key and value to bytes using the US_ASCII charset.
-
It then copies the key bytes to the
destbyte array starting from thedestPosition. -
The
destPositionis incremented by the length of the key bytes. -
It adds a colon character to the
destbyte array atdestPosition. -
It then copies the value bytes to the
destbyte array starting from thedestPosition. -
The
destPositionis incremented by the length of the value bytes. -
It adds a carriage return (CR) and line feed (LF) characters to the
destbyte array atdestPosition. -
The
destPositionis incremented by 2 to account for the CR and LF characters. -
This process is repeated for all the values in the list associated with the key.
-
After iterating over all the key-value pairs, it adds a carriage return (CR) and line feed (LF) characters to the
destbyte array atdestPosition. -
The
destPositionis incremented by 2. -
Finally, it returns the length of the serialized header values.
The serializeToArray method essentially serializes the header values into a byte array by appending the key-value pairs along with the necessary characters to separate them.
The serializeToArray method in the io.nats.client.impl.Headers class is used to serialize the headers into a byte array.
It takes two parameters: destPosition, which specifies the starting position in the dest byte array where the serialized headers will be copied to, and dest, which is the destination byte array.
The method first copies the header version bytes followed by carriage return and line feed characters to the dest byte array at the specified destPosition.
Then, it iterates over each entry in the valuesMap (a map of header names to lists of header values), and for each entry, it iterates over the list of header values.
For each value, it copies the bytes of the header name and value followed by a colon character, and appends the carriage return and line feed characters to the dest byte array.
After processing all the headers, it appends the final carriage return and line feed characters to the dest byte array.
Finally, it returns the total length of the serialized headers.
Overall, this method serializes the headers and appends them to the dest byte array, returning the length of the serialized headers.

private void checkKey(String key) {
// key cannot be null or empty and contain only printable characters except colon
if (key == null || key.length() == 0) {
throw new IllegalArgumentException(KEY_CANNOT_BE_EMPTY_OR_NULL);
}
int len = key.length();
for (int idx = 0; idx < len; idx++) {
char c = key.charAt(idx);
if (c < 33 || c > 126 || c == ':') {
throw new IllegalArgumentException(KEY_INVALID_CHARACTER + "'" + c + "'");
}
}
}This method is defined in the class io.nats.client.impl.Headers and is used to validate whether a given key is valid or not. The method takes a String parameter key as input.
-
Check if the provided
keyis null or empty:if (key == null || key.length() == 0) { throw new IllegalArgumentException(KEY_CANNOT_BE_EMPTY_OR_NULL); }
- If the
keyis null or empty, anIllegalArgumentExceptionis thrown with the message "Key cannot be empty or null."
- If the
-
Calculate the length of the
key:int len = key.length();
-
Iterate through each character of the
key:for (int idx = 0; idx < len; idx++) { char c = key.charAt(idx); // Perform validation checks on each character }
-
Perform validation checks on each character:
- Check if the character is less than 33 (ASCII value) or greater than 126 (ASCII value) or equal to ':'.
if (c < 33 || c > 126 || c == ':') { throw new IllegalArgumentException(KEY_INVALID_CHARACTER + "'" + c + "'"); }
- If the character does not meet the above conditions, an
IllegalArgumentExceptionis thrown with the message "Invalid character found in key: ''", where '' represents the specific character that failed the validation.
Note: The purpose of this method is to ensure that the provided key is not null, not empty, and contains only printable characters except for the colon (':').
The checkKey method in the Headers class of the io.nats.client.impl package is used to validate a given key string.
It checks if the key is null or empty, and throws an IllegalArgumentException with an appropriate error message if it is.
Then, it iterates over each character in the key string and checks if it is a printable character (ASCII value between 33 and 126) and not a colon. If any character fails this check, it throws an IllegalArgumentException with an error message indicating the invalid character.
In summary, this method ensures that a key string meets certain criteria (not null or empty, contains only printable characters except colon) and throws an exception if any invalid conditions are found.

private void checkValue(String val) {
// Generally more permissive than HTTP. Allow only printable
// characters and include tab (0x9) to cover what's allowed
// in quoted strings and comments.
val.chars().forEach(c -> {
if ((c < 32 && c != 9) || c > 126) {
throw new IllegalArgumentException(VALUE_INVALID_CHARACTERS + c);
}
});
}The checkValue method in the Headers class, located in the io.nats.client.impl package, performs a validation check on a given val parameter. This method ensures that the characters in the provided val are within the allowable range.
Step by step description of the checkValue method:
-
The method starts with a private visibility modifier, indicating that it can only be accessed within the
Headersclass. -
The method takes a single parameter,
val, which is aStringrepresenting the value to be checked. -
The method uses the
chars()method of thevalstring to convert it into anIntStreamof Unicode code points. -
For each character in the
IntStream, the method performs the following validation check:a. If the character's code point is less than 32 (excluding the tab character with a code point of 9) or greater than 126, an
IllegalArgumentExceptionis thrown. This exception includes the value of the invalid character. -
The method does not return any value, as it is a
voidmethod.
In summary, the checkValue method ensures that the characters in the provided val are printable and within the allowable range, throwing an exception if any invalid character is found.
The checkValue method, defined in the Headers class of the io.nats.client.impl package, is used to validate a value before it is set or added to a header.
The method iterates over each character in the val string and checks if it falls within the range of printable characters. It allows printable characters, including tabs (0x9), which are commonly used in quoted strings and comments.
If a character in the val string is found to be outside the permissible range (less than 32 and not equal to 9, or greater than 126), an IllegalArgumentException is thrown with a specific message indicating the invalid character.

SimplifiedSubscriptionMaker
The SimplifiedSubscriptionMaker class is a public class used for creating simplified subscriptions. It provides functionality and methods for managing subscriptions in a simplified manner.
public NatsJetStreamPullSubscription makeSubscription(MessageHandler messageHandler) throws IOException, JetStreamApiException
public NatsJetStreamPullSubscription makeSubscription(MessageHandler messageHandler) throws IOException, JetStreamApiException {
if (messageHandler == null) {
return (NatsJetStreamPullSubscription) js.subscribe(subscribeSubject, pso);
}
dispatcher = js.conn.createDispatcher();
return (NatsJetStreamPullSubscription) js.subscribe(subscribeSubject, dispatcher, messageHandler, pso);
}- Check if the
messageHandlerparameter isnull. - If
messageHandlerisnull, call thesubscribemethod of thejsobject withsubscribeSubjectandpsoas arguments. This will create and return aNatsJetStreamPullSubscriptionobject. - If
messageHandleris notnull, create a new dispatcher by calling thecreateDispatchermethod of thejs.connobject. - Call the
subscribemethod of thejsobject withsubscribeSubject,dispatcher,messageHandler, andpsoas arguments. This will create and return aNatsJetStreamPullSubscriptionobject. - Return the
NatsJetStreamPullSubscriptionobject obtained from the previous step.
Note: This method can throw IOException and JetStreamApiException, so make sure to handle these exceptions appropriately.
The makeSubscription method in the SimplifiedSubscriptionMaker class is used to create a subscription to a NATS JetStream pull-based consumer group.
The method takes a MessageHandler object as a parameter, which is responsible for processing incoming messages. If the messageHandler is null, the method creates and returns a NatsJetStreamPullSubscription by subscribing directly to the subscribeSubject with the specified pso (PullSubOptions).
If a messageHandler is provided, the method creates a NATS Dispatcher using js.conn.createDispatcher() and then subscribes to the subscribeSubject using the dispatcher, messageHandler, and the pso. Finally, it returns a NatsJetStreamPullSubscription.
Overall, the makeSubscription method allows for the creation of a pull-based subscription to a NATS JetStream consumer group, either with or without a custom MessageHandler.

StatusMessage
StatusMessage is a public class that extends the IncomingMessage class. It represents a message that contains a status update. This class provides functionality to create, manipulate, and access status messages in a software application.
ConsumerNamesReader
ConsumerNamesReader is a class that extends the StringListReader class. It is responsible for reading and processing a list of consumer names. This class provides methods and functionality to retrieve and manipulate the consumer names from a specified source.
NatsWatchSubscription
The NatsWatchSubscription<T> class is a public class that implements the AutoCloseable interface. It represents a subscription to a NATS streaming server. This class allows the user to watch and receive messages from the server. It is generic and can be parameterized with a specific type T for the messages being received. The NatsWatchSubscription<T> class also provides a method to close the subscription and release any resources associated with it.
protected void finishInit(NatsFeatureBase fb, String subscribeSubject, DeliverPolicy deliverPolicy, boolean headersOnly, WatchMessageHandler handler) throws IOException, JetStreamApiException
protected void finishInit(NatsFeatureBase fb, String subscribeSubject, DeliverPolicy deliverPolicy, boolean headersOnly, WatchMessageHandler<T> handler) throws IOException, JetStreamApiException {
if (deliverPolicy == DeliverPolicy.New || fb._getLast(subscribeSubject) == null) {
handler.sendEndOfData();
}
PushSubscribeOptions pso = PushSubscribeOptions.builder().stream(fb.getStreamName()).ordered(true).configuration(ConsumerConfiguration.builder().ackPolicy(AckPolicy.None).deliverPolicy(deliverPolicy).headersOnly(headersOnly).filterSubject(subscribeSubject).build()).build();
dispatcher = (NatsDispatcher) ((NatsJetStream) js).conn.createDispatcher();
sub = js.subscribe(subscribeSubject, dispatcher, handler, false, pso);
if (!handler.endOfDataSent) {
long pending = sub.getConsumerInfo().getCalculatedPending();
if (pending == 0) {
handler.sendEndOfData();
}
}
}The finishInit method in the NatsWatchSubscription class is responsible for initializing and completing the setup of a particular subscription. Here is a step-by-step breakdown of what the method does:
-
Check if the
deliverPolicyis set to "New" or if the last message received for thesubscribeSubjectis null.- If true, call the
sendEndOfDatamethod on the providedhandlerobject. This is used to indicate that there is no more data to be received for the subscription.
- If true, call the
-
Create a
PushSubscribeOptionsobject with the following configurations:- Set the stream name using
fb.getStreamName(). - Enable ordered delivery.
- Create a
ConsumerConfigurationobject with the following settings:- Set the acknowledgement policy to "None".
- Set the deliver policy to the provided
deliverPolicy. - Set the
headersOnlyflag. - Set the filter subject to the
subscribeSubject.
- Build and configure the
PushSubscribeOptionsobject.
- Set the stream name using
-
Create a
dispatcherobject by casting thejs(NatsJetStream) connection's dispatcher toNatsDispatcher. -
Subscribe to the
subscribeSubjectusing thejs(NatsJetStream) object with the provideddispatcher,handler, andpso(PushSubscribeOptions).- Set the
falseargument to indicate that the consumer is not durable.
- Set the
-
Check if the
endOfDataSentflag on thehandlerobject is not set.- If true, get the calculated number of pending messages using
sub.getConsumerInfo().getCalculatedPending(). - Check if the pending count is equal to 0.
- If true, call the
sendEndOfDatamethod on thehandlerobject.
- If true, call the
- If true, get the calculated number of pending messages using
This method initializes and sets up the subscription for receiving messages based on the provided parameters, and also handles cases where there is no data or pending messages to be received.
The finishInit method in the NatsWatchSubscription class is responsible for finalizing the initialization of a NatsWatchSubscription object.
This method takes several parameters such as fb (an instance of NatsFeatureBase), subscribeSubject (the subject to subscribe to), deliverPolicy (the delivery policy), headersOnly (indicating if only headers should be received), and handler (an instance of WatchMessageHandler).
The method first checks if the deliverPolicy is set to DeliverPolicy.New or if there are no previously received messages for the subscribed subject. If either of these conditions is true, it calls the sendEndOfData method on the handler to indicate the end of data.
Next, it creates a PushSubscribeOptions object with the appropriate configuration options for the subscription. It then creates a NatsDispatcher using the connection from the NatsJetStream object and assigns it to the dispatcher variable.
The method then subscribes to the subscribeSubject using the js (NatsJetStream) object, the dispatcher, the handler, and the PushSubscribeOptions object.
Finally, it checks if the sendEndOfData method has already been called on the handler. If not, it retrieves the number of pending messages from the subscription and if there are no pending messages, it calls the sendEndOfData method on the handler.
In summary, the finishInit method initializes a NatsWatchSubscription object by setting up the subscription to a specified subject, configuring the delivery options, and ensuring that the appropriate end of data indication is sent to the message handler.
public void unsubscribe() {
if (dispatcher != null) {
dispatcher.unsubscribe(sub);
if (dispatcher.getSubscriptionHandlers().size() == 0) {
dispatcher.connection.closeDispatcher(dispatcher);
dispatcher = null;
}
}
}This method is defined in the NatsWatchSubscription class of the io.nats.client.impl package. It is used to unsubscribe from a subscription.
The method has the following steps:
- Check if the
dispatchervariable is notnull. - If
dispatcheris notnull, call theunsubscribe()method on thedispatcherobject, passing thesub(subscription) parameter. - Check if the
dispatcherobject'sgetSubscriptionHandlers()method returns a size of 0. - If the size is 0, it means that there are no more handlers subscribed to the
dispatcher. In this case, call thecloseDispatcher()method on theconnectionobject, passing thedispatcheras a parameter, and setdispatchervariable tonull.
Note: The dispatcher and connection objects are assumed to be defined in the NatsWatchSubscription class.
The unsubscribe method, defined in the NatsWatchSubscription class of the io.nats.client.impl package, is responsible for unsubscribing from a NATS topic subscription.
The method first checks if the dispatcher (a NatsDispatcher object) is not null. If it is not null, it calls the unsubscribe method of the dispatcher object, passing the sub (a SubscriptionImpl object) as an argument. This effectively unsubscribes from the topic.
Next, it checks if there are no more subscription handlers registered with the dispatcher. If there are no subscription handlers, it closes the connection for the dispatcher by calling the closeDispatcher method of the connection object and sets the dispatcher to null.
In summary, the unsubscribe method allows for the graceful unsubscription from a NATS topic, cleaning up the necessary resources in case there are no more subscription handlers left.

MessageQueue
The MessageQueue class is a software component that provides a mechanism for storing and organizing messages. It allows messages to be added to the queue in a sequential order and retrieved from the queue based on a specific criteria. This class serves as a central hub for message management, ensuring efficient and reliable message delivery within a software system.
boolean push(NatsMessage msg, boolean internal) {
this.filterLock.lock();
try {
// If we aren't running, then we need to obey the filter lock
// to avoid ordering problems
if (!internal && this.discardWhenFull) {
return this.queue.offer(msg);
}
if (!this.offer(msg)) {
throw new IllegalStateException("Output queue is full " + queue.size());
}
this.sizeInBytes.getAndAdd(msg.getSizeInBytes());
this.length.incrementAndGet();
return true;
} finally {
this.filterLock.unlock();
}
}The push method in the MessageQueue class is used to add a message to the queue. It takes two parameters: msg, which is the NatsMessage to be pushed, and internal, a boolean flag indicating whether the push operation is internal.
Here is a step-by-step description of what the push method is doing based on its body:
-
The method acquires a lock (
filterLock) to ensure thread safety while modifying the queue. -
If the push operation is not internal and the
discardWhenFullflag is set totrue, the method tries to add themsgto the queue using theoffermethod of the underlying queue implementation (this.queue). If the queue is full, the method immediately returnsfalse, indicating that the message was not added. -
If the push operation is internal or the
discardWhenFullflag is set tofalse, themsgis added to the queue using theoffermethod. If theoffermethod returnsfalse, indicating that the queue is full, anIllegalStateExceptionis thrown with a message indicating the size of the queue. -
The
sizeInBytesfield of theMessageQueueis updated by addingmsg.getSizeInBytes()to its current value using thegetAndAddmethod of theAtomicLongclass. This field represents the total size of all messages in the queue in bytes. -
The
lengthfield of theMessageQueueis incremented by one using theincrementAndGetmethod of theAtomicIntegerclass. This field represents the current length of the queue, i.e., the number of messages in the queue. -
The method returns
trueto indicate that the message was successfully added to the queue. -
Finally, the lock (
filterLock) is released in afinallyblock to ensure that it is always unlocked, even in case of exceptions.
The push method defined in the io.nats.client.impl.MessageQueue class is responsible for adding a message to the message queue.
The method takes two parameters: msg, which represents the message to be pushed onto the queue, and internal, a boolean flag indicating whether the push operation is internal or not.
Within the method, a lock is acquired to ensure thread safety. If the push operation is not internal and the queue is set to discard messages when full, the method will attempt to offer the message to the queue using the offer method. The offer method returns true if successful, indicating that the message was added to the queue.
If the push operation is internal or the queue is not set to discard messages when full, and the push operation using the offer method fails, an IllegalStateException is thrown with a message indicating the queue is full.
After successfully pushing the message to the queue, the size and length of the queue are updated. The size of the message in bytes is incremented using msg.getSizeInBytes(), and the length of the queue is incremented by one. Finally, the method returns true to indicate that the push operation was successful.
Regardless of the outcome, the lock is released using the unlock method in a finally block to ensure that the lock is always released, even if an exception is thrown during the push operation.

void poisonTheQueue() {
try {
this.queue.add(this.poisonPill);
} catch (IllegalStateException ie) {
// queue was full, so we don't really need poison pill
// ok to ignore this
}
}This method is defined in the io.nats.client.impl.MessageQueue class. It adds a poison pill to the queue.
void poisonTheQueue()- The method creates a
try-catchblock to handle anyIllegalStateExceptionthat may be thrown during the execution. - Inside the
tryblock, the method addsthis.poisonPillto the queue using theaddmethod. - If the queue was full and the
addoperation throws anIllegalStateException, it means that adding the poison pill was not possible. - In the
catchblock, anIllegalStateExceptionis caught and ignored. No action is taken in this case. - The method does not return a value.
Note: The purpose of adding a poison pill to the queue is usually to indicate that the queue should be terminated or stopped. This allows consumers of the queue to recognize the poison pill and take appropriate actions.
The poisonTheQueue method in the MessageQueue class is used to add a poison pill to the queue.
A poison pill is a special message or object that is used to signal the termination of a processing queue. In this method, the poisonPill object is added to the queue using the add method. If the queue is full and cannot accept any more elements, an IllegalStateException is thrown. However, this exception is caught and ignored, as the poison pill is not necessary if the queue is already full.
The purpose of adding a poison pill to the queue is to ensure that any consumers waiting for messages from the queue are eventually notified of its termination. When a consumer encounters the poison pill, it knows that there are no more messages to process and can gracefully exit the processing loop.

boolean offer(NatsMessage msg) {
try {
return this.queue.offer(msg, 5, TimeUnit.SECONDS);
} catch (InterruptedException ie) {
return false;
}
}The offer method, defined in the MessageQueue class within the io.nats.client.impl package, takes a NatsMessage object as an argument and returns a boolean value. This method is responsible for adding the specified message to the message queue.
Here is a step-by-step description of what the offer method does:
-
The method takes the
NatsMessageobject,msg, as a parameter. -
Inside the method, a try-catch block is used to handle any potential
InterruptedExceptionthat may occur. -
The
offermethod of the underlying queue is called, passing in themsgobject as well as a timeout value of 5 seconds and theTimeUnit.SECONDSenum. -
If the
offermethod is able to successfully add themsgobject to the queue within the specified timeout period, it will returntrue. -
If the operation is interrupted, the
catchblock will catch theInterruptedExceptionand handle it accordingly. -
In case of an
InterruptedException, the method will returnfalse.
In summary, the offer method attempts to add the specified NatsMessage object to the message queue with a timeout of 5 seconds. If the operation is successful, it returns true, otherwise it returns false.
The offer method of the io.nats.client.impl.MessageQueue class is used to add a NatsMessage object to the internal queue.
The method attempts to add the NatsMessage object to the queue using the offer method of the underlying Queue implementation. The offer method has a timeout of 5 seconds, meaning that if the queue is full, the method will wait for up to 5 seconds for space to become available before returning false.
If the operation is interrupted by an InterruptedException, the method catches the exception and returns false.
Overall, the offer method provides a way to add messages to the MessageQueue, with a timeout for handling cases where the queue is full.

NatsMessage poll(Duration timeout) throws InterruptedException {
NatsMessage msg = null;
if (timeout == null || this.isDraining()) {
// try immediately
msg = this.queue.poll();
} else {
long nanos = timeout.toNanos();
if (nanos != 0) {
msg = this.queue.poll(nanos, TimeUnit.NANOSECONDS);
} else {
// A value of 0 means wait forever
// We will loop and wait for a LONG time
// if told to suspend/drain the poison pill will break this loop
while (this.isRunning()) {
msg = this.queue.poll(100, TimeUnit.DAYS);
if (msg != null)
break;
}
}
}
if (msg == poisonPill) {
return null;
}
return msg;
}The poll method of the MessageQueue class is responsible for retrieving a message from the internal message queue.
-
timeout(optional): ADurationobject specifying the maximum time to wait for a message before returning. If not specified or if the queue is in the draining state, the method tries to immediately retrieve a message from the queue.
-
NatsMessage: The retrieved message from the queue. If the retrieved message is the poison pill (a special marker used to signal the termination of the queue), the method returnsnull.
-
If
timeoutis not specified or if the queue is in the draining state:- Assign the result of calling the
pollmethod on the queue tomsg.- If there are no messages in the queue,
msgwill benull.
- If there are no messages in the queue,
- Assign the result of calling the
-
If
timeoutis specified and the queue is not in the draining state:- Convert
timeoutinto the number of nanoseconds and assign it tonanos. - If
nanosis not equal to 0:- Assign the result of calling the
pollmethod on the queue with thenanosandTimeUnit.NANOSECONDSparameters tomsg. - If there are no messages in the queue within the specified timeout,
msgwill benull.
- Assign the result of calling the
- If
nanosis equal to 0, indicating an infinite wait time:- Enter a loop while the queue is in the running state:
- Assign the result of calling the
pollmethod on the queue with a timeout of 100 days tomsg. - If a message is retrieved (
msgis notnull), exit the loop. - This loop continues until a message is retrieved or the queue is no longer running.
- Assign the result of calling the
- Enter a loop while the queue is in the running state:
- Convert
-
If
msgis equal to the poison pill:- Return
null.
- Return
-
Return
msg, which is the retrieved message from the queue.
The poll method in the MessageQueue class is used to retrieve a NatsMessage from the queue. It takes a Duration parameter representing the timeout for waiting to receive a message.
If the timeout is null or the queue is in a draining state, the method immediately tries to retrieve a message using this.queue.poll().
If the timeout is not null and the queue is not in a draining state, the method waits for the specified duration to receive a message using this.queue.poll(nanos, TimeUnit.NANOSECONDS).
If the duration is zero, meaning wait forever, the method will loop and wait for a message to be available. It uses this.queue.poll(100, TimeUnit.DAYS) to wait indefinitely for a message, and breaks the loop if a message is received or if the queue is no longer running.
If the retrieved message is the poison pill (a special value used to indicate the end of the queue), null is returned. Otherwise, the retrieved NatsMessage is returned.
NatsMessage pop(Duration timeout) throws InterruptedException {
if (!this.isRunning()) {
return null;
}
NatsMessage msg = this.poll(timeout);
if (msg == null) {
return null;
}
this.sizeInBytes.getAndAdd(-msg.getSizeInBytes());
this.length.decrementAndGet();
return msg;
}The pop method in the MessageQueue class is used to retrieve a NatsMessage from the queue. Here is a step-by-step description of what the method does:
-
Check if the message queue is running:
- If the message queue is not running, return
null.
- If the message queue is not running, return
-
Call the
pollmethod with a specified timeout duration:- The
pollmethod waits for a specified duration to retrieve a message from the queue. - If a message is retrieved within the timeout, assign it to the
msgvariable. - If no message is retrieved within the timeout, return
null.
- The
-
Update the size and length of the queue:
- If a message is retrieved (
msgis notnull), subtract its size in bytes from the total size of the queue using thesizeInBytes.getAndAdd(-msg.getSizeInBytes())method. - Decrement the length of the queue using the
length.decrementAndGet()method.
- If a message is retrieved (
-
Return the retrieved message:
- Return the retrieved
msgobject.
- Return the retrieved
Note: If the pop method is interrupted while waiting for a message (due to InterruptedException), it will propagate the exception.
The pop method in the MessageQueue class, io.nats.client.impl.MessageQueue.pop, retrieves the next message from the queue and removes it. It takes a Duration parameter timeout to specify how long to wait for a message to become available in case the queue is empty. If the MessageQueue is not currently running, the method returns null. Otherwise, it calls the poll method with the specified timeout to get the next message. If there is no message available within the specified timeout, it returns null. If a message is retrieved successfully, the method updates the total size of the queue by subtracting the size of the retrieved message from sizeInBytes, decrements the length of the queue by one, and finally returns the retrieved message.

// Use the next field to read the entire set accumulated. // maxSize and maxMessages are both checked and if either is exceeded // the method returns. // // A timeout of 0 will wait forever (or until the queue is stopped/drained) // // Only works in single reader mode, because we want to maintain order. // accumulate reads off the concurrent queue one at a time, so if multiple // readers are present, you could get out of order message delivery. NatsMessage accumulate(long maxSize, long maxMessages, Duration timeout) throws InterruptedException
// Waits up to the timeout to try to accumulate multiple messages
// Use the next field to read the entire set accumulated.
// maxSize and maxMessages are both checked and if either is exceeded
// the method returns.
//
// A timeout of 0 will wait forever (or until the queue is stopped/drained)
//
// Only works in single reader mode, because we want to maintain order.
// accumulate reads off the concurrent queue one at a time, so if multiple
// readers are present, you could get out of order message delivery.
NatsMessage accumulate(long maxSize, long maxMessages, Duration timeout) throws InterruptedException {
if (!this.singleThreadedReader) {
throw new IllegalStateException("Accumulate is only supported in single reader mode.");
}
if (!this.isRunning()) {
return null;
}
NatsMessage msg = this.poll(timeout);
if (msg == null) {
return null;
}
long size = msg.getSizeInBytes();
if (maxMessages <= 1 || size >= maxSize) {
this.sizeInBytes.addAndGet(-size);
this.length.decrementAndGet();
return msg;
}
long count = 1;
NatsMessage cursor = msg;
while (cursor != null) {
NatsMessage next = this.queue.peek();
if (next != null && next != this.poisonPill) {
long s = next.getSizeInBytes();
if (maxSize < 0 || (size + s) < maxSize) {
// keep going
size += s;
count++;
cursor.next = this.queue.poll();
cursor = cursor.next;
if (count == maxMessages) {
break;
}
} else {
// One more is too far
break;
}
} else {
// Didn't meet max condition
break;
}
}
this.sizeInBytes.addAndGet(-size);
this.length.addAndGet(-count);
return msg;
}The accumulate method in the MessageQueue class is used to accumulate multiple messages from the queue. It waits for a specified timeout duration to accumulate messages, up to a maximum size and maximum number of messages.
Here is a step-by-step description of what the method does:
- Check if the queue is in single reader mode. If not, throw an
IllegalStateExceptionindicating that accumulate is only supported in single reader mode. - Check if the queue is running. If not, return
null. - Use the
pollmethod to retrieve the next message from the queue, waiting for the specified timeout duration. If no message is received, returnnull. - Get the size of the retrieved message.
- Check if the
maxMessagesis less than or equal to 1 or if the size of the retrieved message is greater than or equal to themaxSize. If true, decrement the size and length counters, and return the retrieved message. - Initialize variables
sizeto the size of the retrieved message andcountto 1. - Start a loop to accumulate more messages.
- Peek at the next message in the queue without removing it.
- If a next message exists and it is not the
poisonPill: a. Get the size of the next message. b. Check if themaxSizeis less than 0 or if the accumulated size plus the next message size is less thanmaxSize. If true, continue accumulating.- Increase the accumulated size and count.
- Set the
nextreference of the current message to the next message and assign the next message to thecursorvariable. - Check if the accumulated count is equal to
maxMessages. If true, exit the loop. c. If the accumulated size is greater than or equal tomaxSize, exit the loop because accumulating one more message would exceed the maximum size. d. If the next message does not meet the max condition, exit the loop.
- Decrement the size and length counters by the accumulated size and count.
- Return the retrieved message.
Note: The accumulate method must be called in a single reader mode to maintain the order of message delivery. If multiple readers are present, there is a possibility of out-of-order message delivery.
The accumulate method in the MessageQueue class is used to accumulate multiple messages from the queue up to a specific maximum size or number of messages.
Here's how it works:
-
This method can only be used when the queue is in single reader mode, ensuring that message order is maintained.
-
The method waits for a specified timeout duration to try to accumulate multiple messages. If the timeout is set to 0, it will wait indefinitely until the queue is stopped or drained.
-
The method checks the
maxSizeandmaxMessagesparameters and if either limit is exceeded, the method returns. -
It retrieves the first message from the queue using the
pollmethod with the specified timeout duration. -
If no message is available, it returns
null. -
If the retrieved message's size is equal to or greater than the
maxSizeparameter ormaxMessagesis set to 1, it subtracts the message size from the total size of the queue and decrements the length by 1, then returns the message. -
If the retrieved message's size is less than the
maxSizeparameter andmaxMessagesis greater than 1, it continues to accumulate messages until either themaxMessageslimit is reached or the accumulated size (including the next message's size) exceeds themaxSize. -
For each additional message accumulated, it increases the total size and count, updates the
nextreference in the previously accumulated message, and moves on to the next message in the queue until themaxMessageslimit is reached or the accumulated size exceeds themaxSize. -
After accumulating all the messages, it subtracts the accumulated size from the total size of the queue and decrements the length by the number of accumulated messages.
-
Finally, it returns the first accumulated message.

void filter(Predicate<NatsMessage> p) {
this.filterLock.lock();
try {
if (this.isRunning()) {
throw new IllegalStateException("Filter is only supported when the queue is paused");
}
ArrayList<NatsMessage> newQueue = new ArrayList<>();
NatsMessage cursor = this.queue.poll();
while (cursor != null) {
if (!p.test(cursor)) {
newQueue.add(cursor);
} else {
this.sizeInBytes.addAndGet(-cursor.getSizeInBytes());
this.length.decrementAndGet();
}
cursor = this.queue.poll();
}
this.queue.addAll(newQueue);
} finally {
this.filterLock.unlock();
}
}The filter method in the MessageQueue class is used to filter the messages in the queue based on a given predicate.
Here is a step-by-step description of what the method does:
- Acquire a lock to ensure thread safety for the filtering operation.
- Check if the queue is currently running. If it is, throw an
IllegalStateExceptionwith the message "Filter is only supported when the queue is paused". - Create a new ArrayList to hold the filtered messages.
- Poll the queue to retrieve the next message (starting from the front of the queue) and assign it to the
cursorvariable. - Enter a loop that iterates until the
cursorvariable isnull. - Inside the loop, check if the
cursormessage satisfies the provided predicate (p.test(cursor)). If it does not pass the predicate test, add it to thenewQueuelist. - If the
cursormessage does pass the predicate test, subtract its size in bytes from the total size of all messages (sizeInBytes) usingthis.sizeInBytes.addAndGet(-cursor.getSizeInBytes()), and decrement the length of the queue (length.decrementAndGet()). - Poll the queue again to retrieve the next message and update the
cursorvariable. - Once the loop completes, add all the messages from the
newQueuelist back to the original queue usingthis.queue.addAll(newQueue). - Release the lock used for synchronization by calling
this.filterLock.unlock()in afinallyblock.
The filter method in the MessageQueue class is used to remove messages from the queue based on a provided predicate.
- It first checks if the queue is running, and if it is not, it throws an
IllegalStateExceptionsince filtering is only supported when the queue is paused. - Then, it creates a new
ArrayListto store the filtered messages. - It retrieves a message from the queue using
polland checks if the message satisfies the predicate. If the message does not satisfy the predicate, it is added to the new queue. - If the message satisfies the predicate, the size in bytes and the length of the queue are updated accordingly.
- This process continues until all messages in the queue have been processed.
- Finally, the new queue is added back to the original queue.
The
filtermethod is useful for selectively removing messages from the queue based on specific criteria defined by thePredicate.

ServerPoolEntry
The ServerPoolEntry class is a public class that represents an entry in a server pool. It is used to manage the connection to a server in a pool.
NatsKeyValueManagement
The NatsKeyValueManagement class is a public implementation of the KeyValueManagement interface. It provides functionality for managing key-value pairs.
public KeyValueStatus create(KeyValueConfiguration config) throws IOException, JetStreamApiException
@Override
public KeyValueStatus create(KeyValueConfiguration config) throws IOException, JetStreamApiException {
StreamConfiguration sc = config.getBackingConfig();
// most validation / KVC setup is done in the KeyValueConfiguration Builder
// but this is done here because the context has a connection which has the server info with a version
if (serverOlderThan272) {
// null discard policy will use default
sc = StreamConfiguration.builder(sc).discardPolicy(null).build();
}
return new KeyValueStatus(jsm.addStream(sc));
}The create method in the NatsKeyValueManagement class is used to create a key-value store based on a given configuration.
- The method takes a
KeyValueConfigurationobject as an argument. - Inside the method, the
StreamConfigurationobject is extracted from theKeyValueConfiguration. - The method checks if the server version is older than 2.7.2. If it is, it proceeds to the next step; otherwise, it skips the next step.
- If the server version is older than 2.7.2, the method creates a new
StreamConfigurationobject using a builder pattern. It sets the discard policy tonullto use the default discard policy, and builds the newStreamConfigurationobject. - Finally, the method calls the
addStreammethod on the underlying JetStream management (jsm) instance, passing in theStreamConfigurationobject as a parameter. - The method wraps the result of the
addStreammethod call in aKeyValueStatusobject and returns it.
The create method is implemented in the NatsKeyValueManagement class and it is used to create a key-value store using the given KeyValueConfiguration.
Here's a breakdown of what the method does:
- It retrieves the
StreamConfigurationfrom theKeyValueConfiguration. - It checks if the server version is older than 2.7.2.
- If the server version is older than 2.7.2, it sets the discard policy of the
StreamConfigurationto null (which will use the default discard policy). - It then adds the stream to the JetStream Manager (jsm) using the modified
StreamConfiguration. - Finally, it returns a
KeyValueStatusobject created with theaddStreamoperation on the JetStream Manager.
This method handles the creation of a key-value store by configuring the stream and adding it to the JetStream Manager.

IncomingMessage
IncomingMessage is a public class that extends the NatsMessage class. It represents an incoming message in the Nats messaging system. This class inherits all the properties and methods of the NatsMessage class and provides additional functionality specific to incoming messages.
ProtocolMessage
The ProtocolMessage class is a special version of the NatsMessage class that represents a protocol message. It inherits all the properties and functionalities of the NatsMessage class.
StreamListReader
The StreamListReader class is a subclass of AbstractListReader class. It extends the functionality of the AbstractListReader by implementing additional methods and behaviors specific to reading data from a stream.
void processItems(List items)
@Override
void processItems(List<JsonValue> items) {
for (JsonValue v : items) {
streams.add(new StreamInfo(v));
}
}The processItems method in the io.nats.client.impl.StreamListReader class is declared with the @Override annotation, indicating that it overrides a method from a parent class or interface.
This method takes a List of JsonValues as its parameter, named items. Here is the step-by-step description of what this method does:
- Start a loop to iterate through each JsonValue in the
itemsList. - For each JsonValue
vinitems, do the following:- Create a new
StreamInfoobject, passingvas the argument to its constructor. - Add the newly created
StreamInfoobject to thestreamslist.
- Create a new
The purpose of this method is to iterate through a list of JsonValues and create a new StreamInfo object for each value, then add it to the streams list. This method is likely used to process and handle a list of JsonValues representing streams in the application.
The processItems method in the io.nats.client.impl.StreamListReader class takes a list of JsonValue items as input. It iterates through each item in the list and creates a new StreamInfo object by passing the current item as a parameter. The created StreamInfo objects are then added to a streams list.
NatsMessageConsumerBase
The NatsMessageConsumerBase class is an implementation of the MessageConsumer interface. It provides the functionality to consume messages from a NATS messaging system.
protected void initSub(NatsJetStreamPullSubscription sub) throws JetStreamApiException, IOException {
this.sub = sub;
if (lastConsumerInfo == null) {
lastConsumerInfo = sub.getConsumerInfo();
}
pmm = (PullMessageManager) sub.manager;
}The initSub method is defined in the io.nats.client.impl.NatsMessageConsumerBase class. This method takes in a NatsJetStreamPullSubscription parameter and is responsible for initializing the subscription by setting the sub variable and performing additional setup operations.
- Set the
subvariable to the providedNatsJetStreamPullSubscriptionparameter. - Check if the
lastConsumerInfovariable isnull.- If
lastConsumerInfoisnull, proceed to the next step. - If
lastConsumerInfois notnull, skip to step 5.
- If
- Call the
getConsumerInfomethod of thesubobject to retrieve the consumer information. - Assign the consumer information returned by
getConsumerInfoto thelastConsumerInfovariable. - Cast the
sub.managerobject to aPullMessageManagerand assign it to thepmmvariable.
-
JetStreamApiException: This exception is thrown when there is an error with the JetStream API. -
IOException: This exception is thrown when there is an I/O operation failure.
The method initSub in the class NatsMessageConsumerBase is responsible for initializing a NatsJetStreamPullSubscription and setting it as the current subscription. The method is called with a parameter of type NatsJetStreamPullSubscription, which is assigned to the class variable sub.
In the method, it first assigns the passed subscription to the sub variable. If the lastConsumerInfo is null, it obtains the consumer information from the subscription and assigns it to lastConsumerInfo. Finally, it assigns the PullMessageManager instance from the subscription's manager to the class variable pmm.
This method potentially throws JetStreamApiException and IOException.

public void stop(long timeout) throws InterruptedException
@Override
public void stop(long timeout) throws InterruptedException {
synchronized (subLock) {
if (!stopped) {
try {
if (sub.getNatsDispatcher() != null) {
sub.getDispatcher().drain(Duration.ofMillis(timeout));
} else {
sub.drain(Duration.ofMillis(timeout));
}
} finally {
stopped = true;
}
}
}
}The stop(long timeout) method in the NatsMessageConsumerBase class is used to stop the message consumer. Here is a step-by-step description of what the method is doing:
- It acquires a lock on the
subLockobject to ensure thread safety. - It checks if the consumer has already been stopped. If it has, the method does nothing.
- If the consumer has not been stopped, it proceeds to drain the message dispatcher.
- Inside a try-finally block:
a. It checks if the consumer has a
NatsDispatcherby calling thegetNatsDispatcher()method on thesubobject. b. If aNatsDispatcheris available, it calls thedrain(Duration timeout)method on theDispatcherobject associated with the consumer, passing the specified timeout value. c. If aNatsDispatcheris not available, it calls thedrain(Duration timeout)method on thesubobject, passing the specified timeout value. d. Thedrain()method blocks until all messages have been processed or the timeout is reached. - Finally, it sets the
stoppedflag totrue, indicating that the consumer has been stopped.
Note: The Duration.ofMillis(timeout) method is used to specify the timeout duration in milliseconds. The InterruptedException may be thrown if the thread is interrupted while waiting for the drain() method to complete.
The stop method in the NatsMessageConsumerBase class is responsible for stopping the message consumer by performing certain actions:
- It acquires a lock using the
subLockobject to ensure thread safety during the execution of the method. - It checks if the message consumer has not already been stopped by examining the
stoppedvariable. - If the consumer has not been stopped, it proceeds with the following actions:
a. It checks if the underlying NATS dispatcher is available through the
sub.getNatsDispatcher()method. If available, it drains the dispatcher by calling thedrainmethod with the specified timeout duration. b. If the dispatcher is not available, it directly drains the subscription object by calling thedrainmethod with the specified timeout duration. - Once the draining process is completed, the
finallyblock sets thestoppedvariable to true to indicate that the consumer has been stopped.
The method takes a timeout parameter of type long which specifies the maximum time to wait for the draining process to complete. It throws an InterruptedException if the thread is interrupted while waiting for the draining process to finish.

public void close() throws Exception
@Override
public void close() throws Exception {
try {
if (!stopped && sub.isActive()) {
if (sub.getNatsDispatcher() != null) {
sub.getDispatcher().unsubscribe(sub);
} else {
sub.unsubscribe();
}
}
} catch (Throwable ignore) {
// nothing to do
}
}The close() method in the NatsMessageConsumerBase class is used to unsubscribe the NATS message consumer from its associated topic.
Here is a step-by-step description of what the close() method does based on its body:
- Checks if the NATS message consumer is not already stopped (
stoppedvariable isfalse) and the subscription is still active (sub.isActive()returnstrue). - If the NATS message consumer has a NATS dispatcher assigned (
sub.getNatsDispatcher() != null), it calls theunsubscribe()method on the dispatcher and passes in thesubobject. This allows the NATS dispatcher to handle the unsubscription process. - If the NATS message consumer does not have a NATS dispatcher assigned, it directly calls the
unsubscribe()method on the subscription itself (sub.unsubscribe()). This is the case when the subscription was created without a dispatcher. - If any exception or error occurs during the unsubscription process, it is caught and ignored (
Throwable ignore). This is done to keep the close() method from throwing any exceptions.
Overall, the close() method ensures that the NATS message consumer is safely unsubscribed from the topic it is consuming messages from.
The close method defined in the NatsMessageConsumerBase class is responsible for unsubscribing and closing a NATS message consumer.
Here's a breakdown of what the method does:
- It first checks if the consumer is not already stopped and the subscription is active.
- If the
NatsDispatcherassociated with the consumer is not null, it calls theunsubscribemethod on the dispatcher and passes the subscription object as a parameter. - If the
NatsDispatcheris null, it calls theunsubscribemethod directly on the subscription object. - Any exceptions or errors thrown during the execution of the method are caught and ignored.
In summary, the purpose of this method is to safely unsubscribe and close the NATS message consumer.

PushMessageManager
The PushMessageManager class is a subclass of MessageManager that is responsible for managing the sending and receiving of push notifications. It inherits all the functionalities of the MessageManager class and adds specific methods and features for handling push messages in a software application.
protected void startup(NatsJetStreamSubscription sub)
@Override
protected void startup(NatsJetStreamSubscription sub) {
super.startup(sub);
sub.setBeforeQueueProcessor(this::beforeQueueProcessorImpl);
if (hb) {
initOrResetHeartbeatTimer();
}
}This method is defined in the io.nats.client.impl.PushMessageManager class and is used to initialize the NatsJetStreamSubscription and perform additional operations before starting the subscription.
The method starts by calling the super.startup(sub) to execute the startup logic in the parent class.
The method sets the beforeQueueProcessor of the NatsJetStreamSubscription to this::beforeQueueProcessorImpl. This means that the beforeQueueProcessorImpl method will be called before processing each message in the subscription queue.
The method checks if the hb flag is enabled. If it is, the method proceeds to the next step. If not, the method skips to the end.
If the hb flag is enabled, the method calls the initOrResetHeartbeatTimer method. This method is responsible for initializing or resetting the heartbeat timer used for monitoring the health of the subscription.
The method ends after completing the above steps. Any further logic or methods outside of this method will be executed after the startup process is completed.
Note: The details of the beforeQueueProcessorImpl and initOrResetHeartbeatTimer methods are not provided in the given code snippet. They could be defined elsewhere in the PushMessageManager class or its parent class.
The startup method is an overridden method in the PushMessageManager class of the io.nats.client.impl package. It takes a NatsJetStreamSubscription object as a parameter.
The purpose of this method is to perform initialization tasks when starting up the PushMessageManager for the given subscription.
- It calls the
super.startup(sub)method to invoke the startup method of the parent class, which likely performs some general initialization tasks. - It sets a specific "before queue processor" for the given subscription by calling the
sub.setBeforeQueueProcessor(this::beforeQueueProcessorImpl)method. This means that the specified implementation (beforeQueueProcessorImplmethod) will be executed before each message is pushed to the queue of the subscription. - If a heartbeat flag (
hb) is set to true, it initializes or resets a heartbeat timer by calling theinitOrResetHeartbeatTimer()method. This suggests that thePushMessageManagermight be responsible for managing heartbeats in some way.
Overall, this method sets up necessary components and configurations for the PushMessageManager associated with the given subscription.

protected Boolean beforeQueueProcessorImpl(NatsMessage msg)
@Override
protected Boolean beforeQueueProcessorImpl(NatsMessage msg) {
if (hb) {
// only need to track when heartbeats are expected
messageReceived();
Status status = msg.getStatus();
if (status != null) {
// only fc heartbeats get queued
if (status.isHeartbeat()) {
// true if a fc hb
return hasFcSubject(msg);
}
}
}
return true;
}The beforeQueueProcessorImpl method, defined in the PushMessageManager class of the io.nats.client.impl package, performs the following steps:
- Overrides the
beforeQueueProcessorImplmethod from the superclass. - Takes a
NatsMessageobject as a parameter. - Checks if the variable
hbis true. - If
hbis true, calls themessageReceivedmethod to track the message received. - Gets the status from the
NatsMessageobject and assigns it to thestatusvariable. - Checks if the
statusvariable is not null. - If the
statusvariable is not null, checks if it represents a heartbeat message. - If the
statusrepresents a heartbeat message, calls thehasFcSubjectmethod to determine if it is a fc (flow control) heartbeat. - Returns
trueifhbis false or if thestatusdoes not represent a heartbeat message, otherwise returns the result of thehasFcSubjectmethod.
This method is protected, meaning it can only be accessed within the same class or its subclasses. It is intended for internal use and its purpose is to perform logic related to message processing before adding the message to the queue.
The method beforeQueueProcessorImpl in the class PushMessageManager is an overridden method that takes in a NatsMessage object as its parameter and returns a Boolean value.
The purpose of this method is to process a message before it is added to the message queue.
The method starts by checking if the hb variable is true. If it is, it means that heartbeats are expected, so the method proceeds with further processing.
Next, it calls the messageReceived() method to track the receipt of the message.
Then, it retrieves the status of the message using the getStatus() method and assigns it to the status variable.
If the status variable is not null, it checks if the status is a heartbeat. If it is, it calls the hasFcSubject() method to determine if the message has a "fc" subject.
Finally, if any of the conditions mentioned above are satisfied, it returns true. Otherwise, it returns true as the default value.

protected ManageResult manage(Message msg)
@Override
protected ManageResult manage(Message msg) {
if (msg.getStatus() == null) {
trackJsMessage(msg);
return MESSAGE;
}
return manageStatus(msg);
}The manage method in the PushMessageManager class in the io.nats.client.impl package is responsible for handling incoming messages and processing them based on their status.
Here is a step-by-step description of what the manage method does based on its body:
-
The method is overridden with the
@Overrideannotation, indicating that it overrides a method from a superclass or interface. -
The
managemethod takes aMessageobject as its parameter, which represents an incoming message. -
The method checks if the
msgobject's status is null using thegetStatus()method. This condition is checked using the==operator. -
If the status is null, it means that the message does not have a status. In this case, the
trackJsMessagemethod is called to track the message for JavaScript. The exact details of this method are not provided in the given code snippet. -
After tracking the JavaScript message, the method returns the
ManageResultenum valueMESSAGE. TheManageResultenum likely represents the result of managing the message, which can be used for further processing or error handling. -
If the status is not null, meaning the message has a status, the method calls the
manageStatusmethod to handle the message based on its status. The details of this method are not provided in the given code snippet.
It is important to note that the exact behavior of the manage method and the methods it calls (trackJsMessage and manageStatus) may vary depending on the implementation details of the PushMessageManager class and its dependencies. Without further context or code details, it is not possible to provide a more specific description of the functionality of these methods.
The manage method in the PushMessageManager class is responsible for handling messages received from a Message object.
The method works as follows:
- It first checks if the status of the message is null.
- If the status is null, it tracks the message using the
trackJsMessagemethod and returns aManageResultof typeMESSAGE. - If the status is not null, it delegates the handling of the message to the
manageStatusmethod and returns the result of that method.
Overall, the manage method performs the necessary processing based on the status of the message and returns the appropriate result.

protected ManageResult manageStatus(Message msg) {
// this checks fc, hb and unknown
// only process fc and hb if those flags are set
// otherwise they are simply known statuses
Status status = msg.getStatus();
if (fc) {
boolean isFcNotHb = status.isFlowControl();
String fcSubject = isFcNotHb ? msg.getReplyTo() : extractFcSubject(msg);
if (fcSubject != null) {
processFlowControl(fcSubject, isFcNotHb ? FLOW_CONTROL : HEARTBEAT);
return STATUS_HANDLED;
}
}
conn.executeCallback((c, el) -> el.unhandledStatus(c, sub, status));
return STATUS_ERROR;
}The manageStatus method is defined in the class io.nats.client.impl.PushMessageManager and takes a Message as its parameter.
The method starts by retrieving the status from the input msg.
Then, it checks if the fc flag is set. If it is set, it determines whether the status is related to flow control or heartbeat. If it is related to flow control, it gets the flow control subject from the replyTo field of the msg object, otherwise it calls the extractFcSubject method to get the flow control subject.
If a valid flow control subject is obtained, it then calls the processFlowControl method, passing the flow control subject and the appropriate type (FLOW_CONTROL or HEARTBEAT). Finally, it returns STATUS_HANDLED.
If the fc flag is not set, or if the flow control subject is not obtained, it calls the executeCallback method of the conn object, passing a lambda function that handles unhandled statuses. The lambda function calls the unhandledStatus method of the el (element) object, passing the connection c, the subscription sub, and the status.
Ultimately, the method returns STATUS_ERROR if no flow control subject is obtained or if the status is not handled.
Please note that the code snippets provided in the description are not complete and might not compile as-is. The description assumes that the missing code and relevant objects and variables are properly defined and accessible within the class and its scope.
The method manageStatus in class PushMessageManager is responsible for managing the status of a Message object.
It starts by retrieving the Status from the message.
Then, it checks if the flag fc is set. If fc is true, it determines whether the status is flow control or heartbeat.
If the status is flow control, it extracts the flow control subject and processes it accordingly. It returns STATUS_HANDLED to indicate that the status was successfully handled.
If the status is not flow control, it executes the callback defined in conn.executeCallback passing the connection, event loop, and status as arguments. This callback handles unhandled statuses.
Finally, if any errors occur during the process, it returns STATUS_ERROR.

private void processFlowControl(String fcSubject, FlowControlSource source) {
// we may get multiple fc/hb messages with the same reply
// only need to post to that subject once
if (fcSubject != null && !fcSubject.equals(lastFcSubject)) {
conn.publishInternal(fcSubject, null, null, null);
// set after publish in case the pub fails
lastFcSubject = fcSubject;
conn.executeCallback((c, el) -> el.flowControlProcessed(c, sub, fcSubject, source));
}
}The processFlowControl method is defined in the io.nats.client.impl.PushMessageManager class. It takes two parameters, fcSubject and source, both of unspecified types.
- Check if fcSubject is not null and is different from the lastFcSubject variable.
- If the above condition is true, publish an internal message to the fcSubject using the conn.publishInternal method. The message being published is null and all other parameters for the publishInternal method are also null.
- After publishing the message, set the lastFcSubject variable equal to fcSubject.
- Call the executeCallback method of the conn object passing a lambda expression as a parameter. The lambda expression takes two parameters, c and el, and calls the flowControlProcessed method of el passing c, sub, fcSubject, and source as arguments.
Note: The specific types of the parameters fcSubject and source, as well as the objects conn and sub, are not provided in the given code snippet.
The processFlowControl method in the PushMessageManager class handles the flow control logic for receiving flow control messages. It takes in a fcSubject and a source as arguments.
This method first checks if the fcSubject is not null and different from the last processed flow control subject. If it is a new flow control subject, the method publishes a message to the fcSubject using the conn.publishInternal method. After successfully publishing the message, it updates the lastFcSubject variable with the new fcSubject.
Finally, it executes a callback function, passing in the client connection, the event loop, the subscriber, the fcSubject, and the source. This allows for additional processing or handling of the flow control event.

NatsJetStreamMessage
The NatsJetStreamMessage class is a subclass of IncomingMessage that represents a message received from the NATS JetStream messaging system. It provides additional functionality and properties specific to JetStream messages.
public void ackSync(Duration d) throws InterruptedException, TimeoutException
@Override
public void ackSync(Duration d) throws InterruptedException, TimeoutException {
if (ackHasntBeenTermed()) {
validateDurationRequired(d);
Connection nc = getJetStreamValidatedConnection();
if (nc.request(replyTo, AckAck.bytes, d) == null) {
throw new TimeoutException("Ack response timed out.");
}
lastAck = AckAck;
}
}This method is used to acknowledge the successful processing of a JetStream message. It sends a synchronous acknowledgement to the server and waits for a response within the given timeout duration.
-
d(Duration): The timeout duration for waiting for the acknowledgement response.
-
Check if the message has not already been acknowledged (ackHasntBeenTermed).
-
Validate the provided duration (validateDurationRequired).
-
Get the validated JetStream connection (getJetStreamValidatedConnection).
-
Send a request to the server using the replyTo subject and the AckAck bytes as the payload. Use the provided duration as the timeout.
-
If the response is null, throw a TimeoutException with the message "Ack response timed out."
-
Set the lastAck to AckAck to indicate that the acknowledgement was successful.
The ackSync method in class NatsJetStreamMessage is responsible for synchronously acknowledging the receipt of a message.
This method takes a duration parameter (d) which represents the maximum time to wait for an acknowledgment response before throwing a TimeoutException.
In this method, it first checks if the acknowledgment has already been terminated by checking the status using ackHasntBeenTermed(). If the acknowledgment has not been terminated, it proceeds with the acknowledgment process.
It validates the provided duration by calling validateDurationRequired(d), ensuring it meets the necessary requirements.
Next, it retrieves the validated JetStream connection by calling getJetStreamValidatedConnection().
The method then sends an acknowledgment request to the NATS server using the request method of the connection object (nc). It specifies the reply-to subject (replyTo) and the acknowledgment bytes (AckAck.bytes) to be sent. If the acknowledgment response times out (i.e., nc.request returns null), a TimeoutException is thrown.
Finally, if the acknowledgment request is successful, the method updates the last acknowledgment status (lastAck) to AckAck.

public NatsJetStreamMetaData metaData()
@Override
public NatsJetStreamMetaData metaData() {
if (this.jsMetaData == null) {
this.jsMetaData = new NatsJetStreamMetaData(this);
}
return this.jsMetaData;
}The metaData method in the NatsJetStreamMessage class is responsible for returning the metadata associated with the current instance of the NatsJetStreamMessage class.
The method begins with an @Override annotation, indicating that it overrides a method declared in a parent class or implemented in an interface.
The first statement in the method checks if the jsMetaData instance variable is null. This variable is of type NatsJetStreamMetaData and is declared in the class itself. If it is indeed null, it means that the metadata has not been initialized yet.
In that case, the method proceeds to instantiate a new NatsJetStreamMetaData object, passing the current instance of the NatsJetStreamMessage class as a parameter to the constructor. This allows the NatsJetStreamMetaData object to access and work with the properties and methods of the NatsJetStreamMessage class.
Once the jsMetaData variable is initialized with the new NatsJetStreamMetaData object, it is assigned to the class instance's jsMetaData variable, ensuring that subsequent calls to metaData will return the same object without re-instantiating it.
Finally, the method returns the jsMetaData object.
It is important to note that without further information on the NatsJetStreamMetaData class and its members, it is not possible to provide a detailed description of what the metaData method does with the BODY parameter. However, based on the provided code snippet, it can be inferred that the metaData method is responsible for initializing and returning the associated metadata object for the current NatsJetStreamMessage instance.
The metaData method in the NatsJetStreamMessage class of the io.nats.client.impl package is used to retrieve the metadata associated with the current message.
If the jsMetaData object is not yet initialized, the method creates a new instance of NatsJetStreamMetaData using the current message and assigns it to the jsMetaData variable.
Finally, the method returns the jsMetaData object containing the metadata of the message.

private void ackReply(AckType ackType, long delayNanos) {
if (ackHasntBeenTermed()) {
Connection nc = getJetStreamValidatedConnection();
nc.publish(replyTo, ackType.bodyBytes(delayNanos));
lastAck = ackType;
}
}The ackReply method in the NatsJetStreamMessage class is responsible for acknowledging a message received from a NATS JetStream subscription. Here is a step-by-step description of what the method does based on its body:
- Check if the message has not been terminated yet by calling the
ackHasntBeenTermed()method. - Obtain a validated connection to the JetStream server by calling the
getJetStreamValidatedConnection()method. - Publish an acknowledgement message to the reply subject using the obtained connection. The acknowledgement message is created by calling the
bodyBytes(delayNanos)method on theackTypeparameter. - Set the
lastAckinstance variable to the value ofackType.
Note: It is assumed that the replyTo instance variable is already set with the appropriate reply subject before calling the ackReply method.
The ackReply method in the NatsJetStreamMessage class is used to send an acknowledgment reply for a JetStream message.
This method takes two parameters: ackType and delayNanos.
If the acknowledgement for the message has not already been terminated, the method gets the validated JetStream connection, publishes an acknowledgment message to the replyTo subject with the appropriate body bytes based on the specified ackType and delayNanos, and updates the lastAck variable with the provided ackType.
The purpose of this method is to allow the software to acknowledge the receipt of a JetStream message and provide the necessary information for further processing or handling of the message.

NatsJetStreamPullSubscription
The NatsJetStreamPullSubscription class is a subclass of NatsJetStreamSubscription and represents a subscription to a JetStream pull-based consumer. It provides functionalities for receiving and processing messages from a JetStream pull-based source.
protected String _pull(PullRequestOptions pullRequestOptions, boolean raiseStatusWarnings, TrackPendingListener trackPendingListener)
protected String _pull(PullRequestOptions pullRequestOptions, boolean raiseStatusWarnings, TrackPendingListener trackPendingListener) {
String publishSubject = js.prependPrefix(String.format(JSAPI_CONSUMER_MSG_NEXT, stream, consumerName));
String pullSubject = getSubject().replace("*", Long.toString(this.pullSubjectIdHolder.incrementAndGet()));
manager.startPullRequest(pullSubject, pullRequestOptions, raiseStatusWarnings, trackPendingListener);
connection.publish(publishSubject, pullSubject, pullRequestOptions.serialize());
connection.lenientFlushBuffer();
return pullSubject;
}The method _pull is defined in the class io.nats.client.impl.NatsJetStreamPullSubscription. Its purpose is to perform a pull operation on a JetStream subscription.
Here is a step-by-step description of what the method is doing based on its body:
- It initializes a
publishSubjectvariable by prepending the prefix to the formatted stringJSAPI_CONSUMER_MSG_NEXTusing the values ofstreamandconsumerName. - It generates a
pullSubjectby replacing the asterisk (*) wildcard in the subject with the current value ofthis.pullSubjectIdHolder.incrementAndGet(). This value represents a unique identifier for the pull request. - It invokes the
startPullRequestmethod of themanagerobject with thepullSubject,pullRequestOptions,raiseStatusWarnings, andtrackPendingListenerarguments. This method is responsible for initiating the pull request. - It publishes a message to the
publishSubjectusing thepullSubjectand the serialized form ofpullRequestOptions. - It flushes the connection's buffer using the
lenientFlushBuffermethod. This ensures that the pull request message is sent immediately. - Finally, it returns the
pullSubject, which can be used to track the pull request.
Please note that some variable types and method calls are inferred and may not be explicitly mentioned in the provided code snippet.
The _pull method in the NatsJetStreamPullSubscription class is used to pull messages from a JetStream consumer.
Here is a breakdown of what the method does:
- It constructs the subject to publish the next pull request.
- It generates a unique pullSubject by replacing "*" with the incremented value of
pullSubjectIdHolder. - It starts a pull request with the JetStream manager using the pullSubject.
- It publishes the pullSubject and the serialized pull request options to the publishSubject.
- It flushes the connection buffer to ensure that the publish call is sent immediately.
- Finally, it returns the pullSubject that was used for the pull request.

private List<Message> _fetch(int batchSize, long maxWaitMillis) {
List<Message> messages = drainAlreadyBuffered(batchSize);
int batchLeft = batchSize - messages.size();
if (batchLeft == 0) {
return messages;
}
try {
long start = System.nanoTime();
Duration expires = Duration.ofMillis(maxWaitMillis > MIN_EXPIRE_MILLIS ? maxWaitMillis - EXPIRE_ADJUSTMENT : maxWaitMillis);
String pullSubject = _pull(PullRequestOptions.builder(batchLeft).expiresIn(expires).build(), false, null);
// timeout > 0 process as many messages we can in that time period
// If we get a message that either manager handles, we try again, but
// with a shorter timeout based on what we already used up
long maxWaitNanos = maxWaitMillis * 1_000_000;
long timeLeftNanos = maxWaitNanos;
while (batchLeft > 0 && timeLeftNanos > 0) {
Message msg = nextMessageInternal(Duration.ofNanos(timeLeftNanos));
if (msg == null) {
// normal timeout
return messages;
}
switch(manager.manage(msg)) {
case MESSAGE:
messages.add(msg);
batchLeft--;
break;
case STATUS_TERMINUS:
// if there is a match, the status applies otherwise it's ignored
if (pullSubject.equals(msg.getSubject())) {
return messages;
}
break;
case STATUS_ERROR:
// if there is a match, the status applies otherwise it's ignored
if (pullSubject.equals(msg.getSubject())) {
throw new JetStreamStatusException(msg.getStatus(), this);
}
break;
}
// anything else, try again while we have time
timeLeftNanos = maxWaitNanos - (System.nanoTime() - start);
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
return messages;
}The _fetch method is defined in the io.nats.client.impl.NatsJetStreamPullSubscription class and takes two parameters: batchSize (the number of messages to fetch) and maxWaitMillis (the maximum time to wait for messages).
-
The method first calls the
drainAlreadyBufferedmethod to retrieve any messages that have already been buffered. These messages are added to themessageslist. -
The method checks how many more messages are needed to reach the desired
batchSize. -
If no more messages are needed (i.e.,
batchLeft == 0), the method returns themessageslist. -
If more messages are needed, the method proceeds to set up the fetching process.
-
It calculates the expiry time for the fetch operation based on the
maxWaitMillisparameter. IfmaxWaitMillisis greater thanMIN_EXPIRE_MILLIS, it subtractsEXPIRE_ADJUSTMENT; otherwise, it usesmaxWaitMillisdirectly. -
It calls the
_pullmethod to initiate pulling messages from the server. The_pullmethod takes aPullRequestOptionsobject with thebatchLeftvalue and theexpiresInduration (set in the previous step). It also specifiesfalsefor thenoWaitandlastBatchparameters, andnullfor thestatusCodesparameter. -
It sets the maximum wait time in nanoseconds (
maxWaitNanos) based on themaxWaitMillisvalue. -
It enters a loop that continues until either
batchLeftbecomes 0 or thetimeLeftNanosbecomes 0. -
In each iteration, it calls the
nextMessageInternalmethod to fetch the next message. This method takes aDurationobject that represents the remaining time (timeLeftNanos) for waiting. If no message is received within this time, the method returns themessageslist (normal timeout case). -
If a message is received, the method checks the message with the
managerto determine its status. -
If the manager returns
MESSAGE, the message is added to themessageslist,batchLeftis decremented, and the loop continues for the next message. -
If the manager returns
STATUS_TERMINUS, it checks if the message subject matches thepullSubject. If it does, the method returns themessageslist. -
If the manager returns
STATUS_ERROR, it again checks if the message subject matches thepullSubject. If it does, it throws aJetStreamStatusExceptionwith the message status and the currentNatsJetStreamPullSubscriptioninstance. -
The method then calculates the remaining time (
timeLeftNanos) for the loop by subtracting the elapsed time from themaxWaitNanos. -
The loop continues until either
batchLeftbecomes 0 or thetimeLeftNanosbecomes 0. -
If the loop ends without returning, the method completes by returning the
messageslist. -
The method handles the
InterruptedExceptionby interrupting the current thread.
This method is responsible for fetching messages from the server based on the provided batchSize and maxWaitMillis parameters, using the drainAlreadyBuffered method and the _pull method, and processing each message with the manager. It returns a list of messages fetched within the specified limits.
The _fetch method is used to retrieve a batch of messages from a NATS JetStream pull subscription.
It takes in two parameters: batchSize - the maximum number of messages to fetch in one batch, and maxWaitMillis - the maximum time to wait for new messages to arrive.
The method first drains any messages that have already been buffered from previous fetch operations. It then calculates the number of messages still needed to reach the desired batch size.
If there are already enough buffered messages, the method returns the buffered messages.
Otherwise, it starts a loop to fetch new messages from the subscription. It tries to fetch as many messages as possible within the remaining time, based on the maxWaitMillis parameter. The loop continues until either the desired batch size is reached or the maximum wait time has been exceeded.
For each fetched message, it checks the result of the manager.manage(msg) method. If the result is MESSAGE, it adds the message to the list of fetched messages and decrements the count of messages still needed. If the result is STATUS_TERMINUS, it checks if the pull subject matches the message's subject. If it does, it returns the fetched messages. If the result is STATUS_ERROR, it checks if the pull subject matches the message's subject and throws a JetStreamStatusException if it does.
After processing each message, it calculates the remaining time and continues the loop if there is still time left.
If an InterruptedException is caught, the method interrupts the current thread and returns the already fetched messages.
Finally, it returns the list of fetched messages.
private List<Message> drainAlreadyBuffered(int batchSize) {
List<Message> messages = new ArrayList<>(batchSize);
try {
while (true) {
Message msg = nextMessageInternal(null);
if (msg == null) {
// no more message currently queued
return messages;
}
if (manager.manage(msg) == MessageManager.ManageResult.MESSAGE) {
messages.add(msg);
if (messages.size() == batchSize) {
return messages;
}
}
// since this is buffered, no non-message applies, try again
}
} catch (InterruptedException ignore) {
Thread.currentThread().interrupt();
}
return messages;
}The drainAlreadyBuffered method, defined in the class io.nats.client.impl.NatsJetStreamPullSubscription, is used to retrieve a batch of messages from the subscription's buffer.
Here is a step-by-step description of what the method does:
-
The method takes an integer parameter
batchSizeindicating the maximum number of messages to retrieve in the batch. -
It initializes an empty
ArrayListcalledmessageswith a capacity ofbatchSize. -
The method enters a
whileloop that runs indefinitely until areturnstatement is reached. -
Inside the loop, the
nextMessageInternalmethod is called withnullas a parameter to retrieve the next message from the subscription's internal buffer. -
If
nextMessageInternalreturnsnull, it means there are no more messages currently queued in the buffer. In this case, themessageslist (which may contain previously retrieved messages) is returned. -
If
nextMessageInternalreturns a non-nullMessageobject, it means a message has been retrieved from the buffer. -
The retrieved message is then passed to the
manager.managemethod, which performs some management operations on the message. Depending on the result of this operation, the message may or may not be added to themessageslist. -
If the
manager.managemethod returnsMessageManager.ManageResult.MESSAGE, indicating that the message should be processed, it is added to themessageslist using themessages.addmethod. -
After adding the message to the list, the method checks if the size of the
messageslist has reached thebatchSizelimit. If so, it means the desired number of messages for the batch has been reached, and themessageslist is returned. -
If the
messageslist has not reached thebatchSizelimit, the loop continues, and the next message is retrieved from the buffer. -
If an
InterruptedExceptionis caught during the execution of the loop, it means the thread executing the method has been interrupted. In this case, the interrupt flag is set again by callingThread.currentThread().interrupt(), and the method exits. -
Finally, the method returns the
messageslist, which may contain some messages retrieved from the buffer.
Note: The exact behavior and purpose of the nextMessageInternal method, as well as the manager object and its manage method, are not described in the provided code snippet. Further information about these components would be necessary to fully understand the functionality of the drainAlreadyBuffered method.
The drainAlreadyBuffered method in the NatsJetStreamPullSubscription class is responsible for retrieving a batch of already buffered messages from the subscription.
It takes in a parameter batchSize, which specifies the maximum number of messages to retrieve in a batch. The method creates an empty list called messages to store the retrieved messages.
The method then enters a while loop that continues until there are no more messages currently queued. Within the loop, it calls the nextMessageInternal method to fetch the next message. If there is no message available, the method returns the list of retrieved messages.
If a message is found, it is passed to the manager.manage method, which determines if the message should be added to the messages list based on some condition. If the condition is met, the message is added to the list.
The method checks if the messages list has reached the batchSize limit, and if so, it returns the list of retrieved messages.
If the loop is interrupted by an InterruptedException, the method interrupts the current thread and returns the messages list.
Finally, if there are no more messages and the loop exits, the method returns the messages list.
In summary, the drainAlreadyBuffered method iteratively retrieves messages from an already buffered queue until no more messages are available or until the desired batch size is reached. It returns the retrieved messages in a list.

private Iterator<Message> _iterate(final int batchSize, long maxWaitMillis) {
final List<Message> buffered = drainAlreadyBuffered(batchSize);
// if there was a full batch buffered, no need to pull, just iterate over the list you already have
int batchLeft = batchSize - buffered.size();
if (batchLeft == 0) {
return new Iterator<Message>() {
@Override
public boolean hasNext() {
return buffered.size() > 0;
}
@Override
public Message next() {
return buffered.remove(0);
}
};
}
// if there were some messages buffered, reduce the raw pull batch size
String pullSubject = _pull(PullRequestOptions.builder(batchLeft).expiresIn(maxWaitMillis).build(), false, null);
final long timeout = maxWaitMillis;
// the iterator is also more complicated
return new Iterator<Message>() {
int received = 0;
boolean done = false;
Message msg = null;
@Override
public boolean hasNext() {
try {
if (msg != null) {
return true;
}
if (done) {
return false;
}
if (buffered.size() == 0) {
msg = _nextUnmanaged(timeout, pullSubject);
if (msg == null) {
done = true;
return false;
}
} else {
msg = buffered.remove(0);
}
done = ++received == batchSize;
return true;
} catch (InterruptedException e) {
msg = null;
done = true;
Thread.currentThread().interrupt();
return false;
}
}
@Override
public Message next() {
Message next = msg;
msg = null;
return next;
}
};
}The _iterate method in the NatsJetStreamPullSubscription class is a private method that returns an iterator over a batch of messages.
-
The method starts by draining any already buffered messages from a list called
bufferedusing thedrainAlreadyBufferedmethod. ThebatchSizeparameter determines the number of messages to drain. -
If the number of messages drained is equal to the
batchSize, meaning a full batch was already buffered, the method returns a new iterator that iterates over thebufferedlist. -
If some messages were drained from the buffer, the method reduces the raw pull batch size by the number of messages remaining to reach the
batchSize. It does this by calling the_pullmethod with the reduced batch size,falsefor theexpectsSingleparameter, andnullfor thepullSubjectparameter. -
The method then creates a new iterator. This iterator keeps track of the number of received messages (
received), whether the iteration is done (done), and the current message being iterated (msg). -
The
hasNextmethod of the iterator is implemented as follows:- If
msgis notnull, indicating that there is a message already fetched, the method returnstrue. - If
doneistrue, indicating that the iteration is completed, the method returnsfalse. - If the
bufferedlist is empty, meaning there are no more buffered messages, the method calls the_nextUnmanagedmethod with thetimeoutandpullSubjectparameters to fetch the next message from the server. If the returned message isnull, indicating that there are no more messages, the method setsdonetotrueand returnsfalse. - If the
bufferedlist is not empty, the method removes the first message from the list and assigns it to themsgvariable. - The method then increments the
receivedcount and checks if it is equal to thebatchSize. If it is, the method setsdonetotrue.
- If
-
The
nextmethod of the iterator simply returns the current message (msg) and resets it tonullto prepare for the next iteration.
The iterator returned by the _iterate method allows iterating over a batch of messages, fetching from the buffer or pulling from the server as needed, until the batch size is reached or there are no more messages.
The _iterate method is a private method defined in the NatsJetStreamPullSubscription class. This method is used to iterate over a batch of messages from a pull subscription in the NATS JetStream client library.
Here's a breakdown of what the method does:
- It first drains any messages that are already buffered up to the specified batch size.
- If there are a full batch of messages already buffered, it returns an iterator that iterates over the existing list of messages.
- If there are some messages already buffered, it adjusts the pull batch size accordingly.
- It calls the
_pullmethod to initiate a pull request for the remaining messages, with a reduced batch size and a maximum wait time. - It then returns an iterator that fetches messages from the pulled messages or the buffered messages until the batch size is reached.
- The
hasNextmethod is used to check if there is a next message available.- If a message is already fetched from the pull or buffered messages, it returns true.
- If the iterator is done iterating, it returns false.
- If there are no buffered messages, it calls the
_nextUnmanagedmethod to fetch the next message from the pull subscription, with a timeout value. - If there are buffered messages, it removes the first message from the buffer and assigns it to the
msgvariable. - It tracks the number of received messages and checks if the batch size has been reached to set the
doneflag.
- The
nextmethod is used to retrieve the next message from the iterator. It returns themsgvariable and resets it to null.
Overall, this method is responsible for iterating over a batch of messages from a pull subscription, pulling new messages if necessary, and returning them through an iterator.

NatsJetStreamSubscription
The NatsJetStreamSubscription class is a JetStream specific subscription that extends the NatsSubscription class and implements the JetStreamSubscription and NatsJetStreamConstants interfaces. It provides functionality for subscribing to messages using the JetStream protocol.
public Message nextMessage(Duration timeout) throws InterruptedException, IllegalStateException
@Override
public Message nextMessage(Duration timeout) throws InterruptedException, IllegalStateException {
if (timeout == null) {
return _nextUnmanagedNoWait(null);
}
long millis = timeout.toMillis();
if (millis <= 0) {
return _nextUnmanagedWaitForever(null);
}
return _nextUnmanaged(millis, null);
}This method is used to retrieve the next message from the NatsJetStreamSubscription.
-
timeout: A Duration object representing the maximum amount of time to wait for a message. If null, the method will attempt to retrieve a message without waiting.
- Returns the next message that is received from the NatsJetStreamSubscription.
-
Check if the
timeoutparameter is null.- If it is null, proceed to step 2.
- Otherwise, continue to step 3.
-
Call the method
_nextUnmanagedNoWait(null)to retrieve the next message without waiting.- Return the message.
-
Convert the
timeoutinto milliseconds by callingtimeout.toMillis().- Store the result in the variable
millis.
- Store the result in the variable
-
Check if the value of
millisis less than or equal to 0.- If it is less than or equal to 0, proceed to step 5.
- Otherwise, continue to step 6.
-
Call the method
_nextUnmanagedWaitForever(null)to retrieve the next message, waiting indefinitely.- Return the message.
-
Call the method
_nextUnmanaged(millis, null)to retrieve the next message, waiting for the specified duration.- Return the message.
The nextMessage method in the io.nats.client.impl.NatsJetStreamSubscription class is responsible for retrieving the next message from the subscription, with an optional timeout parameter.
If the timeout parameter is null, the method calls the _nextUnmanagedNoWait method to immediately return the next available message, if any.
If the timeout parameter is not null, the method converts it to milliseconds and checks if it is less than or equal to 0. If so, it calls the _nextUnmanagedWaitForever method to wait indefinitely for the next message.
Otherwise, it calls the _nextUnmanaged method with the timeout in milliseconds to wait for the next message. The retrieved Message object is then returned.
The method can throw InterruptedException if the thread is interrupted while waiting for the next message, and IllegalStateException if the subscription is in an invalid state.

public Message nextMessage(long timeoutMillis) throws InterruptedException, IllegalStateException
@Override
public Message nextMessage(long timeoutMillis) throws InterruptedException, IllegalStateException {
if (timeoutMillis <= 0) {
return _nextUnmanagedWaitForever(null);
}
return _nextUnmanaged(timeoutMillis, null);
}The nextMessage method in the NatsJetStreamSubscription class is responsible for retrieving the next message from the subscription queue.
The method takes a timeoutMillis parameter, which represents the maximum amount of time in milliseconds to wait for a message to become available.
If the timeoutMillis value is less than or equal to 0, the method calls the _nextUnmanagedWaitForever method to wait indefinitely until a message is received. Otherwise, it calls the _nextUnmanaged method with the given timeout to wait for a message until the timeout expires.
The method returns the next available message or null if no message is received within the specified timeout.

protected Message _nextUnmanagedWaitForever(String expectedPullSubject) throws InterruptedException {
while (true) {
Message msg = nextMessageInternal(Duration.ZERO);
if (msg != null) {
// null shouldn't happen, so just a code guard b/c nextMessageInternal can return null
switch(manager.manage(msg)) {
case MESSAGE:
return msg;
case STATUS_ERROR:
// if the status applies throw exception, otherwise it's ignored, fall through
if (expectedPullSubject == null || expectedPullSubject.equals(msg.getSubject())) {
throw new JetStreamStatusException(msg.getStatus(), this);
}
break;
}
// Check again since waiting forever when:
// 1. Any STATUS_HANDLED or STATUS_TERMINUS
// 2. STATUS_ERRORS that aren't for expected pullSubject
}
}
}The _nextUnmanagedWaitForever method defined in class io.nats.client.impl.NatsJetStreamSubscription is a method that waits indefinitely for the next message from the NATS server.
Here is a step-by-step description of what this method does based on its body:
- The method enters an infinite loop using
while (true). - Within the loop, it calls the
nextMessageInternalmethod with a duration of zero, indicating that it should wait for the next message immediately. - If a message is returned by
nextMessageInternal(i.e.,msgis not null), the method proceeds to the next step. - It checks the status of the message by calling
manager.manage(msg). Themanagerobject is responsible for handling the message and returning a status. - If the status returned by
manager.manage(msg)isMESSAGE, it means that the message is a regular message, and the method returns the message byreturn msg. - If the status is
STATUS_ERROR, the method checks whether the expected pull subject is null or equal to the subject of the message. If it is, it means that the message is an error related to the expected pull subject, and the method throws aJetStreamStatusExceptionwith the message's status and a reference to the subscription. - If the status is neither
MESSAGEnorSTATUS_ERROR, the method continues to the next iteration of the loop and waits for the next message. - The method repeats the above steps, checking for a new message in each iteration of the loop.
This method is useful when you want to wait indefinitely for the next message from the NATS server without specifying a timeout duration. It is often used in scenarios where a continuous stream of messages needs to be processed without interruption.
The _nextUnmanagedWaitForever method in the NatsJetStreamSubscription class is responsible for retrieving the next message from the subscription and handling it according to certain conditions.
The method uses an infinite loop to continuously retrieve the next message using the nextMessageInternal method with a duration of zero (indicating no timeout). It then checks the retrieved message against several conditions.
If the message is not null, it proceeds to check the status of the message using the manage method of the subscription's manager. Based on the returned status, the method performs different actions:
- If the status is
MESSAGE, the method returns the message. - If the status is
STATUS_ERROR, the method checks if theexpectedPullSubject(passed as a parameter) isnullor if it matches the subject of the message. If it does, the method throws aJetStreamStatusExceptionwith the message's status and a reference to the subscription. - If none of the conditions above apply, the method continues to the next iteration of the loop to check for more messages.
The purpose of this method is to continuously retrieve messages from the subscription and handle them appropriately based on their status and the expected pull subject, if provided.

protected Message _nextUnmanagedNoWait(String expectedPullSubject) throws InterruptedException {
while (true) {
Message msg = nextMessageInternal(null);
if (msg == null) {
return null;
}
switch(manager.manage(msg)) {
case MESSAGE:
return msg;
case STATUS_TERMINUS:
// if the status applies return null, otherwise it's ignored, fall through
if (expectedPullSubject == null || expectedPullSubject.equals(msg.getSubject())) {
return null;
}
break;
case STATUS_ERROR:
// if the status applies throw exception, otherwise it's ignored, fall through
if (expectedPullSubject == null || expectedPullSubject.equals(msg.getSubject())) {
throw new JetStreamStatusException(msg.getStatus(), this);
}
break;
}
// Check again when, regular messages might have arrived
// 1. Any STATUS_HANDLED
// 2. STATUS_TERMINUS or STATUS_ERRORS that aren't for expected pullSubject
}
}The _nextUnmanagedNoWait method is defined in the NatsJetStreamSubscription class and it takes an expectedPullSubject parameter. Here is a step-by-step description of what this method does:
- The method enters an infinite loop using the
while (true)statement. - Inside the loop, it calls the
nextMessageInternalmethod to retrieve the next message from the subscription. If there are no more messages, it returnsnull. - After getting a message, the method calls the
managemethod of the subscription's manager object (manager.manage(msg)) and performs different actions based on the returned value. - If the returned value is
MESSAGE, it means that the message is a regular message, and the method returns the message. - If the returned value is
STATUS_TERMINUS, it means that the message contains a termination status. The method checks ifexpectedPullSubjectisnullor if it matches the subject of the message. If it does, the method returnsnullto indicate that the status applies. If it doesn't match, it continues to the next iteration of the loop. - If the returned value is
STATUS_ERROR, it means that the message contains an error status. The method follows a similar logic as in step 5, but instead of returningnull, it throws aJetStreamStatusExceptionwith the status and the current subscription as parameters. - After handling the message, the method checks again for any new messages that might have arrived in the meantime.
- If there is any message with
STATUS_HANDLED, it means that another thread has already handled the message and it continues to the next iteration of the loop. - If there is any message with
STATUS_TERMINUSorSTATUS_ERRORthat doesn't match theexpectedPullSubject, it means that another message has arrived that is not relevant to the current pull operation, and it continues to the next iteration of the loop.
- If there is any message with
The method keeps looping until a regular message is returned or an error occurs.
The method _nextUnmanagedNoWait is a protected method defined in the NatsJetStreamSubscription class of the io.nats.client.impl package.
This method is responsible for retrieving the next message from the subscription, without waiting for new messages to arrive. It takes the expectedPullSubject as a parameter, which is the subject that the client is expecting to pull messages from.
Inside the method, there is an infinite while loop that continuously checks for the next message using the nextMessageInternal method. If there is no message available, the method returns null.
If a message is received, it is passed to the manage method of the manager object. The manage method is expected to return one of the following enum values: MESSAGE, STATUS_TERMINUS, or STATUS_ERROR.
Based on the returned value, the method takes appropriate actions. If the message indicates a regular message (enum value MESSAGE), the message is returned. If the message indicates a termination status for the subscription (enum value STATUS_TERMINUS), the method checks if this termination status is applicable for the expectedPullSubject. If it is, the method returns null; otherwise, it continues to the next iteration of the loop. If the message indicates an error status (enum value STATUS_ERROR), the method checks if this error status is applicable for the expectedPullSubject. If it is, a JetStreamStatusException is thrown; otherwise, it continues to the next iteration of the loop.
After processing the message, the loop checks again for any regular messages that might have arrived. It specifically looks for:
- Any
STATUS_HANDLEDmessages. -
STATUS_TERMINUSorSTATUS_ERRORSmessages that are not for theexpectedPullSubject.
This method is used for retrieving messages from a JetStream subscription in a non-blocking manner and handling different statuses appropriately.

protected Message _nextUnmanaged(long timeout, String expectedPullSubject) throws InterruptedException
protected Message _nextUnmanaged(long timeout, String expectedPullSubject) throws InterruptedException {
long timeoutNanos = timeout * 1_000_000;
long timeLeftNanos = timeoutNanos;
long start = System.nanoTime();
while (timeLeftNanos > 0) {
Message msg = nextMessageInternal(Duration.ofNanos(timeLeftNanos));
if (msg == null) {
// normal timeout
return null;
}
switch(manager.manage(msg)) {
case MESSAGE:
return msg;
case STATUS_TERMINUS:
// if the status applies return null, otherwise it's ignored, fall through
if (expectedPullSubject == null || expectedPullSubject.equals(msg.getSubject())) {
return null;
}
break;
case STATUS_ERROR:
// if the status applies throw exception, otherwise it's ignored, fall through
if (expectedPullSubject == null || expectedPullSubject.equals(msg.getSubject())) {
throw new JetStreamStatusException(msg.getStatus(), this);
}
break;
}
// anything else, try again while we have time
timeLeftNanos = timeoutNanos - (System.nanoTime() - start);
}
return null;
}The _nextUnmanaged method is defined in the io.nats.client.impl.NatsJetStreamSubscription class. It is a protected method that returns a Message object. The method takes two parameters: timeout, which is a long representing the timeout period in milliseconds, and expectedPullSubject, which is a String representing the expected pull subject.
Here is a step-by-step description of what the method does:
- Convert the
timeoutin milliseconds to nanoseconds by multiplying it by1_000_000. - Initialize the
timeLeftNanosvariable to the value oftimeoutNanos. - Get the current time in nanoseconds and store it in the
startvariable. - Enter a
whileloop that runs as long astimeLeftNanosis greater than 0. - Call the
nextMessageInternalmethod, passing in aDurationobject created fromtimeLeftNanos. - If the returned message is
null, it means the timeout period has elapsed, so returnnull. - Switch on the result of calling the
managemethod of themanagerobject, passing in the received message. - If the result is
MESSAGE, return the message. - If the result is
STATUS_TERMINUS, check if theexpectedPullSubjectisnullor if it is equal to the subject of the received message. If either condition is true, returnnull, otherwise continue to the next case. - If the result is
STATUS_ERROR, check if theexpectedPullSubjectisnullor if it is equal to the subject of the received message. If either condition is true, throw aJetStreamStatusExceptionwith the status from the received message and the current subscription. - If none of the above cases apply, continue to the next iteration of the loop.
- Calculate the remaining time by subtracting the elapsed time since the start from
timeoutNanos, and store it intimeLeftNanos. - Return
nullif the timeout period has elapsed.
That's the step-by-step description of the _nextUnmanaged method.
The _nextUnmanaged method is used in the NatsJetStreamSubscription class to retrieve the next message from the subscription.
This method takes in a timeout value (in milliseconds) and an expected pull subject. It then calculates the timeout in nanoseconds and iteratively checks for the next message until either a message is received or the timeout expires.
Within the loop, the method calls the nextMessageInternal method to get the next message. If no message is received within the timeout, null is returned.
If a message is received, the method uses the manager to manage the message. Depending on the result, the method either returns the message (MESSAGE result), returns null if the message's subject matches the expected pull subject (STATUS_TERMINUS result), or throws a JetStreamStatusException if the message's subject matches the expected pull subject and the result is STATUS_ERROR.
If none of the above conditions are met, the method continues to try again until there is no time left, and then returns null.
Overall, the _nextUnmanaged method is responsible for fetching the next message from the subscription, managing the message, and handling various scenarios based on the result.

NatsDispatcher
The NatsDispatcher class is a subclass of NatsConsumer and implements the Dispatcher and Runnable interfaces. It provides functionality for dispatching events and running background tasks.
public void run() {
try {
while (this.running.get()) {
// start
NatsMessage msg = this.incoming.pop(this.waitForMessage);
if (msg != null) {
NatsSubscription sub = msg.getNatsSubscription();
if (sub != null && sub.isActive()) {
MessageHandler handler = subscriptionHandlers.get(sub.getSID());
if (handler == null) {
handler = defaultHandler;
}
// A dispatcher can have a null defaultHandler. You can't subscribe without a handler,
// but messages might come in while the dispatcher is being closed or after unsubscribe
// and the [non-default] handler has already been removed from subscriptionHandlers
if (handler != null) {
sub.incrementDeliveredCount();
this.incrementDeliveredCount();
try {
handler.onMessage(msg);
} catch (Exception exp) {
connection.processException(exp);
}
if (sub.reachedUnsubLimit()) {
connection.invalidate(sub);
}
}
}
}
if (breakRunLoop()) {
return;
}
}
} catch (InterruptedException exp) {
if (this.running.get()) {
this.connection.processException(exp);
}
//otherwise we did it
} finally {
this.running.set(false);
this.thread = null;
}
}The run() method in class io.nats.client.impl.NatsDispatcher is responsible for running the message dispatching loop. Here is a step-by-step description of what this method does:
- The method is wrapped in a try-catch block.
- There is a while loop that continues running as long as the
runningflag is set totrue. This flag is aAtomicBooleanthat can be toggled by calling thestop()method on the dispatcher. - Inside the loop, the method pops a message from the
incomingqueue, using thepop()method. ThewaitForMessageparameter determines the maximum time to wait for a message to be available. - If a message is retrieved (
msg != null), the method checks if the message's associated subscription is active (sub.isActive()). - If the subscription is active, the method retrieves the message handler for the subscription from a map called
subscriptionHandlers. If there is no specific handler found for the subscription, it uses thedefaultHandler. - If a handler is found (either specific or default), the method increments the delivery count for both the subscription and the dispatcher.
- The method then invokes the
onMessage()method of the handler, passing the message as an argument. If an exception is thrown during the invocation, the exception is processed by the connection to which the dispatcher belongs. - After handling the message, the method checks if the subscription has reached its unsubscribe limit (
sub.reachedUnsubLimit()). If so, it invalidates the subscription by callingconnection.invalidate(sub). - After processing the message, the method checks if the
breakRunLoop()method returnstrue. This method can be overridden by subclasses to provide custom logic for breaking out of the loop. Iftrueis returned, the method returns, ending the loop. - If no message is retrieved from the queue or the subscription is not active, or the subscription handler is missing, the method continues to the next iteration of the loop.
- If an
InterruptedExceptionis thrown while waiting for a message, the method checks if therunningflag is still set totrue. If it is, the exception is processed by the connection. - Finally, the
runningflag is set tofalse, indicating that the dispatcher has stopped running. Thethreadfield is set tonull, indicating that the underlying thread has terminated.
This method essentially fetches messages from the incoming queue, checks if the associated subscriptions are active, invokes the appropriate message handler, and takes appropriate actions based on the subscription's state. It continues to run until explicitly stopped or interrupted.
The run method in the NatsDispatcher class is responsible for continuously processing incoming messages from the NATS server. It runs in a loop until a specific condition is met.
Within the loop, it pops an incoming message from the message queue and checks if it is not null. If the message exists, it retrieves the corresponding subscription and checks if it is active. If the subscription is active, it retrieves the message handler associated with the subscription from the subscriptionHandlers map. If no handler is found, it falls back to the default handler.
The method then increments the delivered count for both the subscription and the dispatcher, and proceeds to invoke the onMessage method of the handler, passing the message as a parameter. If any exception occurs during the execution of the handler, it is caught and processed by the connection object.
After handling the message, the method checks if the subscription has reached its unsubscribe limit. If so, it invalidates the subscription on the connection.
The method also checks for a condition to break the run loop, and if it is met, the method returns.
If any interrupted exception is thrown during the execution of the method, it is caught and processed by the connection. Finally, the method sets the running flag to false and clears the thread variable.

void stop(boolean unsubscribeAll) {
this.running.set(false);
this.incoming.pause();
if (this.thread != null) {
try {
if (!this.thread.isCancelled()) {
this.thread.cancel(true);
}
} catch (Exception exp) {
// let it go
}
}
if (unsubscribeAll) {
this.subscriptionsUsingDefaultHandler.forEach((subj, sub) -> {
this.connection.unsubscribe(sub, -1);
});
this.subscriptionsWithHandlers.forEach((sid, sub) -> {
this.connection.unsubscribe(sub, -1);
});
}
this.subscriptionsUsingDefaultHandler.clear();
this.subscriptionsWithHandlers.clear();
this.subscriptionHandlers.clear();
}The stop method of the NatsDispatcher class is used to stop the dispatcher and perform cleanup tasks. It takes a boolean parameter unsubscribeAll that indicates whether to unsubscribe from all currently subscribed subjects or not.
Here is a step-by-step description of the method:
-
Set
runningflag tofalseusing thesetmethod of theAtomicBooleanvariablerunning. This flag is used to control the main loop of the dispatcher. -
Pause the incoming message processing by calling the
pausemethod of theIncomingobjectthis.incoming. This method will stop the processing of incoming messages until it is resumed. -
Check if the
threadobject is not null (indicating that the dispatcher is running on a separate thread). -
Inside the conditional block, check if the
threadis not cancelled by calling theisCancelledmethod of theFutureobjectthis.thread. This method returnstrueif the task running on the thread has been cancelled. -
If the
threadis not cancelled, cancel it by calling thecancelmethod of theFutureobjectthis.thread. Thetrueparameter indicates that the task should be interrupted if it is currently running. -
Handle any exceptions that may occur during the cancellation process by catching
Exceptionobjects and ignoring them. The comment "// let it go" indicates that any exceptions should be ignored. -
If the
unsubscribeAllparameter istrue, perform unsubscribe operations for all subscriptions using default handlers and for subscriptions with specific handlers. -
Iterate over each entry in the
subscriptionsUsingDefaultHandlermap using a lambda expression. For each entry, call theunsubscribemethod of theConnectionobjectthis.connectionto unsubscribe from the subject specified by the subscription. The-1indicates that the subscription should be immediately unsubscribed. -
Iterate over each entry in the
subscriptionsWithHandlersmap using a lambda expression. For each entry, call theunsubscribemethod of theConnectionobjectthis.connectionto unsubscribe from the subject specified by the subscription. The-1indicates that the subscription should be immediately unsubscribed. -
Clear the
subscriptionsUsingDefaultHandler,subscriptionsWithHandlers, andsubscriptionHandlersmaps to remove all stored subscription data.
The stop method is used to gracefully stop the dispatcher and clean up any resources associated with it.
The stop method in the NatsDispatcher class is responsible for stopping the dispatcher processing and clearing all subscriptions.
Here is a brief description of what the method does:
- It sets the
runningflag tofalse, indicating that the dispatcher is no longer running. - It pauses the
incomingchannel, preventing any further message processing. - It checks if there is a running thread associated with the dispatcher, and if so, tries to cancel it.
- If the
unsubscribeAllparameter istrue, it unsubscribes from all subscriptions. - It clears the collections that store the subscriptions and their associated handlers.

void resendSubscriptions() {
this.subscriptionsUsingDefaultHandler.forEach((id, sub) -> {
this.connection.sendSubscriptionMessage(sub.getSID(), sub.getSubject(), sub.getQueueName(), true);
});
this.subscriptionsWithHandlers.forEach((sid, sub) -> {
this.connection.sendSubscriptionMessage(sub.getSID(), sub.getSubject(), sub.getQueueName(), true);
});
}The resendSubscriptions method in the NatsDispatcher class is used to resend all the subscriptions stored in two different maps, namely subscriptionsUsingDefaultHandler and subscriptionsWithHandlers.
Here is a step-by-step description of what the method does:
- Iterate over each entry in the
subscriptionsUsingDefaultHandlermap. - For each entry, retrieve the subscription ID and the corresponding subscription object.
- Use the
connectionobject to send a subscription message using the following parameters:
- Subscription ID: Retrieved from the subscription object using
sub.getSID() - Subject: Retrieved from the subscription object using
sub.getSubject() - Queue Name: Retrieved from the subscription object using
sub.getQueueName() - Resend Flag: Set to
true
- Repeat steps 2-3 for all entries in the
subscriptionsUsingDefaultHandlermap. - Iterate over each entry in the
subscriptionsWithHandlersmap. - For each entry, retrieve the subscription ID and the corresponding subscription object.
- Use the
connectionobject to send a subscription message using the same parameters as in step 3. - Repeat steps 6-7 for all entries in the
subscriptionsWithHandlersmap.
Overall, the resendSubscriptions method is responsible for resending all the subscriptions in the NatsDispatcher class by iterating over the two maps and using the connection object to send subscription messages with the subscription details.
The resendSubscriptions method in the NatsDispatcher class is responsible for resending subscriptions to the NATS server.
The method iterates over two sets of subscriptions: subscriptionsUsingDefaultHandler and subscriptionsWithHandlers. For each subscription, it retrieves the subscription ID, subject, and queue name using the getSID(), getSubject(), and getQueueName() methods, respectively. It then calls the sendSubscriptionMessage() method on the connection object, passing in the subscription ID, subject, queue name, and a boolean value of true to indicate that it should be resent.
The purpose of this method is to ensure that all subscriptions are reestablished with the NATS server, in case any were lost or not initially sent properly.

// We will first attempt to remove from subscriptionsWithHandlers // using the sub's SID, and if we don't find it there, we'll check // the subscriptionsUsingDefaultHandler Map and verify the SID // matches before removing. By verifying the SID in all cases we can // be certain we're removing the correct Subscription. void remove(NatsSubscription sub)
// Called by the connection when a subscription is removed.
// We will first attempt to remove from subscriptionsWithHandlers
// using the sub's SID, and if we don't find it there, we'll check
// the subscriptionsUsingDefaultHandler Map and verify the SID
// matches before removing. By verifying the SID in all cases we can
// be certain we're removing the correct Subscription.
void remove(NatsSubscription sub) {
if (this.subscriptionsWithHandlers.remove(sub.getSID()) != null) {
this.subscriptionHandlers.remove(sub.getSID());
} else {
NatsSubscription s = this.subscriptionsUsingDefaultHandler.get(sub.getSubject());
if (s.getSID().equals(sub.getSID())) {
this.subscriptionsUsingDefaultHandler.remove(sub.getSubject());
}
}
}The remove method in the NatsDispatcher class is used to remove a subscription from the dispatcher. Here is a step-by-step description of what this method is doing:
- Check if the given subscription
subexists in thesubscriptionsWithHandlersmap using its subscription ID (SID). - If the subscription is found in the
subscriptionsWithHandlersmap, remove it by calling theremovemethod on the map and passing the subscription ID. - If the removal is successful (i.e., the subscription ID is found in the map), also remove the subscription's handler from the
subscriptionHandlersmap using the subscription ID as the key. - If the subscription is not found in the
subscriptionsWithHandlersmap, retrieve the subscription with the same subject from thesubscriptionsUsingDefaultHandlermap. - Check if the retrieved subscription's subscription ID matches the ID of the given subscription (
sub). - If the subscription IDs match, remove the retrieved subscription from the
subscriptionsUsingDefaultHandlermap by calling theremovemethod on the map and passing the subscription's subject as the key.
By performing these steps, the remove method ensures that the correct subscription is removed from the dispatcher, either by searching for its subscription ID in the subscriptionsWithHandlers map or by matching its ID with the retrieved subscription from the subscriptionsUsingDefaultHandler map.
The remove method in the NatsDispatcher class is used to remove a subscription from the dispatcher. This method is called when a subscription is being removed from the connection.
First, it tries to remove the subscription from the subscriptionsWithHandlers map using the subscription's SID (unique identifier). If the subscription is found in this map, it is removed along with its handler from the subscriptionHandlers map.
If the subscription is not found in the subscriptionsWithHandlers map, the method checks the subscriptionsUsingDefaultHandler map to find a subscription with the same subject and verifies that the SID matches. If the SID matches, the subscription is removed from the subscriptionsUsingDefaultHandler map.
By verifying the SID in both cases, the method ensures that it is removing the correct subscription.

public Dispatcher subscribe(String subject) {
if (subject == null || subject.length() == 0) {
throw new IllegalArgumentException("Subject is required in subscribe");
}
this.subscribeImplCore(subject, null, null);
return this;
}The subscribe method in the NatsDispatcher class is used to subscribe to a specific subject in a NATS system.
Here is a step-by-step description of what the method is doing based on its body:
- Check if the
subjectparameter is null or an empty string. - If
subjectis null or an empty string, throw anIllegalArgumentExceptionwith a message stating that the subject is required for subscribing. - If the
subjectis valid (not null or empty), call thesubscribeImplCoremethod with thesubject,null, andnullparameters. - The
subscribeImplCoremethod is responsible for the actual implementation of subscribing to the subject. - Return a reference to the current instance of the
NatsDispatcherclass (i.e.,this).
In summary, the subscribe method ensures that a valid subject is provided, calls the subscribeImplCore method to perform the subscription, and returns a reference to the NatsDispatcher object.
The subscribe method is defined in the class NatsDispatcher and is used to subscribe to a specific subject in the NATS messaging system.
The method takes a subject as a parameter, which represents the name of the subject to subscribe to. If the subject is null or an empty string, an IllegalArgumentException is thrown, indicating that a valid subject is required for the subscription.
Internally, the method calls the subscribeImplCore method, passing the subject along with null values for message handlers and options.
Finally, the method returns a reference to the current NatsDispatcher instance, allowing for method chaining if desired.

NatsSubscription subscribeReturningSubscription(String subject) {
if (subject == null || subject.length() == 0) {
throw new IllegalArgumentException("Subject is required in subscribe");
}
return this.subscribeImplCore(subject, null, null);
}The subscribeReturningSubscription method in the NatsDispatcher class is responsible for subscribing to a NATS subject and returning a NatsSubscription object.
Here is a step-by-step description of what this method does:
- It takes a
subjectas input parameter. - It first checks if the
subjectis null or empty. If it is, it throws anIllegalArgumentExceptionwith a message stating that the subject is required for subscription. - If the subject is not null or empty, it calls the
subscribeImplCoremethod passing thesubject, as well asnullfor thequeueandnullfor thedurable. - The
subscribeImplCoremethod is responsible for actually subscribing to the subject and returning aNatsSubscriptionobject. - Finally, the
subscribeReturningSubscriptionmethod returns theNatsSubscriptionobject obtained from thesubscribeImplCoremethod.
That's the step-by-step description of the subscribeReturningSubscription method and what it does based on its implementation.
The subscribeReturningSubscription method is a functionality defined in the NatsDispatcher class that allows the software engineer to subscribe to a specific NATS subject and return a NatsSubscription object.
The method takes in a subject parameter, which is the name of the NATS subject to subscribe to. If the subject is null or has a length of 0, the method throws an IllegalArgumentException with the message "Subject is required in subscribe".
The method then calls the subscribeImplCore method with the subject parameter and null values for the queue and callback parameters. Finally, it returns the result of the subscribeImplCore method, which is a NatsSubscription object.

public Subscription subscribe(String subject, MessageHandler handler) {
if (subject == null || subject.length() == 0) {
throw new IllegalArgumentException("Subject is required in subscribe");
}
if (handler == null) {
throw new IllegalArgumentException("MessageHandler is required in subscribe");
}
return this.subscribeImplCore(subject, null, handler);
}The subscribe method in the NatsDispatcher class is used to subscribe to a specified subject and register a MessageHandler to handle incoming messages on that subject. Here is a step-by-step description of what this method is doing:
- Check if the
subjectparameter is null or empty. - If the subject is null or empty, throw an
IllegalArgumentExceptionwith the message "Subject is required in subscribe". - Check if the
handlerparameter is null. - If the handler is null, throw an
IllegalArgumentExceptionwith the message "MessageHandler is required in subscribe". - If both the subject and handler are valid, call the
subscribeImplCoremethod with the subject, null, and the handler as parameters. - Return the result of the
subscribeImplCoremethod, which is aSubscriptionobject.
Note: The subscribeImplCore method is not included in the provided code snippet, so it's unclear what specific actions it performs.
The subscribe method in the io.nats.client.impl.NatsDispatcher class is used to subscribe to a specific subject for receiving messages from NATS server.
The method takes two parameters: subject (the subject to subscribe to) and handler (the message handler to process incoming messages).
If the subject parameter is null or empty, an IllegalArgumentException is thrown. If the handler parameter is null, an IllegalArgumentException is also thrown.
The method then calls the subscribeImplCore method, passing the subject, null (indicating no specific queue group), and the handler. This method is responsible for the actual subscription and returns a Subscription object that represents the subscription.
In summary, the subscribe method is used to subscribe to a subject and provide a message handler to process incoming messages.

public Dispatcher subscribe(String subject, String queueName) {
if (subject == null || subject.length() == 0) {
throw new IllegalArgumentException("Subject is required in subscribe");
}
if (queueName == null || queueName.length() == 0) {
throw new IllegalArgumentException("QueueName is required in subscribe");
}
this.subscribeImplCore(subject, queueName, null);
return this;
}The subscribe method in the NatsDispatcher class allows you to subscribe to a specific subject using a queue group name. Below is a step-by-step description of what this method does:
-
Checks if the
subjectparameter is null or empty.- If it is null or empty, an
IllegalArgumentExceptionis thrown with the message "Subject is required in subscribe".
- If it is null or empty, an
-
Checks if the
queueNameparameter is null or empty.- If it is null or empty, an
IllegalArgumentExceptionis thrown with the message "QueueName is required in subscribe".
- If it is null or empty, an
-
If both
subjectandqueueNameare valid, the method calls thesubscribeImplCoremethod with the provided subject, queue group name, and a null subscription options argument.- The
subscribeImplCoremethod is responsible for the actual implementation of the subscribe logic.
- The
-
Finally, the
subscribemethod returns the current instance of theNatsDispatcherobject, allowing for method chaining.
Overall, the subscribe method ensures that valid parameters are provided, throws exceptions if they are missing, and then passes the parameters to the subscribeImplCore method to handle the actual subscription logic.
The subscribe method in the NatsDispatcher class is used to subscribe to a specific subject and receive messages from it.
It takes two parameters: subject and queueName.
If the subject or queueName is null or an empty string, an IllegalArgumentException is thrown with a corresponding error message.
Otherwise, it calls the subscribeImplCore method passing the subject, queueName, and null for the option parameter.
Finally, it returns the instance of the NatsDispatcher class itself, allowing for method chaining.

public Subscription subscribe(String subject, String queueName, MessageHandler handler) {
if (subject == null || subject.length() == 0) {
throw new IllegalArgumentException("Subject is required in subscribe");
}
if (queueName == null || queueName.length() == 0) {
throw new IllegalArgumentException("QueueName is required in subscribe");
}
if (handler == null) {
throw new IllegalArgumentException("MessageHandler is required in subscribe");
}
return this.subscribeImplCore(subject, queueName, handler);
}The subscribe method in the NatsDispatcher class is responsible for subscribing to a specific subject with a given queue name and message handler.
Here is a step-by-step description of what this method does:
-
Check if the
subjectparameter is null or empty:- If true, throw an
IllegalArgumentExceptionwith the message "Subject is required in subscribe".
- If true, throw an
-
Check if the
queueNameparameter is null or empty:- If true, throw an
IllegalArgumentExceptionwith the message "QueueName is required in subscribe".
- If true, throw an
-
Check if the
handlerparameter is null:- If true, throw an
IllegalArgumentExceptionwith the message "MessageHandler is required in subscribe".
- If true, throw an
-
Call the
subscribeImplCoremethod with thesubject,queueName, andhandlerparameters and return the result:- This is a private method that handles the actual subscription logic.
- It is responsible for subscribing to the specified subject with the given queue name and message handler.
In summary, the subscribe method performs some input validation and then delegates the actual subscription logic to the private subscribeImplCore method.
The subscribe method in the NatsDispatcher class is used to subscribe to a specific subject and queue in the NATS messaging system.
The method takes three parameters:
-
subject: The subject to which the subscriber wants to subscribe. -
queueName: The name of the queue to which the subscriber wants to join. This parameter is used for load balancing messages between multiple subscribers. -
handler: TheMessageHandlerinstance that will be invoked to process incoming messages.
First, the method checks if the subject, queueName, and handler parameters are not null and have valid values. If any of these parameters are missing or invalid, an IllegalArgumentException is thrown.
If all the parameters are valid, the method calls the subscribeImplCore method to create and register a subscription for the given subject and queue. The subscribeImplCore method is a private method defined in the NatsDispatcher class, and it handles the actual subscribing logic.
The method returns a Subscription object, which represents the subscription created for the given subject and queue. This object can be used to control the subscription, such as unsubscribing or modifying options.

NatsSubscription subscribeImplCore(String subject, String queueName, MessageHandler handler)
// Assumes the subj/queuename checks are done, does check for closed status
NatsSubscription subscribeImplCore(String subject, String queueName, MessageHandler handler) {
checkBeforeSubImpl();
// If the handler is null, then we use the default handler, which will not allow
// duplicate subscriptions to exist.
if (handler == null) {
NatsSubscription sub = this.subscriptionsUsingDefaultHandler.get(subject);
if (sub == null) {
sub = connection.createSubscription(subject, queueName, this, null);
NatsSubscription wonTheRace = this.subscriptionsUsingDefaultHandler.putIfAbsent(subject, sub);
if (wonTheRace != null) {
// Could happen on very bad timing
this.connection.unsubscribe(sub, -1);
}
}
return sub;
}
return _subscribeImplHandlerProvided(subject, queueName, handler, null);
}The subscribeImplCore method is defined in the NatsDispatcher class and is responsible for subscribing to a NATS subject with the given queue name and message handler.
Here is a step-by-step description of what the method does:
-
It starts by calling the
checkBeforeSubImplmethod to ensure that the pre-subscription checks are done. -
It checks if the provided message handler (
handler) is null. If it is null, it means that the default handler should be used. -
If the handler is null, it retrieves the previously created subscription using the subject from the
subscriptionsUsingDefaultHandlermap. If there is no existing subscription for the subject, it creates a new subscription using the NATS connection with the given subject and queue name, and registers itself as the message handler. -
It then calls the
putIfAbsentmethod onsubscriptionsUsingDefaultHandlermap, which atomically puts the new subscription in the map if the subject doesn't already exist. If another thread wins the race and adds a subscription before this one, it unsubscribes the newly created subscription. -
Finally, if the message handler is not null, it calls a private method
_subscribeImplHandlerProvidedto handle the subscription with the provided handler, subject, queue name, andnullas options.
The method returns the subscription object sub whether it was obtained from the subscriptionsUsingDefaultHandler map or created in the _subscribeImplHandlerProvided method.
The subscribeImplCore method in the NatsDispatcher class is responsible for subscribing to a subject and queue name, with an optional message handler.
It first checks for any necessary conditions before proceeding with the subscription.
If the message handler is null, it uses the default handler, which ensures that duplicate subscriptions do not exist. It checks if a subscription for the given subject already exists using the default handler. If it doesn't exist, it creates a new subscription using the connection.createSubscription method and adds it to the subscriptionsUsingDefaultHandler map with the subject as the key. If another thread won the race and created the subscription, the method unsubscribes from the subscription it created.
If the message handler is not null, it calls the _subscribeImplHandlerProvided method with the given subject, queue name, and message handler.
The method returns the subscription object.

private NatsSubscription _subscribeImplHandlerProvided(String subject, String queueName, MessageHandler handler, NatsSubscriptionFactory nsf)
private NatsSubscription _subscribeImplHandlerProvided(String subject, String queueName, MessageHandler handler, NatsSubscriptionFactory nsf) {
NatsSubscription sub = connection.createSubscription(subject, queueName, this, nsf);
this.subscriptionsWithHandlers.put(sub.getSID(), sub);
this.subscriptionHandlers.put(sub.getSID(), handler);
return sub;
}The method _subscribeImplHandlerProvided is defined in the NatsDispatcher class within the io.nats.client.impl package. Here is a step-by-step description of what this method is doing:
-
The method is receiving four parameters:
subject(the subject to subscribe to),queueName(the optional name of the queue group),handler(the message handler to be assigned to the subscription), andnsf(an instance ofNatsSubscriptionFactory). -
Inside the method, a new
NatsSubscriptionobject is created by calling thecreateSubscriptionmethod on theconnectionobject. This method is responsible for actually subscribing to the specified subject and queue group (if provided). Thethiskeyword refers to the current instance of theNatsDispatcherclass, which acts as the listener for the subscription. -
The newly created
NatsSubscriptionobject is added to thesubscriptionsWithHandlersmap, using the subscription ID (sub.getSID()) as the key. This map is used to keep track of all subscriptions associated with their respective handlers. -
The
handlerparameter is also added to thesubscriptionHandlersmap, using the subscription ID as the key. This map is used to associate each subscription ID with its specific message handler. -
Finally, the method returns the newly created
NatsSubscriptionobject.
In summary, the _subscribeImplHandlerProvided method creates a new subscription to the specified subject and queue group (if provided), associates it with the provided message handler, and keeps track of the subscription and its handler using two separate maps.
The _subscribeImplHandlerProvided method in the NatsDispatcher class is responsible for subscribing to a specific subject using a provided message handler. It takes the subject, queue name, message handler, and an instance of NatsSubscriptionFactory as parameters.
Internally, the method creates a new NatsSubscription object by calling the createSubscription method of the connection object. It then adds the subscription to the subscriptionsWithHandlers map using the subscription ID as the key, and stores the message handler in the subscriptionHandlers map using the same subscription ID.
Finally, the method returns the newly created NatsSubscription object.

String reSubscribe(NatsSubscription sub, String subject, String queueName, MessageHandler handler) {
String sid = connection.reSubscribe(sub, subject, queueName);
this.subscriptionsWithHandlers.put(sid, sub);
this.subscriptionHandlers.put(sid, handler);
return sid;
}Method: reSubscribe
This method is responsible for re-subscribing to a NATS subject using a specified queue and message handler. It takes in a NATS subscription object, the subject to subscribe to, the name of the queue, and a message handler as parameters. Upon successful re-subscription, it returns the subscription ID.
-
sub: A NATS subscription object representing the current subscription. -
subject: A string specifying the subject to subscribe to. -
queueName: A string specifying the name of the queue to use for load balancing. -
handler: A MessageHandler object responsible for handling incoming messages.
- Obtain the connection object from the current instance.
- Call the
reSubscribemethod of the connection object, passing in thesub,subject, andqueueNameparameters. - Assign the returned subscription ID (sid) to the local variable
sid. - Add the
subobject to thesubscriptionsWithHandlersmap usingsidas the key. - Add the
handlerobject to thesubscriptionHandlersmap usingsidas the key. - Return the subscription ID (
sid).
The reSubscribe method is defined in the NatsDispatcher class. It takes in a NatsSubscription object, a subject as a String, a queue name as a String, and a MessageHandler object.
The method first calls the reSubscribe method of the connection object (assuming it is of type Connection), passing in the subscription, subject, and queue name. It captures the returned subscription ID as a String variable named sid.
Next, it adds the subscription sid as a key and the sub object as the value to the subscriptionsWithHandlers map.
Then, it adds the sid as a key and the handler object as the value to the subscriptionHandlers map.
Finally, the method returns the sid.
In summary, the reSubscribe method re-subscribes a provided subscription (NatsSubscription) to a specified subject and queue name, and associates a message handler (MessageHandler) with the subscription. It keeps track of the association between the subscription and the handler using two separate maps. The method returns the subscription ID.

private void checkBeforeSubImpl() {
if (!running.get()) {
throw new IllegalStateException("Dispatcher is closed");
}
if (isDraining()) {
throw new IllegalStateException("Dispatcher is draining");
}
}The method checkBeforeSubImpl() in the NatsDispatcher class is responsible for checking certain conditions before subscribing to a subject.
Here is a step-by-step description of what this method does:
-
Checks if the
runningflag is set tofalse. If it is, it means that the dispatcher is closed. If the flag isfalse, the method throws anIllegalStateExceptionwith the message "Dispatcher is closed". -
Checks if the dispatcher is currently in the process of draining messages. The
isDraining()method is used to determine this. If the dispatcher is indeed draining, the method throws anIllegalStateExceptionwith the message "Dispatcher is draining".
These checks ensure that the dispatcher is in a valid state before allowing a subscription to a subject. If either of these checks fail, an exception is thrown, indicating that the subscription cannot proceed.
The checkBeforeSubImpl method is a private method defined in the NatsDispatcher class from the io.nats.client.impl package. This method is responsible for checking if certain conditions are met before subscribing to a message dispatcher.
First, it checks if the running flag is set to false. If the dispatcher is not currently running, it throws an IllegalStateException with the message "Dispatcher is closed".
Next, it checks if the dispatcher is currently in the process of draining. If the dispatcher is draining, it throws an IllegalStateException with the message "Dispatcher is draining".
In summary, this method ensures that the dispatcher is in a valid state for subscribing before allowing a subscription to be created.

public Dispatcher unsubscribe(String subject, int after) {
if (!this.running.get()) {
throw new IllegalStateException("Dispatcher is closed");
}
if (isDraining()) {
// No op while draining
return this;
}
if (subject == null || subject.length() == 0) {
throw new IllegalArgumentException("Subject is required in unsubscribe");
}
NatsSubscription sub = this.subscriptionsUsingDefaultHandler.get(subject);
if (sub != null) {
// Connection will tell us when to remove from the map
this.connection.unsubscribe(sub, after);
}
return this;
}The unsubscribe method in the NatsDispatcher class, located in the package io.nats.client.impl, is used to unsubscribe from a subject.
-
Check if the dispatcher is closed. If it is closed, throw an
IllegalStateExceptionwith the message "Dispatcher is closed". This ensures that the method cannot be called when the dispatcher is closed. -
Check if the dispatcher is currently draining messages. If it is, return the current instance of the dispatcher without performing any further actions. This is to prevent any operations while the dispatcher is draining.
-
Check if the
subjectparameter is null or empty. If it is, throw anIllegalArgumentExceptionwith the message "Subject is required in unsubscribe". This ensures that a valid subject is provided for unsubscribing. -
Get the subscription associated with the
subjectfrom thesubscriptionsUsingDefaultHandlermap. If a matching subscription is found, proceed to the next step; otherwise, skip to step 7. -
Call the
unsubscribemethod on theconnectionobject, passing the found subscription and theafterparameter. This effectively unsubscribes from the subject. Theconnectionobject is responsible for informing the dispatcher when to remove the subscription from thesubscriptionsUsingDefaultHandlermap. -
Return the current instance of the dispatcher. This allows for method chaining.
-
End the method. If no subscription was found for the provided subject, the method returns the current instance of the dispatcher without performing any further actions.
The unsubscribe method in the NatsDispatcher class allows a client to unsubscribe from a specific subject.
It first checks if the Dispatcher is running, and if not, throws an IllegalStateException.
Next, if the Dispatcher is in the process of draining, it simply returns itself without performing any further operations.
Then, it ensures that a valid subject is provided; otherwise, it throws an IllegalArgumentException.
If a subscription exists for the provided subject, it delegates the actual unsubscribe operation to the connection and passes the subscription and a "time to wait" parameter.
Finally, it returns itself, allowing for method chaining.

public Dispatcher unsubscribe(Subscription subscription, int after) {
if (!this.running.get()) {
throw new IllegalStateException("Dispatcher is closed");
}
if (isDraining()) {
// No op while draining
return this;
}
if (subscription.getDispatcher() != this) {
throw new IllegalStateException("Subscription is not managed by this Dispatcher");
}
// We can probably optimize this path by adding getSID() to the Subscription interface.
if (!(subscription instanceof NatsSubscription)) {
throw new IllegalArgumentException("This Subscription implementation is not known by Dispatcher");
}
NatsSubscription ns = ((NatsSubscription) subscription);
// Grab the NatsSubscription to verify we weren't given a different manager's subscription.
NatsSubscription sub = this.subscriptionsWithHandlers.get(ns.getSID());
if (sub != null) {
// Connection will tell us when to remove from the map
this.connection.unsubscribe(sub, after);
}
return this;
}This method is defined in the class io.nats.client.impl.NatsDispatcher and takes two parameters: subscription and after.
If the running flag is set to false, an IllegalStateException is thrown with the message "Dispatcher is closed".
If the isDraining() method returns true, the method simply returns the current Dispatcher object without performing any operations.
If the subscription object's dispatcher is not equal to the current dispatcher, an IllegalStateException is thrown with the message "Subscription is not managed by this Dispatcher".
If the subscription object is not an instance of NatsSubscription, an IllegalArgumentException is thrown with the message "This Subscription implementation is not known by Dispatcher".
Cast the subscription object to NatsSubscription and assign it to the variable ns.
Retrieve the NatsSubscription object associated with the same subscription ID (ns.getSID()) from the subscriptionsWithHandlers map. If it exists, assign it to the variable sub.
If sub is not null, call the unsubscribe() method of the connection object (of type NatsConnection) and pass in the sub object and after parameter.
Finally, return the current Dispatcher object.
Note: This method is used to unsubscribe a subscription from the NATS connection. It performs various checks and verifies that the subscription is managed by the dispatcher before unsubscribing it from the connection.
The unsubscribe method in the NatsDispatcher class allows the software engineer to unsubscribe a particular subscription from the NATS messaging system.
The method first checks if the dispatcher is closed and throws an exception if it is. Then, it checks if the dispatcher is currently draining, and if so, it returns without performing any operation.
Next, it verifies that the given subscription is managed by this dispatcher. If not, it throws an exception.
Then, it checks if the given subscription is an instance of the NatsSubscription class. If not, it throws an exception.
After that, it obtains the NatsSubscription instance from the given subscription. It then retrieves the subscription with the same subscription ID (SID) from the subscriptionsWithHandlers map.
If a matching subscription is found, it delegates the unsubscribe operation to the underlying NATS connection by calling the unsubscribe method on the connection. The after parameter specifies the number of expected messages to be processed before actually unsubscribing.
Finally, the method returns the NatsDispatcher instance to allow for method chaining.

void sendUnsubForDrain() {
this.subscriptionsUsingDefaultHandler.forEach((id, sub) -> {
this.connection.sendUnsub(sub, -1);
});
this.subscriptionsWithHandlers.forEach((sid, sub) -> {
this.connection.sendUnsub(sub, -1);
});
}The sendUnsubForDrain method is defined in the NatsDispatcher class within the io.nats.client.impl package. This method performs the following steps:
- Iterate over each subscription that is using the default handler, using the
forEachmethod on thesubscriptionsUsingDefaultHandlermap. - For each subscription, invoke the
sendUnsubmethod on theconnectionobject passing in the subscription and-1as arguments. This method is responsible for sending anUNSUBmessage to the NATS server to unsubscribe from the specified subscription. - Iterate over each subscription with individual handlers, using the
forEachmethod on thesubscriptionsWithHandlersmap. - For each subscription, again invoke the
sendUnsubmethod on theconnectionobject passing in the subscription and-1as arguments. This ensures that all subscriptions are unsubscribed from the NATS server.
In summary, the sendUnsubForDrain method unsubscribes all subscriptions associated with the NatsDispatcher, both with default handlers and individual handlers, by invoking the sendUnsub method on the underlying connection object.
The sendUnsubForDrain method in the NatsDispatcher class is used to send unsubscribe messages for draining existing subscriptions. It iterates over the subscriptionsUsingDefaultHandler map and subscriptionsWithHandlers map to get the subscriptions associated with the default handler and custom handlers respectively. For each subscription, it calls the sendUnsub method of the underlying connection object with a timeout value of -1, indicating an immediate unsubscribe. This method is used in situations where the client needs to unsubscribe from all active subscriptions as part of a draining process.

NatsIterableConsumer
The NatsIterableConsumer class is a public class that extends the NatsMessageConsumer class and implements the IterableConsumer interface. It provides functionality for consuming messages from a NATS message broker and allows for iteration over the received messages.
public Message nextMessage(Duration timeout) throws InterruptedException, JetStreamStatusCheckedException
@Override
public Message nextMessage(Duration timeout) throws InterruptedException, JetStreamStatusCheckedException {
try {
return sub.nextMessage(timeout);
} catch (JetStreamStatusException e) {
throw new JetStreamStatusCheckedException(e);
} catch (IllegalStateException i) {
// this happens if the consumer is stopped, since it is
// drained/unsubscribed, so don't pass it on if it's expected
return null;
}
}The nextMessage method described in the class io.nats.client.impl.NatsIterableConsumer is responsible for retrieving the next message from a subscription, with a specified timeout for waiting. Here is a step-by-step description of what the method does:
- It overrides the
nextMessagemethod from the parent class or interface. - It has a parameter
timeoutof typeDurationwhich represents the maximum amount of time to wait for a message. - It may throw two types of exceptions:
InterruptedExceptionandJetStreamStatusCheckedException. - The method tries to retrieve the next message from a subscription by invoking the
nextMessagemethod on asubobject. Thesubobject is presumably a subscription object associated with the consumer. - If the
nextMessagecall throws aJetStreamStatusException, it catches the exception and throws aJetStreamStatusCheckedExceptioninstead. This is done by creating a new instance ofJetStreamStatusCheckedExceptionwith the caught exception as the cause. - If the
nextMessagecall throws anIllegalStateException, it means that the consumer is stopped (possibly due to being drained or unsubscribed). In this case, the method returnsnullwithout re-throwing the exception. - If the
nextMessagecall succeeds and returns a message, it is returned by the method.
Overall, the nextMessage method provides a way to fetch the next message from a subscription with a specified timeout, handling any potential exceptions that may occur during the process.
The nextMessage method in the NatsIterableConsumer class is used to retrieve the next message from the NATS streaming server within a specified timeout duration.
The method overrides the nextMessage method defined in the MessageIterator interface. It takes a Duration parameter representing the maximum amount of time to wait for a message before timing out.
Inside the method, it calls the nextMessage method of the underlying subscription object (sub) and returns the message it receives. If the underlying NATS stream throws a JetStreamStatusException, it converts it to a JetStreamStatusCheckedException and throws it.
If the underlying subscription object is in an illegal state, which typically happens when the consumer is stopped or unsubscribed, the method returns null without passing on the exception.

NatsConsumerContext
The NatsConsumerContext class is a public class that implements the ConsumerContext interface. It provides simplification functionalities, but please note that these features are experimental and subject to change.
public Message next(Duration maxWait) throws IOException, InterruptedException, JetStreamStatusCheckedException, JetStreamApiException
@Override
public Message next(Duration maxWait) throws IOException, InterruptedException, JetStreamStatusCheckedException, JetStreamApiException {
if (maxWait == null) {
return new NextSub(DEFAULT_EXPIRES_IN_MILLIS, streamContext.js, bindPso).next();
}
return next(maxWait.toMillis());
}The next method in class io.nats.client.impl.NatsConsumerContext is used to retrieve the next message from the NATS server.
Here is a step-by-step description of what the method does based on its body:
-
The method overrides the
nextmethod from its superclass and takes amaxWaitparameter of typeDuration, which represents the maximum amount of time to wait for a message. -
The method first checks if the
maxWaitparameter is null. -
If the
maxWaitparameter is null, it creates a new instance of theNextSubclass, passing theDEFAULT_EXPIRES_IN_MILLIS,streamContext.js, andbindPsoas arguments. It then calls thenextmethod on this instance to retrieve the next message. TheNextSubclass is likely responsible for interacting with the NATS server to fetch messages. -
If the
maxWaitparameter is not null, it converts the duration to milliseconds using thetoMillismethod and calls thenextmethod with the converted value. This suggests that thenextmethod is overloaded with a version that takes a long parameter representing the number of milliseconds to wait for a message. -
The method then returns the retrieved message.
Please note that the full functionality and behavior of the method may depend on the implementation details of the NextSub class and other related classes.
The next method in the NatsConsumerContext class is responsible for retrieving the next message from a NATS consumer.
The method takes a parameter maxWait of type Duration, which defines the maximum time to wait for a message.
In the method body, it checks if maxWait is null. If it is null, it calls the next method of the NextSub class, passing the default expiration value, the JetStream context, and the bindPso (presumed to be some sort of bind point) as parameters. This is done by creating a new instance of NextSub and invoking the next method on it.
If maxWait is not null, the method converts the duration to milliseconds using the toMillis method and then calls the next method recursively with the converted value.
The method returns a Message object representing the next message obtained from the NATS consumer. It can throw various exceptions including IOException, InterruptedException, JetStreamStatusCheckedException, and JetStreamApiException.

public Message next(long maxWaitMillis) throws IOException, InterruptedException, JetStreamStatusCheckedException, JetStreamApiException
@Override
public Message next(long maxWaitMillis) throws IOException, InterruptedException, JetStreamStatusCheckedException, JetStreamApiException {
if (maxWaitMillis < MIN_EXPIRES_MILLS) {
throw new IllegalArgumentException("Max wait must be at least " + MIN_EXPIRES_MILLS + " milliseconds.");
}
return new NextSub(maxWaitMillis, streamContext.js, bindPso).next();
}This method next(long maxWaitMillis) is defined in the io.nats.client.impl.NatsConsumerContext class. It is an overridden method from its parent class.
Step-by-Step Description:
-
Check if the value of
maxWaitMillisis less than the constantMIN_EXPIRES_MILLS.- If it is less, throw an
IllegalArgumentExceptionwith the message "Max wait must be at least MIN_EXPIRES_MILLS milliseconds.".
- If it is less, throw an
-
Create a new instance of the
NextSubclass, passing the value ofmaxWaitMillis, thestreamContext.jsobject, and thebindPsoobject as arguments. -
Call the
next()method on theNextSubinstance to retrieve the next message. -
Return the retrieved message.
The purpose of this method is to retrieve the next message from the NextSub instance with a specified maximum wait time. It performs some basic argument validation and then delegates the task to the NextSub class.
The method next in the NatsConsumerContext class is responsible for retrieving the next message from a NATS consumer.
Here is a breakdown of what the method does:
- It takes a parameter
maxWaitMilliswhich specifies the maximum time to wait for a message. - If the
maxWaitMillisis less than a constant valueMIN_EXPIRES_MILLS, anIllegalArgumentExceptionis thrown with a corresponding error message. - Otherwise, a new instance of the
NextSubclass is created, passing themaxWaitMillis, thestreamContext.jsobject, and thebindPsoobject as parameters. - The
nextmethod of theNextSubinstance is then called to retrieve the next message from the NATS consumer. - The retrieved message is returned as the result of the method.

public MessageConsumer consume(MessageHandler handler, ConsumeOptions consumeOptions) throws IOException, JetStreamApiException
@Override
public MessageConsumer consume(MessageHandler handler, ConsumeOptions consumeOptions) throws IOException, JetStreamApiException {
Validator.required(handler, "Message Handler");
Validator.required(consumeOptions, "Consume Options");
return new NatsMessageConsumer(new SimplifiedSubscriptionMaker(streamContext.js, bindPso), handler, consumeOptions, lastConsumerInfo);
}The consume method in the NatsConsumerContext class is responsible for creating a new MessageConsumer and returning it. Here is a step-by-step description of what the method does:
- It overrides the
consumemethod from theMessageConsumerinterface. - The method takes two parameters:
handlerof typeMessageHandlerandconsumeOptionsof typeConsumeOptions. - It performs validation checks to ensure that the
handlerandconsumeOptionsparameters are not null. If either of them is null, an exception is thrown with a descriptive error message. - It creates a new instance of the
NatsMessageConsumerclass. - The
NatsMessageConsumerconstructor takes four parameters:streamContext.js,bindPso,handler,consumeOptions, andlastConsumerInfo. - The
streamContext.jsis a field of theNatsConsumerContextclass and is used to access the JetStream context. - The
bindPsois another field of theNatsConsumerContextclass. - The
handleris theMessageHandlerpassed as a parameter to theconsumemethod. - The
consumeOptionsis theConsumeOptionspassed as a parameter to theconsumemethod. - The
lastConsumerInfois another field of theNatsConsumerContextclass. - The instantiated
NatsMessageConsumerobject is then returned as the result of theconsumemethod.
In summary, the consume method creates a new NatsMessageConsumer object using the provided MessageHandler, ConsumeOptions, and other required information, and returns it as a MessageConsumer.
The consume method in the NatsConsumerContext class is responsible for creating a MessageConsumer object that will consume messages from a NATS server.
The method requires a MessageHandler object and ConsumeOptions object as input parameters. These objects define how the messages will be handled (through the handler parameter) and the specific options for consuming messages (through the consumeOptions parameter).
Internally, the method creates a new NatsMessageConsumer object, which is a specialized implementation of the MessageConsumer interface. This object is instantiated with a SimplifiedSubscriptionMaker object, the handler parameter, the consumeOptions parameter, and the lastConsumerInfo object.
Overall, the consume method provides a convenient way to create a NATS message consumer with the specified message handler and consume options.

NatsConnection
The NatsConnection class is an implementation of the Connection interface. It represents a connection to a NATS messaging system, allowing software engineers to interact with it programmatically. This class provides the necessary functionalities to establish, manage, and handle communications with the NATS server.
void connect(boolean reconnectOnConnect) throws InterruptedException, IOException
// Connect is only called after creation
void connect(boolean reconnectOnConnect) throws InterruptedException, IOException {
if (options.getServers().size() == 0) {
throw new IllegalArgumentException("No servers provided in options");
}
boolean trace = options.isTraceConnection();
long start = System.nanoTime();
this.lastError.set("");
timeTrace(trace, "starting connect loop");
Set<NatsUri> failList = new HashSet<>();
boolean keepGoing = true;
NatsUri first = null;
NatsUri cur;
while (keepGoing && (cur = serverPool.peekNextServer()) != null) {
if (first == null) {
first = cur;
} else if (cur.equals(first)) {
// connect only goes through loop once
break;
}
// b/c we only peeked.
serverPool.nextServer();
// let server pool resolve hostnames, then loop through resolved
List<NatsUri> resolvedList = resolveHost(cur);
while (resolvedList.size() > 0) {
if (isClosed()) {
keepGoing = false;
break;
}
// new on each attempt
connectError.set("");
timeTrace(trace, "setting status to connecting");
updateStatus(Status.CONNECTING);
timeTrace(trace, "trying to connect to %s", cur);
NatsUri resolved = resolvedList.remove(0);
tryToConnect(cur, resolved, System.nanoTime());
if (isConnected()) {
serverPool.connectSucceeded(cur);
keepGoing = false;
break;
}
timeTrace(trace, "setting status to disconnected");
updateStatus(Status.DISCONNECTED);
failList.add(cur);
serverPool.connectFailed(cur);
String err = connectError.get();
if (this.isAuthenticationError(err)) {
this.serverAuthErrors.put(resolved, err);
}
}
}
if (!isConnected() && !isClosed()) {
if (reconnectOnConnect) {
timeTrace(trace, "trying to reconnect on connect");
reconnect();
} else {
timeTrace(trace, "connection failed, closing to cleanup");
c