Endpoints and groups
The previous page gave you a running OrderInventory service with exactly
one endpoint, check, answering on orders.inventory.check. That's the
smallest useful shape: one service, one handler, one subject.
Real services rarely stay that small. A service usually answers more than one kind of request, and once it does you want those subjects organized rather than scattered. This page adds the two pieces that do that: a service can hold multiple endpoints, and a group gives a set of endpoints a shared subject prefix.
By the end you'll have a second service, ShippingQuote, alongside
OrderInventory, and you'll know how to lay out several endpoints under
one prefix.
A service can hold multiple endpoints
An endpoint is a named handler on a subject. You met one on the last
page; nothing stops a service from having several. Each call to
AddEndpoint(name, handler) registers another named handler, and the
endpoint's subject defaults to its name.
Add a second service the same way you added the first. ShippingQuote
promotes the Core NATS shipping.quote providers into a named service. It
has one endpoint, quote, and because you want it to answer on
shipping.quote rather than on the bare name quote, you set the subject
explicitly:
- CLI
#!/bin/bash
# Add a second service alongside OrderInventory.
#
# A service is created by your client library, not by the CLI: the
# library call is AddService(Name: "ShippingQuote", Version: "1.0.0")
# with one endpoint named "quote" whose subject is set to
# "shipping.quote". The CLI does not create endpoints; it talks to the
# services your code runs. This snippet shows the second service once it
# is running, the way every other language tab creates it.
# With ShippingQuote running, send a request to its endpoint. The
# service answers on shipping.quote, independent of OrderInventory.
nats service request ShippingQuote quote \
'{"order_id":"ord_8w2k","customer":"acme-co","total_cents":4200,"ts":"2026-05-22T10:14:22Z"}'
# Confirm both services are now visible. Two independent services run
# against the same nats-server, each with its own name, version, and
# auto-generated service ID.
nats service list
Two services now run against the same nats-server. OrderInventory
answers on orders.inventory.check; ShippingQuote answers on
shipping.quote. Each is independent, with its own name, its own version,
and its own discovery. The framework gave each a unique service ID without
you asking.
When the subject should match the endpoint name, you can omit it. An
endpoint named quote with no subject set answers on quote. You set the
subject explicitly only when the wire subject and the endpoint name differ,
which is the common case once subjects carry structure like
orders.inventory.check.
The handler contract is unchanged from the last page: you read the request
with req.Data() and reply with req.Respond(). Adding endpoints doesn't
change how a single request
is served; it only adds more named handlers to the same running service.
A group is a subject prefix
Once a service grows past one endpoint, the subjects start to repeat. Two
inventory endpoints would naturally be orders.inventory.check and
orders.inventory.reserve: the same orders.inventory stem, twice. A
group captures that stem once.
AddGroup("orders.inventory") returns a group, and any endpoint you add
to that group answers on {group}.{endpoint}. An endpoint named check
inside the orders.inventory group answers on orders.inventory.check;
an endpoint named reserve answers on orders.inventory.reserve. You
write the prefix once and the framework joins it to each endpoint name.
- CLI
#!/bin/bash
# Organize endpoints under a group subject prefix.
#
# Grouping is a library call: AddGroup("orders.inventory") returns a
# group, and an endpoint named "check" added to that group answers on
# {group}.{endpoint} = orders.inventory.check. The CLI does not build
# groups; it sends to the subject the group produced. This snippet shows
# that the grouped endpoint answers on the joined subject, exactly as the
# other language tabs construct it.
# The grouped "check" endpoint answers on orders.inventory.check — the
# group prefix joined to the endpoint name. A caller only needs the
# subject; it never sees whether a group built it.
nats service request OrderInventory check \
'{"order_id":"ord_8w2k","customer":"acme-co","total_cents":4200,"ts":"2026-05-22T10:14:22Z"}'
# Read back the service to see each endpoint's resolved subject. The
# subject column shows orders.inventory.check, the group prefix applied.
nats service info OrderInventory
The subject a caller sends to is always {group}.{endpoint}. There's no
separate routing layer; the group is just a way to build the subject. A
client that knows the subject doesn't need to know whether the endpoint
was added directly or through a group. The wire looks identical either way.
Groups also nest. A group added inside another group combines both
prefixes, so the subject becomes {outer}.{inner}.{endpoint}. You rarely
need more than one level for a service this size, but the prefixes stack
the way you'd expect.
A group can set the queue group
Every endpoint joins a queue group, the load-balancing group that
makes the server deliver each request to exactly one member. The default
name is "q", and you've been using it without touching it: all
instances of OrderInventory share "q", so a request goes to one of
them, not all of them. The queue group mechanism itself lives in
Core NATS; here it's just the default a
service endpoint already uses.
The queue group is set at three levels, and each level overrides the one
above it. The service sets a default. A group can override it for every
endpoint under it. A single endpoint can override it again. If none of
them set anything, the endpoint falls back to "q".
You override the queue group on an endpoint with one option. Here check
joins a custom queue group instead of the default "q":
- CLI
#!/bin/bash
# Override the queue group on one endpoint.
#
# The queue group is a library setting on the endpoint: the option
# WithEndpointQueueGroup("q-inventory") makes "check" load-balance in
# its own group instead of the service default "q". The CLI does not set
# queue groups; it sends requests that the server delivers to one member
# of whatever group the endpoint joined. This snippet shows the observable
# result, the way the other language tabs configure the override.
# Send a request to the check endpoint. Whether check uses the default
# "q" or a custom queue group, the caller behavior is identical: the
# server delivers the request to exactly one instance in that group and
# one reply comes back.
nats service request OrderInventory check \
'{"order_id":"ord_8w2k","customer":"acme-co","total_cents":4200,"ts":"2026-05-22T10:14:22Z"}'
# Inspect the endpoint to confirm its queue group. The info output lists
# each endpoint with its subject and queue group, so you can verify the
# override took effect.
nats service info OrderInventory
Most services never need this. The default "q" already load-balances all
instances of a service against each other, which is what you want almost
every time. Reach for an override only when you want a subset of endpoints
to load-balance separately. The full set of endpoint and group options is
documented in Reference. We only need the behavior here.
The animation shows one service with two endpoints. A request to
orders.inventory.check lights the check endpoint; a request to
shipping.quote lights the grouped endpoint. It's the same service on the
same connection, with two named handlers chosen by subject.
Pitfalls
Endpoints and groups have two common pitfalls: one about the queue group, and one about what can't be undone.
Disabling the queue group turns an endpoint into broadcast. Overriding
the queue group changes who load-balances with whom. Disabling it
entirely is a sharper change: an endpoint with no queue group is a plain
subscription, so every instance receives every request instead of
one instance receiving each. For a request-reply endpoint that means the
caller gets one reply per instance and the rest are noise. Do not disable
the queue group to "make sure a request is handled": that's exactly what
the default "q" already guarantees, with one handler, not N. Disable it
only when you genuinely want all instances to act, which is rare for a
service that responds.
You control this with one option. Override the queue group when you want a
subset of endpoints to load-balance separately; never disable it on an
endpoint that responds. Send a request and inspect the endpoint to confirm
which queue group it joined (the default "q", an override, or none):
- CLI
#!/bin/bash
# Override the queue group on one endpoint.
#
# The queue group is a library setting on the endpoint: the option
# WithEndpointQueueGroup("q-inventory") makes "check" load-balance in
# its own group instead of the service default "q". The CLI does not set
# queue groups; it sends requests that the server delivers to one member
# of whatever group the endpoint joined. This snippet shows the observable
# result, the way the other language tabs configure the override.
# Send a request to the check endpoint. Whether check uses the default
# "q" or a custom queue group, the caller behavior is identical: the
# server delivers the request to exactly one instance in that group and
# one reply comes back.
nats service request OrderInventory check \
'{"order_id":"ord_8w2k","customer":"acme-co","total_cents":4200,"ts":"2026-05-22T10:14:22Z"}'
# Inspect the endpoint to confirm its queue group. The info output lists
# each endpoint with its subject and queue group, so you can verify the
# override took effect.
nats service info OrderInventory
Endpoints are immutable once added. There's no remove. You can't detach an endpoint, rename it, or change its subject on a running service. The same holds for the metadata you attach to a service or endpoint: it's fixed at creation. If a service's shape needs to change, you stop it and start a new one with the new layout. Decide the endpoint names and subjects before the service goes live, deliberately and the first time, the same way you pick a stream name in JetStream.
You handle this by inspecting the shape, not editing it. Read back the running service to see exactly which endpoints, subjects, and queue groups it registered; to change any of them, stop the service and start a replacement:
- CLI
#!/bin/bash
# Endpoints are fixed once a service is running — inspect, then restart to change.
#
# There is no library call to remove, rename, or re-subject an endpoint:
# AddEndpoint only appends, and the subject, queue group, and metadata are
# set at creation. The CLI cannot detach an endpoint either; it can only
# read the shape your code registered. This snippet shows how to inspect
# the running layout and confirms there is no remove operation — the way
# every language tab treats endpoints as immutable.
# Read back the service to see the exact endpoints it registered: each name,
# its resolved subject, and its queue group. This is the layout your code
# fixed at AddEndpoint time; nothing here can be edited in place.
nats service info OrderInventory
# There is no "nats service remove-endpoint" — endpoints cannot be detached
# from a running service. To change the shape, stop the service in your code
# (svc.Stop()) and start a new one with the new endpoint names and subjects.
nats service list
Where you are
You now have:
- Two services running against one
nats-server:OrderInventoryanswering onorders.inventory.check, andShippingQuoteanswering onshipping.quote. - The shape to add more endpoints to either, with subjects that default to the endpoint name.
- A group to give related endpoints a shared subject prefix, and the three levels at which a queue group can be set.
Both services still do exactly what their Core NATS responders did. They've only gained structure: names, subjects, and the framework's load-balancing default underneath.
What's next
Two services are running, each with its own name and endpoints. The next
page asks the server what's out there: the $SRV discovery verbs let any
client learn which services exist, what endpoints they expose, and which
instances are answering.
Continue to Discovery.
See also
- Queue groups — how the load-balancing group under every endpoint actually works.
- Scatter-gather — the request pattern
the
ShippingQuoteproviders used before becoming a named service. - Reference — the full set of endpoint and group configuration options.