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

  1. Check whether the channel is already registered before calling the registration API, or treat ErrAlreadyRegistered as idempotent success.
  2. Unregister the existing notification channel (removeNotify) before registering it again.
  3. Create a fresh channel for each registration instead of reusing one channel instance.
  4. 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

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


AI-assisted analysis of nats-io/nats-server@3a66a489d2 (2026-09-02). Data as JSON: /api/errors/d2134da58bfbb523. Report an issue: GitHub.