Skip to content

Handle consume initial subscription failure.#1573

Merged
scottf merged 1 commit into
mainfrom
first-sub-failure
May 11, 2026
Merged

Handle consume initial subscription failure.#1573
scottf merged 1 commit into
mainfrom
first-sub-failure

Conversation

@scottf
Copy link
Copy Markdown
Contributor

@scottf scottf commented May 11, 2026

No description provided.

@scottf scottf changed the title Handle consume initial sub failure. Handle consume initial subscription failure. May 11, 2026
@scottf
Copy link
Copy Markdown
Contributor Author

scottf commented May 11, 2026

@claude review this fix for

java.lang.NullPointerException: Cannot invoke "io.nats.client.impl.PullMessageManager.updateLastMessageReceived()" because "this.pmm" is null
	at io.nats.client.impl.NatsMessageConsumer.resetOnException(NatsMessageConsumer.java:169)
	at io.nats.client.impl.NatsMessageConsumer.doSub(NatsMessageConsumer.java:163)
	at io.nats.client.impl.NatsMessageConsumer.<init>(NatsMessageConsumer.java:59)
	at io.nats.client.impl.NatsConsumerContext.consume(NatsConsumerContext.java:342)
	at io.nats.client.impl.NatsConsumerContext.consume(NatsConsumerContext.java:304)
	at io.nats.client.impl.NatsOrderedConsumerContext.consume(NatsOrderedConsumerContext.java:133)

@synadia-claude-reviewer
Copy link
Copy Markdown

synadia-claude-reviewer Bot commented May 11, 2026

Claude finished @scottf's task in 51s —— View job


Code Review

  • Gather context and read changed files
  • Analyze the fix against the NPE stack trace
  • Review correctness and completeness
  • Post findings

Summary

The fix is correct and minimal. Here's the analysis:

Root Cause

The NPE occurs because pmm is null when resetOnException() is called during the first (constructor-time) subscription attempt:

  1. NatsMessageConsumer.<init> calls doSub(true) (line 59)
  2. Inside doSub, subscriptionMaker.subscribe(...) throws before initSub() is reached
  3. Since initSub() sets pmm = (PullMessageManager)sub.manager (NatsMessageConsumerBase.java:51), pmm remains null
  4. The catch block calls resetOnException(), which dereferences pmm → NPE

The Fix

catch (JetStreamApiException | IOException e) {
    if (first) {
        throw e;
    }
    resetOnException();
}

When first == true, re-throwing is exactly the right behavior:

  • There is no established state to reset — pmm and sub have never been initialized
  • The constructor already declares throws IOException, JetStreamApiException, so the exception propagates cleanly to NatsConsumerContext.consume() (the caller)
  • The caller can handle the failure appropriately rather than swallowing it into a broken half-initialized state

The Non-First Path Is Unaffected

When pullTerminatedByError() calls doSub(false) and a re-subscribe fails, resetOnException() is still called — which is safe there because pmm was necessarily set during the prior successful initSub() call.

Minor Observation

resetOnException() itself has no null-guard on pmm:

private void resetOnException() {
    fullResetPending();
    pmm.updateLastMessageReceived();   // would NPE if pmm == null
    pmm.initOrResetHeartbeatTimer();
}

The fix avoids calling it when pmm is null, which is the right approach. An alternative would be adding a null-check inside resetOnException(), but that would hide the logic intent — the current fix makes it explicit that an initial failure should be surfaced rather than silently recovered.

The fix is correct. No issues found.

Copy link
Copy Markdown
Member

@johnweldon johnweldon left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

@scottf scottf merged commit 9ce1128 into main May 11, 2026
7 checks passed
@scottf scottf deleted the first-sub-failure branch May 11, 2026 20:20
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants