nats-io/nats-server · error
gsl: notification already registered
Error message
gsl: notification already registered
What it means
This error is returned by the GSL (generic sublist) notification registry when the same notification channel is registered more than once for the same subject subscription interest. addNotify scans the existing channel list and returns ErrAlreadyRegistered if the exact channel is already present. It is a duplicate-registration guard, not a network or persistence failure.
Source
Thrown at server/gsl/gsl.go:43
// match multiple published subjects.
// Common byte variables for wildcards and token separator.
const (
pwc = '*'
pwcs = "*"
fwc = '>'
fwcs = ">"
tsep = "."
btsep = '.'
_EMPTY_ = ""
)
// Sublist related errors
var (
ErrInvalidSubject = errors.New("gsl: invalid subject")
ErrNotFound = errors.New("gsl: no matches found")
ErrNilChan = errors.New("gsl: nil channel")
ErrAlreadyRegistered = errors.New("gsl: notification already registered")
)
// SimpleSublist is an alias type for GenericSublist that takes
// empty values, useful for tracking interest only without any
// unnecessary allocations.
type SimpleSublist = GenericSublist[struct{}]
// NewSimpleSublist will create a simple sublist.
func NewSimpleSublist() *SimpleSublist {
return &GenericSublist[struct{}]{root: newLevel[struct{}]()}
}
// A GenericSublist stores and efficiently retrieves subscriptions.
type GenericSublist[T comparable] struct {
sync.RWMutex
root *level[T]
count uint32
}View on GitHub (pinned to 3a66a489d2)
Solutions
- Check whether the channel is already registered before calling the registration API, or treat ErrAlreadyRegistered as idempotent success.
- Unregister the existing notification channel (removeNotify) before registering it again.
- Create a fresh channel for each registration instead of reusing one channel instance.
- Refactor the caller so registration happens exactly once per lifecycle (e.g. guard with sync.Once).
Example fix
// before
ch := make(chan struct{}, 1)
sl.RegisterNotification(sub, ch)
sl.RegisterNotification(sub, ch) // gsl: notification already registered
// after
ch := make(chan struct{}, 1)
if err := sl.RegisterNotification(sub, ch); err != nil && !errors.Is(err, gsl.ErrAlreadyRegistered) {
return err
} Defensive patterns
Strategy: validation
Validate before calling
func alreadyRegistered(chs []chan struct{}, ch chan struct{}) bool {
for _, c := range chs {
if c == ch { return true }
}
return false
}
// call register only if !alreadyRegistered(...)
Type guard
func isAlreadyRegistered(err error) bool {
return err != nil && strings.Contains(err.Error(), "notification already registered")
} Try / catch
if err := sl.RegisterNotification(sub, ch); err != nil && !isAlreadyRegistered(err) {
return fmt.Errorf("register notification: %w", err)
}
// idempotent: ignore duplicate registration Prevention
- Register each notification channel exactly once per subject (guard with sync.Once or a set)
- Unregister the channel before re-registering
- Treat ErrAlreadyRegistered as idempotent success in wrapper APIs
- Use a fresh channel per registration instead of reusing instances
When it happens
Trigger: Calling addNotify (via sublist notification registration APIs) on a GenericSublist where the same channel instance is already registered for the subject; the equality check in the for-range loop over existing channels matches and returns ErrAlreadyRegistered.
Common situations: Re-initializing a server component without unregistering the previous notifier; double-subscribe logic in wrappers that call RegisterNotification twice with the same channel; retrying a registration call after a timeout without removing the prior channel.
Related errors
- account exists
- gsl: invalid subject
- gsl: no matches found
- gsl: nil channel
- error creating store for stream
AI-assisted analysis of nats-io/nats-server@3a66a489d2 (2026-09-02).
Data as JSON: /api/errors/d2134da58bfbb523.
Report an issue: GitHub.