Skip to main content

Request-Reply

Request-Reply is a communication pattern that brings synchronous communication to NATS's asynchronous messaging system. It allows a client to send a request and wait for a response, building RPC-style interactions on top of the core publish-subscribe mechanism.

How Request-Reply Works

Under the hood, request-reply uses NATS's publish-subscribe with these steps:

  1. Client creates a unique reply subject (inbox)
  2. Client subscribes to the reply subject
  3. Client publishes the request with the reply subject
  4. Service receives the request and sees the reply subject
  5. Service publishes the response to the reply subject
  6. Client receives the response

This pattern is so common that NATS clients provide a simplified request() method that handles all these steps automatically.

In the animation above:

  • The orange arrow shows the request flowing from client to service
  • The green dashed arrow shows the reply flowing back
  • This demonstrates the bidirectional, synchronous nature of request-reply

Basic Request-Reply

# Terminal 1: Set up a service that responds to time requests
nats reply time 'echo "The time is $(date)"'

# Terminal 2: Make a request
nats request time ""

# Output: The time is Wed Nov 15 10:23:45 PST 2023

Handling Timeouts

Timeouts are crucial in request-reply to prevent indefinite waiting. All NATS clients support configurable timeouts:

# Request with 2 second timeout
nats request service "" --timeout 2s

# If no response within 2 seconds, returns error

Multiple Responders

When multiple services subscribe to the same request subject, NATS supports two distinct patterns depending on whether queue groups are used:

Pattern 1: All Services Respond (Scatter-Gather)

If each app creates a "service" subscription, all of them will receive the request and all can respond. The client can collect multiple responses:

In this pattern, one request is broadcast to all three services (A, B, C), and all three send responses back. This is useful for:

  • Gathering data from multiple sources
  • Aggregating results from distributed services
  • Querying multiple replicas for consensus

Pattern 2: One Service Responds (Load Balancing)

With queue groups, only one service receives the request and responds, providing automatic load balancing for scalability:

In this pattern, NATS selects one service from the queue group (Service B in this example) to handle the request. This provides:

  • Automatic load distribution across service instances
  • Horizontal scalability
  • Built-in failover (if one service is down, another handles it)

By default, the request() method returns after receiving the first response. To collect multiple responses from the scatter-gather pattern, use manual inbox subscription:

# Terminal 1: First service
nats reply service 'echo "Response from service 1"'

# Terminal 2: Second service
nats reply service 'echo "Response from service 2"'

# Terminal 3: Make request (receives one random response)
nats request service ""

No Responders Detection

NATS will detect when no services are available to handle a request. When there are no subscribers for the request subject, NATS server will return a "no responders" error immediately:

#!/bin/bash

# Request to a subject with no subscribers
nats request no.such.service "test"

# Error: no responders available for request

Request with Headers

NATS supports headers in request-reply, enabling metadata exchange:

#!/bin/bash

# Send request with headers
nats request service "data" -H "X-Request-ID:123" -H "X-Priority:high"

Best Practices

Timeout Strategy

  • Set appropriate timeouts: Too short may miss valid responses, too long blocks unnecessarily
  • Consider network latency: Add buffer for network round-trip time
  • Implement retry logic: For transient failures

Error Handling

  • Always handle timeouts: Don't assume responses will arrive
  • Check for no responders: React appropriately when services are unavailable
  • Validate responses: Ensure response data is valid before processing

Service Design

  • Idempotent operations: Requests might be retried
  • Lightweight processing: Long operations should be async
  • Health checks: Implement service health endpoints
  • Graceful shutdown: Drain pending requests before shutting down

Performance

  • Connection pooling: Reuse connections for multiple requests
  • Inbox reuse: Modern clients reuse inbox subjects for efficiency
  • Batch operations: Group related requests when possible

Request-Reply vs Publish-Subscribe

Choose request-reply when you need:

  • Synchronous communication: Wait for a response before continuing
  • Service discovery: Automatic "no responders" detection
  • Load balancing: Combined with queue groups
  • RPC-style APIs: Traditional request-response patterns

Use publish-subscribe when you need:

  • Fire-and-forget: No response needed
  • Multiple consumers: All subscribers should receive the message
  • Event streaming: Continuous flow of events
  • Decoupled systems: Publishers shouldn't know about subscribers

Try It Yourself

Create a simple calculator service:

#!/bin/bash

# Terminal 1: Calculator service
nats reply calc.add '
read input
echo "$input" | awk "{print \$1 + \$2}"
'

# Terminal 2: Make calculations
echo "5 3" | nats request calc.add -
echo "10 7" | nats request calc.add -