Fetch & subscribe

Run relay fetch demo

Use fetch when you need a result set, such as a profile or a page of notes. For a feed that should keep updating, use subscribe: matching history arrives first, then new events as they happen.

// live: keeps delivering until you call sub.close()
NostrSubscription sub = pool.subscribe(
    new NostrFilter().withKind(1).limit(20));
sub.addEventListener((s, event, stored) -> {
    System.out.println((stored ? "[history] " : "[live] ") + event.getContent());
});
sub.open();

// one-shot: returns the collected events
List<SignedNostrEvent> notes = pool.fetch(
    new NostrFilter().withKind(1).limit(20),
    20, Duration.ofSeconds(10)).await();

Subscriptions

Create the subscription, attach your listeners, then call open().

Listener Callback When it fires
addEventListener onSubEvent(sub, event, stored) for every matching event
addEoseListener onSubEose(sub, relay, everyWhere) when a relay finishes sending history
addOpenListener onSubOpen(sub) when the subscription opens
addCloseListener onSubClose(sub, reasons) when it closes

When handling incoming events, distinguish history from new activity:

  • Use stored to tell whether the event is history (true) or arrived live (false). A freshly opened subscription first replays stored events, then flips to live.
  • EOSE ("end of stored events") means a relay has finished sending its history. After EOSE, that relay's subsequent events are live. onSubEose is invoked per relay; everyWhere refers to the relays selected for this read, not every registered standby relay. A live event from one relay can arrive while another is still sending history.

Stored-event callbacks finish before that relay's EOSE callback advances the read. A filter's limit controls historical results, not the lifetime of a live subscription: .limit(0) still permits live events. Closing or cancelling a fetch retires its subscription so late replies do not reopen it.

Tip

Call setVerifyMatchLocally(true) to also check received events against the filter locally - useful when a relay is malicious, malfunctioning, or limited in its capabilities.

Tip

Remember to close() the subscription when you no longer need it.

Fetch policies

A fetch needs a stopping condition. Usually that is a number of events or a timeout, but you can also wait until the relays have finished sending their stored results.

Policy Stops when…
NostrWaitForEventFetchPolicy The requested number of matching events arrives, the timeout expires, or all relays send EOSE if endOnEose is enabled
NostrAllEOSEPoolFetchPolicy Every relay sends EOSE; a silent relay can keep the fetch waiting

The default fetch(filter, numEvents, timeout) is shorthand for:

pool.fetch(filter, numEvents, /* withEose = */ false, timeout);

With this default, EOSE alone does not end the fetch: it waits for numEvents results or the timeout. Pass withEose = true to return earlier when all relays have finished sending history. The returned list is ordered newest-first.

To use a policy explicitly:

// finish after every relay has sent its stored results
AsyncTask<List<SignedNostrEvent>> task = pool.fetch(
    List.of(new NostrFilter().withKind(1).limit(100)),
    NostrAllEOSEPoolFetchPolicy.get());

// wait for 5 matching events, max 30 seconds, stop early on EOSE
NostrPoolFetchPolicy policy = NostrWaitForEventFetchPolicy.get(
    ev -> ev.getContent().contains("nostr4j"),
    5, true, Duration.ofSeconds(30));