Skip to content

Nats backend#292

Merged
sonus21 merged 134 commits intomasterfrom
nats-backend
May 2, 2026
Merged

Nats backend#292
sonus21 merged 134 commits intomasterfrom
nats-backend

Conversation

@sonus21
Copy link
Copy Markdown
Owner

@sonus21 sonus21 commented Apr 30, 2026

Description

Please include a summary of the changes and the related issue. Please also include relevant motivation and context. List any dependencies that are required for this change.

Fixes # (issue)

Type of change

Please delete options that are not relevant.

  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to not work as expected)
  • This change requires a documentation update

How Has This Been Tested?

Please describe the tests that you ran to verify your changes. Provide instructions so we can reproduce. Please also list any relevant details for your test configuration

  • Unit Test
  • Integration Test
  • Reactive Integration Test

Test Configuration:

  • Spring Version
  • Spring Boot Version
  • Redis Driver Version

Checklist:

  • My code follows the style guidelines of this project
  • I have performed a self-review of my code
  • I have commented my code, particularly in hard-to-understand areas
  • I have made corresponding changes to the documentation
  • I have added tests that prove my fix is effective or that my feature works
  • New and existing unit tests pass locally with my changes

sonus21 and others added 30 commits April 30, 2026 14:55
Introduces an internal MessageBroker SPI in rqueue-core under
com.github.sonus21.rqueue.core.spi/ to enable a NATS/JetStream
backend in a follow-up commit. RedisMessageBroker is added as a thin
delegating implementation backed by the existing RqueueMessageTemplate
and DAO paths; behavior on the Redis path is unchanged.

Public API additions are additive only: setMessageBroker/getMessageBroker
on RqueueMessageTemplateImpl, SimpleRqueueListenerContainerFactory, and
RqueueMessageListenerContainer; a new constructor overload on
RqueueMessageTemplateImpl. When messageBroker is null (the existing
code path) every gate falls back to legacy behavior, so current Redis
users see no change.

Tests: 461 existing tests + 14 new RedisMessageBrokerDelegationTest
cases pass; 8 skipped, 0 failures.

Assisted-By: Claude Code
Includes the new rqueue-nats Gradle module in settings.gradle and
adds natsVersion (2.25.2) plus testcontainersVersion to the root
build. The module's build.gradle declares it as broker-impl-only
(rqueue-core + io.nats:jnats); no Spring/Spring Boot deps live here.

rqueue-spring and rqueue-spring-boot-starter pick up rqueue-nats and
jnats as compileOnly so their @configuration / auto-config classes
can reference NATS types behind @ConditionalOnClass without forcing
NATS onto current Redis users at runtime. Test-scoped runtime deps
let the integration tests exercise both backends.

Assisted-By: Claude Code
Implements the MessageBroker SPI for NATS JetStream in the new
rqueue-nats module. Includes JetStreamMessageBroker with a fluent
Builder, RqueueNatsConfig POJO with stream/consumer defaults, an
idempotent NatsProvisioner, and a ServiceLoader-discovered
JetStreamMessageBrokerFactory registered under
META-INF/services/com.github.sonus21.rqueue.core.spi.MessageBrokerFactory.

v1 scope: pull, durable consumers only; immediate enqueue + ack +
retry-via-nak; DLQ via MaxDeliver + advisory bridge; dedup via
Nats-Msg-Id and the configured Duplicates window; ephemeral
consumers reserved for peek; per-broker in-memory inFlight map for
ack/nack lookup. enqueueWithDelay throws UnsupportedOperationException;
moveExpired is a no-op; capabilities are all false.

Tests: 4 unit tests pass (JetStreamMessageBrokerDelayThrowsTest);
7 integration tests gated on Docker via @testcontainers
(disabledWithoutDocker = true) — they skip when Docker isn't
available and run end-to-end against nats:2.10-alpine -js otherwise.

Assisted-By: Claude Code
Phase 3 of the NATS backend wiring extends the listener annotation with
an optional consumerName(), introduces ConsumerNameResolver for the
"rqueue-<queue>-<bean>#<method>" default, and lets the listener container
flag the message handler to skip the "exactly one primary per queue"
check when the active broker reports usesPrimaryHandlerDispatch == false.
A single WARN is logged for queues with multiple @RqueueHandler methods
under such a broker. Cross-handler (queueName, consumerName) collision
detection runs at container init for the gated path so boot fails fast.

Redis behavior is unchanged: the new flag defaults to true and the
container only takes the gated branches when a non-Redis broker is set.

Assisted-By: Claude Code
Adds nullable fields (natsStream, natsSubject, natsDlqStream,
natsDlqSubject, natsAckWaitOverride, natsMaxDeliverOverride,
natsDedupWindow) and resolved* helpers that derive sensible defaults
from queueName/visibilityTimeout/numRetry when the override is null.
Purely additive: no existing field, ordering, constructor, or method
is modified, and the existing equals/hashCode/toString contract is
preserved.

Assisted-By: Claude Code
…UI for NATS

The dashboard service (RqueueQDetailService) now optionally takes a
MessageBroker via @Autowired(required=false). When the broker is set:

  * getQueueDataStructureDetail() prefers MessageBroker.size(QueueDetail)
    for the pending-queue size; the Redis path is preserved as fallback
    when no broker is wired.
  * getExplorePageData() prefers MessageBroker.peek(QueueDetail, off, n)
    for the ready (LIST) queue.
  * When capabilities().supportsScheduledIntrospection() is false, the
    SCHEDULED nav tab and queue-detail entry are suppressed, the explore
    response for the scheduled queue returns empty rows, and the new
    additive DataViewResponse#hideScheduledPanel flag is set so the
    Pebble template can hide the panel. The scheduled menu item in
    base.html is now wrapped in {% if not hideScheduledPanel %}.
  * supportsCronJobs() drives the additive hideCronJobs flag the same
    way for follow-up cron-management UI work.

Changes are purely additive: existing constructors, field ordering, and
Redis-only behavior are unchanged when no broker bean is present.

Assisted-By: Claude Code
Provides Spring Boot auto-configuration that wires a JetStream-backed
MessageBroker when rqueue.backend=nats is set and io.nats:jnats is on
the classpath. The auto-config sits before RqueueListenerAutoConfig so
its broker bean wins; the listener container factory now picks up an
optional MessageBroker via ObjectProvider, leaving the Redis-only
default path identical when the property/classpath conditions don't
match.

Assisted-By: Claude Code
Gives non-Boot Spring users a way to opt into the JetStream backend
without depending on Spring Boot. The new Backend enum (AUTO, REDIS,
NATS) on @EnableRqueue picks which configuration to import; AUTO
preserves the current Redis-only path unless jnats is on the classpath
and rqueue.backend=nats. NATS forces the import, REDIS skips it. A
small NatsBackendCondition keeps the AUTO branch lazy.

Assisted-By: Claude Code
CLAUDE.md spells out that AI tools (Claude, Copilot, etc.) must not
appear as Co-Authored-By on commits — humans only — but an
Assisted-By: <tool> trailer is acceptable for noting the assistance
without claiming co-authorship. Future AI sessions read this file
cold and align their commit templates accordingly.

Assisted-By: Claude Code
Phase 3.5 introduces a per-listener poller that drives the runtime path
for capability-gated brokers (NATS / JetStream). One poller is bound to
a single (queueDetail, consumerName, handlerMethod) triple and runs an
independent loop: pop a batch with a short wait, deserialize each
payload through the configured MessageConverter, invoke the bound bean
method via reflection, then ack on success or nack with the configured
TaskExecutionBackOff delay on exception.

Direct reflection dispatch (Option B in the phase notes) keeps the
broker path narrow and avoids the primary/secondary handler mapping
that NATS-style backends do not honor. Concurrency is intentionally a
single thread per (queue, consumerName) for v1; JetStream's
MaxAckPending already controls in-flight distribution.

Container wiring lands in the next commit.

Assisted-By: Claude Code
When the active MessageBroker reports usesPrimaryHandlerDispatch=false
the container now skips the legacy Redis-side wiring (startQueue /
startGroup) and instead instantiates one BrokerMessagePoller per
(queue, consumerName) pair resolved from registered @RqueueListener
methods, submitting each to a minimal task executor. The legacy code
path is unchanged when no broker is set or the broker reports
REDIS_DEFAULTS capabilities.

Lifecycle integration:
  * doStop signals each poller to exit and waits up to maxWorkerWaitTime
  * doDestroy closes the broker if it implements AutoCloseable
  * initialize() bypasses worker thread-map creation on the broker path
    since pollers run on their own task executor

@RqueueListener.concurrency > 1 logs a single INFO and is honored as
a single-thread poller in v1; JetStream MaxAckPending governs in-flight
distribution. Priority queues on the broker path are out of scope for
Phase 3.5 and remain a follow-up.

Assisted-By: Claude Code
Adds NatsBackendEndToEndIT under rqueue-spring-boot-starter with a
Testcontainers-managed nats:2.10-alpine -js, an @RqueueListener, and
the standard RqueueMessageEnqueuer. The test exercises the intended
path: enqueue -> JetStreamMessageBroker.enqueue -> stream -> poller ->
listener -> ack.

The test is currently @disabled because the producer enqueue path
(BaseMessageSender#enqueue + RqueueMessageMetadataService) is not yet
routed through MessageBroker; with rqueue.backend=nats and no Redis
instance available, enqueue still hits Redis. The test is left on disk
(compiled, skipped) so the wiring fix has a ready-made acceptance test;
the @disabled message documents what to fix.

Adds testcontainers:junit-jupiter to the starter test classpath since
the test uses @testcontainers / @container.

Assisted-By: Claude Code
Previously the broker poller branch spawned exactly one BrokerMessagePoller
per (queue, consumerName) pair and logged an INFO advising users that
concurrency was not honored. JetStream's MaxAckPending controls in-flight
distribution but per-thread parallelism still requires multiple subscribers
bound to the same durable consumer.

This change spawns N pollers per (queue, consumerName) where N is the upper
bound of the listener's concurrency setting. All threads share the same
durable consumer; JetStream load-balances messages across them and shares a
single MaxAckPending budget.

Elastic ramping (min < max) is documented as not yet implemented — the
container always uses a fixed pool sized to max. The default task executor's
pool is now sized to the sum of resolved thread counts so every poller has
a dedicated worker.

Assisted-By: Claude Code
Adds two additive default methods to MessageBroker so backends can route
on a per-priority basis without breaking existing implementations:

- enqueue(QueueDetail, String priority, RqueueMessage): defaults to the
  unsuffixed enqueue (Redis already encodes priority in the queue name).
- pop(QueueDetail, String priority, String consumerName, int, Duration):
  defaults to the unsuffixed pop.

JetStreamMessageBroker overrides both to derive priority-specific
streams (rqueue-<queue>-<priority>) and subjects (rqueue.<queue>.<priority>).

The listener container now expands a queue's listener-declared priority
map into one BrokerMessagePoller-set per priority bucket, with consumer
names suffixed by "-<priority>". Combined with concurrency, a queue with
N priorities and concurrency=K spawns N*K pollers, each bound to its own
priority-specific durable consumer. Weighted/strict priority dispatch is
intentionally out of scope; relative fairness is left to thread
scheduling.

Cross-queue priorityGroup support is not implemented for NATS in v1; a
single WARN is logged when a listener declares one. Elastic concurrency
ramping is also still deferred; the fixed-pool-at-max behavior is reused.

QueueDetail gains resolvedNatsStreamForPriority and
resolvedNatsSubjectForPriority helpers parallel to the existing
resolvedNats* derivations.

The enqueuer side: RqueueMessageEnqueuerImpl.enqueueWithPriority now
keeps using the suffixed queue name for Redis (unchanged) but, when the
underlying RqueueMessageTemplate exposes a non-primary-handler-dispatch
broker, switches to passing the original queue name plus the priority
hint through BaseMessageSender.pushMessage so the broker's priority-aware
overload routes to the correct subject.

Assisted-By: Claude Code
Add additive default `enqueueReactive` and `enqueueWithDelayReactive`
methods on the MessageBroker SPI that wrap the blocking calls in
Mono.fromRunnable. Override both on JetStreamMessageBroker:
`enqueueReactive` uses `JetStream.publishAsync(...)` adapted via
Mono.fromFuture so the publish does not block the calling thread, and
`enqueueWithDelayReactive` returns Mono.error with the same message as
the synchronous variant.

Assisted-By: Claude Code
Add a setMessageBroker hook on ReactiveRqueueMessageEnqueuerImpl so the
reactive enqueue path can delegate to a MessageBroker SPI implementation
(e.g. JetStream) instead of the legacy reactive Redis template. When no
broker bean is present the impl keeps the existing ReactiveScriptExecutor
path, preserving behavior for Redis users.

Wire the setter from both the Spring Boot autoconfig and the non-Boot
RqueueListenerConfig via ObjectProvider<MessageBroker>, mirroring the
pattern already used for the listener container factory.

Add reactor-test as a test-only dependency on rqueue-core and rqueue-nats
to support the new StepVerifier-based tests.

Tests:
- ReactiveRqueueMessageEnqueuerBrokerRoutingTest verifies broker routing
  for both immediate and delayed enqueue, plus fallback to the redis
  template when no broker is configured.
- JetStreamMessageBrokerReactiveEnqueueIT (Testcontainers, gated on
  Docker) publishes 5 messages reactively and asserts delayed reactive
  enqueue surfaces UnsupportedOperationException.

Assisted-By: Claude Code
When the active MessageBroker reports !usesPrimaryHandlerDispatch (e.g.
NATS/JetStream), short-circuit storeMessageMetadata so the dashboard's
informational HASH write to Redis is not attempted on a NATS-only
deployment that may have no Redis on the classpath. Reactive callers get
Mono.just(true) to mimic a successful save.

Assisted-By: Claude Code
The producer-side gap is now closed: BaseMessageSender routes through
the MessageBroker SPI when the active broker advertises
!usesPrimaryHandlerDispatch(), and storeMessageMetadata short-circuits
on the same flag. The sync RqueueMessageEnqueuer / @RqueueListener path
exercised by this test no longer needs Redis, so we drop @disabled.

Testcontainers' disabledWithoutDocker keeps the test skipping gracefully
on hosts without Docker.

Assisted-By: Claude Code
Adds a docs-overrides.css with a warmer accent for the .note callout in
dark mode; the default blue/purple tone clashes with the surrounding
content. Twelve lines, all CSS, no template wiring needed.

Assisted-By: Claude Code
Adds @tag("nats") meta-annotations (@NatsIntegrationTest and @NatsUnitTest)
in rqueue-nats and applies them to the JetStream test files; tags
RqueueNatsAutoConfigTest, RqueueNatsListenerConfigTest, and
NatsBackendEndToEndIT directly. NatsUnitTest deliberately omits the
MockitoExtension because the existing tests use Mockito.mock() directly
and adding the extension would activate strict-stubbing.

Adds a nats_integration_test job to java-ci.yaml that runs
:rqueue-nats:test :rqueue-spring-boot-starter:test :rqueue-spring:test
with -DincludeTags=nats. Docker is preinstalled on ubuntu-latest, so
Testcontainers picks it up automatically and the JetStream ITs run
against nats:2.10-alpine -js. Coverage from this job is fed into the
existing coverage_report job.

Local verification: the same gradle invocation reports 13 tests run,
9 skipped (Docker-gated ITs without local Docker), 0 failures.

Assisted-By: Claude Code
Drops the @tag("integration") trailer from @NatsIntegrationTest meta and
NatsBackendEndToEndIT so the existing integration_test and
reactive_integration_test jobs (filtered by -DincludeTags=integration)
no longer try to run NATS tests on a Redis-shaped runner. NATS tests now
match only the dedicated nats_integration_test job's -DincludeTags=nats
filter.

This fixes the integration_test and reactive_integration_test job
failures that landed alongside the previous CI commit. The
nats_integration_test job is unchanged.

Assisted-By: Claude Code
The dashboard controller chain (RqueueRestController → RqueueDashboardChartServiceImpl
→ RqueueQStatsDaoImpl → RqueueConfig) currently still hard-requires a
RedisConnectionFactory bean even when rqueue.backend=nats, so excluding
DataRedisAutoConfiguration left the Spring context unable to load on the CI
runner.

Until those beans are made conditional on the broker's
usesPrimaryHandlerDispatch capability (tracked as a v1.x follow-up), this
e2e test starts an embedded Redis on a free port purely to satisfy the
bean graph. The actual produce-and-consume flow runs entirely through
JetStream — Redis is never touched by the message path.

Assisted-By: Claude Code
Adds three NATS-tagged Spring Boot end-to-end integration tests plus a
shared AbstractNatsBootIT base class that lifts the Testcontainers +
@DynamicPropertySource boilerplate.

- NatsConcurrencyE2EIT: proves @RqueueListener(concurrency="3") yields
  >1 parallel handler invocations on JetStream pull subscribers.
- NatsConsumerNameOverrideE2EIT: confirms an explicit consumerName
  attribute lands on the JetStream stream as a durable consumer with
  that exact name (queried via JetStreamManagement).
- NatsReactiveEnqueueE2EIT: enqueues 5 messages via
  ReactiveRqueueMessageEnqueuer (Flux.merge) and verifies a sync
  listener receives all 5; requires rqueue.reactive.enabled=true.

Assisted-By: Claude Code
- NatsPriorityQueuesE2EIT: a single @RqueueListener with
  priority="high=10,low=1" consumes 5 high + 5 low messages enqueued
  via RqueueMessageEnqueuer.enqueueWithPriority and we assert per-
  priority counts come out exact.
- NatsMultipleListenersOnSameQueueE2EIT: documents the desired
  fan-out semantics across two @RqueueListener methods on the same
  queue, but is @disabled because the default WorkQueue stream
  retention only allows a single filter-overlapping consumer; enable
  once the broker supports Limits/Interest retention per queue.

Assisted-By: Claude Code
sonus21 and others added 28 commits May 2, 2026 14:39
…rker registry

Rename natsConsumerName -> consumerName to make the concept broker-agnostic.
Introduce pollerKey (queue##consumer) in RqueueMessageListenerContainer to
prevent the second consumer of a multi-consumer queue being skipped by the
bare-name dedup check. Wire worker registry heartbeats and capacity-exhausted
tracking through consumerTrackingKey so each consumer is reported separately.
Add NATS stream retention policy and a 14-day maxAge default; wire the message
broker onto RqueueMessageTemplateImpl at container start for non-Redis publish
paths. Extract BaseListener/JobListener in the spring-boot example.

Assisted-By: Claude Code
rqueueMessageHandler now requires a MessageBroker argument (added to wire
primary-handler-dispatch capability). Update both test classes to supply a
mocked MessageBroker stubbed with REDIS_DEFAULTS capabilities.

Assisted-By: Claude Code
1. Default retention WorkQueue (was Limits)
   Streams must use WorkQueue retention so that acked messages are removed
   from the stream and broker.size() drains to 0. The peek IT already
   sets Limits explicitly so it is unaffected. Also update
   RqueueNatsProperties so the Spring Boot auto-config matches.

2. provisionDlq always creates the DLQ stream
   autoCreateDlqStream gates automatic bootstrap provisioning, not explicit
   calls. provisionDlq() is opt-in by the caller; guard it with ensureStream
   directly instead of ensureDlqStream, which also checked the flag.

3. Priority stream names use underscore suffix (PriorityUtils convention)
   streamFor/subjectFor(q, priority) used a dash separator (pq-high) but
   the poller's expanded QueueDetail.name uses the PriorityUtils suffix
   (pq_high). Align both to PriorityUtils.getSuffix() so enqueue and pop
   target the same stream. Update NatsStreamValidator's priority loop and
   the unit test assertion accordingly.

Assisted-By: Claude Code
Default stream retention stays Limits (broker-agnostic default).
The enqueuePopAck_drainsStream test requires WorkQueue semantics so
it now sets the policy explicitly on its own config, matching the
pattern already used by JetStreamMessageBrokerPeekIT.

Assisted-By: Claude Code
Each priority sub-queue (pq_high, pq_low) is registered as its own
QueueDetail in EndpointRegistry and processed as a mainStream entry,
which creates exactly one consumer per stream. The priority loop that
runs for the base queue (pq) was calling tryEnsureConsumer a second
time on those same streams, which fails with error 10099 on WorkQueue
streams ("multiple non-filtered consumers not allowed"). Remove the
consumer creation from the priority loop; stream ensure is sufficient
and safe (idempotent via the provisioner cache).

Assisted-By: Claude Code
… provisioning

- Add QueueType enum (QUEUE = WorkQueue retention, STREAM = Limits/fan-out)
- Add queueMode() attribute to @RqueueListener, default QUEUE
- Thread QueueType through MappingInformation → QueueDetail builder
- MessageBroker.onQueueRegistered() hook; JetStreamMessageBroker overrides it
  to provision the stream immediately on registerQueue() calls (producer-only mode)
- NatsProvisioner.ensureStream(name, subjects, QueueType) picks WorkQueue vs
  Limits retention; warns and preserves existing config on mismatch
- NatsStreamValidator and JetStreamMessageBroker pass q.getType() to provisioner
- RqueueRedisConfigImportSelector: lazy-load RqueueRedisListenerConfig via
  ImportSelector so excluding rqueue-redis no longer crashes Spring at startup
- Fix JetStreamMessageBrokerPeekIT and IndependentConsumersIT to use QueueType
  instead of the now-bypassed setRetention() config workaround
- Add JetStreamQueueModeIT: 5 E2E contracts covering stream retention, consumer
  reuse/position preservation, competing-consumer delivery, and fan-out delivery

Assisted-By: Claude Code
…d version bump

- RqueueEndpointManager: add registerQueue(name, QueueType, priorities) overload; old
  no-arg signature preserved as default method for backward compat
- RqueueEndpointManagerImpl: implements new QueueType-aware registration
- GenericMessageConverter: add constructor accepting a custom ObjectMapper
- JetStreamMessageBrokerProducerOnlyIT: refactor to domain event POJOs
- build.gradle: bump version to 4.0.0-SK1

Assisted-By: Claude Code
…eSender

- BaseMessageSender, RqueueEndpointManagerImpl, RqueueMessageEnqueuerImpl,
  RqueueMessageManagerImpl, ReactiveRqueueMessageEnqueuerImpl now take
  MessageBroker as a constructor arg instead of reading it off the template.
- enqueue/storeMessageMetadata/notifyBrokerQueueRegistered route through the
  injected broker unconditionally; the Redis-vs-NATS dispatch lives inside
  each broker implementation.
- RedisMessageBroker overrides enqueueReactive/enqueueWithDelayReactive so
  reactive callers stay on the reactive Redis driver.
- RqueueMessageTemplateImpl no longer holds a MessageBroker; the
  setMessageBroker wiring in RqueueListenerAutoConfig / RqueueListenerConfig /
  RqueueMessageListenerContainer is removed in favor of injecting the broker
  bean directly into the *Impl beans.
- Tests updated to construct *Impl beans with an explicit broker; new
  RqueueMessageEnqueuerBrokerRoutingTest pins the non-reactive routing path.

Assisted-By: Claude Code
- MessageBroker SPI: new default no-op validateQueueName(String) hook.
- JetStreamMessageBroker overrides it to reject queue names containing
  '.', '*', '>' or whitespace. NATS subjects use '.' as a hierarchy
  separator and stream / consumer names disallow it outright, so a
  silent accept turned into an opaque driver-side rejection at first
  publish; now it fails loudly at registration with a message that
  points users at '-' / '_'.
- BaseMessageSender.registerQueueInternal and
  RqueueMessageListenerContainer.initializeQueueRegistry both call
  validateQueueName, so both the explicit RqueueEndpointManager.registerQueue
  path and the @RqueueListener bootstrap path validate.
- NatsProvisioner.ensureStream gains a description overload; the broker
  passes "rqueue queue: <name>" (and "(priority=<p>)" / "rqueue DLQ for
  queue: <name>" for the priority and DLQ variants) so operators can map
  a stream back to the queue that created it via `nats stream info`.
- NatsStreamValidator passes the same description on the bootstrap path.

Tests: JetStreamMessageBrokerQueueNameValidationTest pins the
character-rejection rules; JetStreamMessageBrokerStreamDescriptionTest
pins the description format on enqueue / onQueueRegistered / priority
enqueue paths; RqueueEndpointManagerImplTest gains a regression covering
broker-validation propagation through registerQueue.

Assisted-By: Claude Code
Map QueueDetail.visibilityTimeout to JetStream ackWait and numRetry to
maxDeliver (numRetry + 1, with Integer.MAX_VALUE as the unlimited
sentinel) when the durable pull consumer is provisioned. Falls back to
RqueueNatsConfig.consumerDefaults when the per-queue value is unset.
Resolution lives in JetStreamMessageBroker.resolveAckWait /
resolveMaxDeliver and is used by both popInternal and the bootstrap
NatsStreamValidator so the consumer is created with the right config
upfront (provisioner only logs a warning on existing-config drift).

Also drops dead, never-set NATS-related fields from QueueDetail
(natsStream, natsSubject, natsDlqStream, natsDlqSubject,
natsMaxDeliverOverride, natsDedupWindow) and their resolved* helpers,
plus the corresponding test. Broker DLQ name helpers now compute
directly from prefix + suffix.

Assisted-By: Claude Code
…weep

- RqueueMessagePoller now forwards the configured pollingInterval to
  MessageBroker.pop instead of Duration.ZERO, letting JetStream long-poll
  rather than spinning $JS.API.CONSUMER.MSG.NEXT requests at full speed.
  Covered by a new test in RqueueMessageListenerContainerBrokerBranchTest.
- RqueueNatsAutoConfig wires RqueueConfig into NatsStreamValidator so
  producer-only deployments skip durable consumer creation.
- StringUtils-based null/empty checks in RqueueNatsAutoConfig.
- Copyright headers normalized to 2026 across the NATS-touched files.

Assisted-By: Claude Code
Fixed javadoc compilation warnings by:
- Removing @link tags for cross-module references that aren't resolvable
  during module-specific javadoc generation (RqueueListenerConfig refs)
- Converting method-level @link references to inline code snippets
  (RqueueWorkerInfo#getWorkerId, ConsumerDefaults methods)
- Using code font formatting for methods not visible to javadoc compiler

All javadoc generation for core, redis, and nats modules now passes
without warnings.

Assisted-By: Claude Code
Added or improved class-level documentation for:

- RqueueMessage: Detailed explanation of message envelope structure,
  timing/scheduling fields, failure tracking, and periodic task support.

- QueueDetail: Configuration metadata for listener behavior including
  polling, error handling, dead-letter queues, and priority sub-queues.

- RqueueMessageHandler: Internal handler for @RqueueListener method
  invocation, exception routing, and type-safe argument injection.

Assisted-By: Claude Code
…flict

The test was configuring both redisConnectionFactory and a non-Redis MessageBroker,
which violates the validation in SimpleRqueueListenerContainerFactory that ensures
exactly one transport is configured. Fixed by setting the messageBroker directly
instead of the Redis connection factory when testing with a non-Redis broker.

Assisted-By: Claude Code
@sonus21 sonus21 merged commit acad531 into master May 2, 2026
@sonus21 sonus21 deleted the nats-backend branch May 3, 2026 04:30
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.

1 participant