apache/beam · error

no units

Error message

no <nil> units

What it means

exec.NewPlan validates that no execution unit in the slice is nil before assembling roots and the data source. The library throws this error because a nil unit would panic later during plan execution; it is a defensive constructor check.

Solutions

  1. Filter or assert non-nil units before calling NewPlan.
  2. Check the code that builds the units slice: a failed unit construction usually returns an error that was swallowed.
  3. Add a guard in the plan-construction helper to return the earlier construction error instead of appending nil.

Example fix

// before
units := append(units, maybeUnit) // maybeUnit may be nil
plan, err := exec.NewPlan(id, units)
// after
if maybeUnit == nil {
    return nil, fmt.Errorf("unit construction failed")
}
units := append(units, maybeUnit)
plan, err := exec.NewPlan(id, units)
Defensive patterns

Strategy: validation

Validate before calling

for i, u := range units {
    if u == nil {
        return fmt.Errorf("unit %d is nil before NewPlan", i)
    }
}
plan, err := exec.NewPlan(id, units)

Try / catch

plan, err := exec.NewPlan(id, units)
if err != nil {
    if strings.Contains(err.Error(), "no <nil> units") {
        log.Printf("unit construction produced nil; check builder error handling")
    }
    return err
}

Prevention

When it happens

Trigger: Passing a slice of execution units containing a nil entry to exec.NewPlan, usually because a preceding construction step (e.g. building a ParDo or DataSource) failed silently or a slice append skipped an assignment.

Common situations: Test code building unit lists dynamically (TestFlatten, TestMultiplex style); pipeline translation bugs where an optional transform produces no unit.

Related errors


AI-assisted analysis of apache/beam@12126d8942 (2026-09-13). Data as JSON: /api/errors/8d45c8a18628e963. Report an issue: GitHub.

Appendix: source

Thrown at sdks/go/pkg/beam/core/runtime/exec/plan.go:61

	// TODO: there can be more than 1 DataSource in a bundle.
	source *DataSource
}

// NewPlan returns a new bundle execution plan from the given units.
func NewPlan(id string, units []Unit) (*Plan, error) {
	var roots []Root
	var pcols []*PCollection
	var source *DataSource
	bf := bundleFinalizer{
		callbacks:         []bundleFinalizationCallback{},
		lastValidCallback: time.Now(),
	}
	var onTimers map[string]*ParDo

	for _, u := range units {
		if u == nil {
			return nil, errors.Errorf("no <nil> units")
		}
		if r, ok := u.(Root); ok {
			roots = append(roots, r)
		}
		if s, ok := u.(*DataSource); ok {
			source = s
		}
		if p, ok := u.(*PCollection); ok {
			pcols = append(pcols, p)
		}
		if pd, ok := u.(*ParDo); ok && pd.HasOnTimer() {
			if onTimers == nil {
				onTimers = map[string]*ParDo{}
			}
			onTimers[pd.PID] = pd
		}
		if p, ok := u.(needsBundleFinalization); ok {
			p.AttachFinalizer(&bf)

View on GitHub (pinned to 12126d8942)