nats-io/nats-server · error · ApiError (JSStreamTransformInvalidSource)

10155

10155

Error message

%w %s

What it means

The stream's SubjectTransform.Source is not a valid NATS subject, so the transform cannot be applied. The error wraps ErrBadSubject together with the offending source string. NATS validates the transform's source strictly because republished/transformed messages depend on a well-formed subject to rewrite into the destination.

Source

Thrown at server/stream.go:2421

		}
	}

	if len(cfg.Subjects) == 0 && len(cfg.Sources) == 0 && cfg.Mirror == nil {
		return StreamConfig{}, NewJSStreamInvalidConfigError(
			fmt.Errorf("stream needs at least one configured subject or be a source/mirror"))
	}

	// Check for MaxBytes required and it's limit
	if required, limit := acc.maxBytesLimits(&cfg); required && cfg.MaxBytes <= 0 {
		return StreamConfig{}, NewJSStreamMaxBytesRequiredError()
	} else if limit > 0 && cfg.MaxBytes > limit {
		return StreamConfig{}, NewJSStreamMaxStreamBytesExceededError()
	}

	// Check the subject transform if any
	if cfg.SubjectTransform != nil {
		if cfg.SubjectTransform.Source != _EMPTY_ && !IsValidSubject(cfg.SubjectTransform.Source) {
			return StreamConfig{}, NewJSStreamTransformInvalidSourceError(fmt.Errorf("%w %s", ErrBadSubject, cfg.SubjectTransform.Source))
		}

		err := ValidateMapping(cfg.SubjectTransform.Source, cfg.SubjectTransform.Destination)
		if err != nil {
			return StreamConfig{}, NewJSStreamTransformInvalidDestinationError(err)
		}
	}

	// If we have a republish directive check if we can create a transform here.
	if cfg.RePublish != nil {
		// Check to make sure source is a valid subset of the subjects we have.
		// Also make sure it does not form a cycle.
		// Empty same as all.
		if cfg.RePublish.Source == _EMPTY_ {
			if pedantic {
				return StreamConfig{}, NewJSPedanticError(fmt.Errorf("republish source can not be empty"))
			}
			cfg.RePublish.Source = fwcs

View on GitHub (pinned to 3a66a489d2)

Solutions

  1. Fix cfg.SubjectTransform.Source to a valid NATS subject (letters, digits, '_' '-' '/', '*' per token, '>' only as last token).
  2. Validate the string with nats.IsValidSubject (or server.IsValidSubject) before submitting the config.
  3. Trim whitespace or resolve unsubstituted template variables before calling AddStream.

Example fix

// before
js.AddStream(&nats.StreamConfig{Name: "s", Subjects: []string{"events.>"}, SubjectTransform: &nats.SubjectTransform{Source: "events .>"}})
// after
js.AddStream(&nats.StreamConfig{Name: "s", Subjects: []string{"events.>"}, SubjectTransform: &nats.SubjectTransform{Source: "events.>", Destination: "archive.events.>"}})
Defensive patterns

Strategy: validation

Validate before calling

func validTransformSource(t *nats.SubjectTransform) error {
  if t == nil { return nil }
  if t.Source == "" { return nil }
  if !isValidSubject(t.Source) {
    return fmt.Errorf("transform source %q is not a valid subject", t.Source)
  }
  return nil
}

Type guard

func hasValidTransformSource(t *nats.SubjectTransform) bool {
  return t == nil || t.Source == "" || isValidSubject(t.Source)
}

Try / catch

var jsErr *nats.JSApiError
if _, err := js.AddStream(cfg); err != nil {
  if errors.As(err, &jsErr) && errors.Is(err, nats.ErrBadSubject) || strings.Contains(err.Error(), "bad subject") {
    log.Printf("fix SubjectTransform.Source: %v", err)
  }
}

Prevention

When it happens

Trigger: StreamConfig.Add/Update with cfg.SubjectTransform = &nats.SubjectTransform{Source: "not a subject"} — strings with spaces, empty source, or illegal wildcard placement (e.g. 'foo.**' or a token like 'foo.>'). Check runs whenever SubjectTransform != nil and Source is non-empty.

Common situations: User-supplied transform configuration interpolated into StreamConfig without validation; template variables not substituted (e.g. Source: "{{subject}}"); trailing whitespace or typos in config files.

Related errors


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