apache/beam · critical

panic

Error message

panic: %v %s

What it means

The Beam Go SDK's invoke helper (exec/util.go) recovers a panic from user code (e.g. a DoFn) and converts it into an error. If the panic value is not already a *doFnError, it wraps the panic value plus debug.Stack() into a top-level error message "panic: %v %s". It exists so a crashing DoFn surfaces as a pipeline error with the panic value prominent and the stack trace preserved as the original error.

Solutions

  1. Read the panic value in 'panic: <value>' and fix the DoFn code at the stack-trace location included in the error.
  2. Add nil/empty checks in the DoFn before dereferencing elements, maps, or side inputs.
  3. Recover and translate expected failure modes into returned errors inside the DoFn instead of panicking.
  4. Log incoming elements that trigger the panic and add validation/reject handling for malformed input.

Example fix

// before
func (fn *parseFn) ProcessElement(ctx context.Context, line string, emit func(Row)) {
    fields := strings.Split(line, ",")
    emit(Row{ID: fields[1]}) // panics if line has no comma
}
// after
func (fn *parseFn) ProcessElement(ctx context.Context, line string, emit func(Row)) {
    fields := strings.Split(line, ",")
    if len(fields) < 2 {
        return // skip or log malformed line instead of panicking
    }
    emit(Row{ID: fields[1]})
}
Defensive patterns

Strategy: try-catch

Try / catch

// The SDK already recovers the panic; handle the returned error in your pipeline:
if err := beam.RunWithEnvironment(ctx, p); err != nil {
    var dErr *exec.doFnError
    if errors.As(err, &dErr) {
        log.Printf("DoFn failed: %v", dErr)
    } else if strings.Contains(err.Error(), "panic:") {
        log.Printf("panic in DoFn: %v", err)
    }
}

Prevention

When it happens

Trigger: Any panic (nil pointer dereference, index out of range, explicit panic()) raised inside a DoFn's ProcessElement, StartBundle, FinishBundle, or a wrapped function executed via the exec framework's invoke path.

Common situations: User DoFn code dereferences a nil map/pointer, divides by zero, indexes a slice out of bounds, or panics inside a custom emitter or side-input callback during pipeline execution on DirectRunner or Dataflow.

Understand the failure class

Background: "This is a bug, please report it": internal invariant violations, unreachable panics, and SNH errors explained — this error's family across 47 libraries.

Related errors


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

Appendix: source

Thrown at sdks/go/pkg/beam/core/runtime/exec/util.go:58

	uid  UnitID
	pid  string
}

func (e *doFnError) Error() string {
	return fmt.Sprintf("DoFn[UID:%v, PID:%v, Name: %v] failed:\n%v", e.uid, e.pid, e.doFn, e.err)
}

// callNoPanic calls the given function and catches any panic.
func callNoPanic(ctx context.Context, fn func(context.Context) error) (err error) {
	defer func() {
		if r := recover(); r != nil {
			// Check if the panic value is from a failed DoFn, and return it without a panic trace.
			if e, ok := r.(*doFnError); ok {
				err = e
			} else {
				// Top level error is the panic itself, but also include the stack trace as the original error.
				// Higher levels can then add appropriate context without getting pushed down by the stack trace.
				err = errors.SetTopLevelMsgf(errors.Errorf("panic: %v %s", r, debug.Stack()), "panic: %v", r)
			}
		}
	}()
	return fn(ctx)
}

// MultiStartBundle calls StartBundle on multiple nodes. Convenience function.
func MultiStartBundle(ctx context.Context, id string, data DataContext, list ...Node) error {
	for _, n := range list {
		if err := n.StartBundle(ctx, id, data); err != nil {
			return err
		}
	}
	return nil
}

// MultiFinishBundle calls FinishBundle on multiple nodes. Convenience function.
func MultiFinishBundle(ctx context.Context, list ...Node) error {

View on GitHub (pinned to 12126d8942)