nats-io/nats-server · error

subject transform from '%s' to '%s' for the mirror: %w

Error message

subject transform from '%s' to '%s' for the mirror: %w

What it means

When a mirror's subject transform has a destination, NewSubjectTransform validates source/destination compatibility (token counts, wildcards); this error wraps the underlying transform error, so the mirror config is rejected.

Source

Thrown at server/stream.go:951

	// If mirror, check if the transforms (if any) are valid.
	if cfg.Mirror != nil {
		if len(cfg.Mirror.SubjectTransforms) == 0 {
			if cfg.Mirror.FilterSubject != _EMPTY_ && !IsValidSubject(cfg.Mirror.FilterSubject) {
				jsa.mu.Unlock()
				return nil, fmt.Errorf("subject filter '%s' for the mirror %w", cfg.Mirror.FilterSubject, ErrBadSubject)
			}
		} else {
			for _, st := range cfg.Mirror.SubjectTransforms {
				if st.Source != _EMPTY_ && !IsValidSubject(st.Source) {
					jsa.mu.Unlock()
					return nil, fmt.Errorf("invalid subject transform source '%s' for the mirror: %w", st.Source, ErrBadSubject)
				}
				// check the transform, if any, is valid
				if st.Destination != _EMPTY_ {
					if _, err = NewSubjectTransform(st.Source, st.Destination); err != nil {
						jsa.mu.Unlock()
						return nil, fmt.Errorf("subject transform from '%s' to '%s' for the mirror: %w", st.Source, st.Destination, err)
					}
				}
			}
		}
	}

	// Setup our internal indexed names here for sources and check if the transforms (if any) are valid.
	for _, ssi := range cfg.Sources {
		if len(ssi.SubjectTransforms) == 0 {
			// check the filter, if any, is valid
			if ssi.FilterSubject != _EMPTY_ && !IsValidSubject(ssi.FilterSubject) {
				jsa.mu.Unlock()
				return nil, fmt.Errorf("subject filter '%s' for the source: %w", ssi.FilterSubject, ErrBadSubject)
			}
		} else {
			for _, st := range ssi.SubjectTransforms {
				if st.Source != _EMPTY_ && !IsValidSubject(st.Source) {
					jsa.mu.Unlock()

View on GitHub (pinned to 3a66a489d2)

Solutions

  1. Ensure the destination is structurally compatible with the source (matching token counts, wildcards only where the source has them).
  2. Test the transform with NewSubjectTransform locally to see the underlying reason.
  3. Remove the destination (empty) if no transform is actually wanted.

Example fix

// before
{Source: "foo.*", Destination: "bar.*.extra"}
// after
{Source: "foo.*", Destination: "bar.*"}
Defensive patterns

Strategy: validation

Validate before calling

for _, st := range cfg.Mirror.SubjectTransforms {
  if st.Destination != "" {
    if _, err := server.NewSubjectTransform(st.Source, st.Destination); err != nil { return err }
  }
}

Try / catch

_, err := js.AddStream(cfg)
if err != nil && strings.Contains(err.Error(), "subject transform from") { /* adjust source/destination token structure */ }

Prevention

When it happens

Trigger: AddStream/UpdateStream with a mirror SubjectTransforms entry whose Destination does not compose with its Source (e.g. more tokens in destination, illegal wildcard mapping).

Common situations: Writing transforms like source "foo.*" to destination "bar.*.extra", or destination containing wildcards the server cannot map from the source.

Related errors


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