nats-io/nats-server · error · JSStreamRollupFailedError
JS_STREAM_ROLLUP_FAILED
JS_STREAM_ROLLUP_FAILED
Error message
rollup not permitted
What it means
checkMsgHeadersPreClusteredProposal validates rollup headers on batched publishes. A rollup (Nats-Rollup: sub|all) is only allowed when the stream permits rollups (allowRollup, purge not denied, non-sourced msgs) and the subject hasn't already been rolled up in this batch; otherwise JS_STREAM_ROLLUP_FAILED 'rollup not permitted' is returned.
Source
Thrown at server/jetstream_batching.go:929
var smv StoreMsg
sm, _ := mset.store.LoadLastMsg(schedSubj, &smv)
invalid = sm != nil && len(sliceHeader(JSSchedulePattern, sm.hdr)) == 0
}
if invalid {
apiErr := NewJSMessageSchedulesSchedulerInvalidError()
return hdr, msg, 0, apiErr, apiErr
}
}
} else if !sourced && len(sliceHeader(JSScheduler, hdr)) > 0 {
// Clients may only use Nats-Scheduler alongside Nats-Schedule-Next.
apiErr := NewJSMessageSchedulesSchedulerInvalidError()
return hdr, msg, 0, apiErr, apiErr
}
// Check for any rollups.
if rollup := getRollup(hdr); rollup != _EMPTY_ {
if (!allowRollup || denyPurge) && !sourced {
err := errors.New("rollup not permitted")
return hdr, msg, 0, NewJSStreamRollupFailedError(err), err
}
switch rollup {
case JSMsgRollupSubject:
// Rolling up the subject is only allowed if the first occurrence of this subject in the batch.
if _, ok := diff.inflight[subject]; ok {
err := errors.New("batch rollup sub invalid")
return hdr, msg, 0, NewJSStreamRollupFailedError(err), err
}
case JSMsgRollupAll:
// Rolling up the whole stream is only allowed if this is the first message of the batch.
if len(diff.inflight) > 0 {
err := errors.New("batch rollup all invalid")
return hdr, msg, 0, NewJSStreamRollupFailedError(err), err
}
default:
err := fmt.Errorf("rollup value invalid: %q", rollup)
return hdr, msg, 0, NewJSStreamRollupFailedError(err), errView on GitHub (pinned to 3a66a489d2)
Solutions
- Enable rollups on the stream: `nats stream update ORDERS --allow-rollup` or set AllowRollup: true in StreamConfig.
- Remove the Nats-Rollup header from messages published to streams that disallow rollups.
- Send at most one rollup per subject per batch; split further rollups into later batches.
- Check DenyPurge on the stream — rollups are denied when purging is denied unless the message is sourced.
Example fix
// before: stream without rollup permission
streamCfg := &nats.StreamConfig{Name: "ORDERS"} // AllowRollup defaults false
js.PublishMsg(&nats.Msg{Subject: "orders.new", Header: nats.Header{"Nats-Rollup": []string{"sub"}}, Data: data})
// -> JS_STREAM_ROLLUP_FAILED rollup not permitted
// after
streamCfg.AllowRollup = true
js.UpdateStream(streamCfg)
js.PublishMsg(&nats.Msg{Subject: "orders.new", Header: nats.Header{"Nats-Rollup": []string{"sub"}}, Data: data}) Defensive patterns
Strategy: validation
Validate before calling
si, err := js.StreamInfo("ORDERS")
if err == nil && !si.Config.AllowRollup {
return fmt.Errorf("stream ORDERS disallows rollup; enable AllowRollup first")
} Try / catch
_, err := js.PublishMsg(&nats.Msg{Subject: subj, Header: rollupHdr, Data: data})
var apiErr *nats.APIError
if errors.As(err, &apiErr) && apiErr.ErrorCode == nats.JSStreamRollupFailed {
// enable rollup on the stream or republish without the header
} Prevention
- Enable AllowRollup on streams that receive rollup headers; check DenyPurge too.
- Send at most one rollup per subject per batch.
- Document which streams accept rollups and gate publishers accordingly.
- Re-verify rollup policies after stream recreation or mirroring.
When it happens
Trigger: Publishing a rollup message to a stream with AllowRollup=false or DenyPurge=true; a second rollup for the same subject within one batch (diff.inflight collision).
Common situations: Streams created before rollup support or explicitly configured AllowRollup:false receiving rollup headers; compaction/aggregation jobs sending repeated rollups per subject in one batch; mirrored/restored streams with different policies.
Related errors
- roll-ups require the purge permission
- JS_STREAM_MSG_DELETE_FAILED
- JS_STREAM_PURGE_FAILED
- could not create storage directory - %v
- storage directory is not writable
AI-assisted analysis of nats-io/nats-server@3a66a489d2 (2026-09-02).
Data as JSON: /api/errors/7a8b78b7013b8006.
Report an issue: GitHub.