apache/beam · error

bad nullable: %+v

Error message

bad nullable: %+v

What it means

DecodeCoderRef converts an encoded CoderRef back into a coder. A nullable-type ref must have exactly one component (the inner coder); any other count indicates a malformed or incompatible encoded form, so decoding aborts with the offending ref printed.

Solutions

  1. Regenerate the pipeline artifact from the original pipeline; don't hand-edit encoded coder graphs.
  2. Match the decoding SDK version to the encoding one.
  3. Inspect the printed CoderRef to find which graph-rewriting step broke the component list.
  4. If constructing CoderRefs programmatically, always wrap exactly one component for nullableType.

Example fix

// before
ref := &graphx.CoderRef{Type: graphx.Nullable, Components: nil}
c, err := graphx.DecodeCoderRef(ref)

// after
ref := &graphx.CoderRef{Type: graphx.Nullable, Components: []*graphx.CoderRef{innerRef}}
c, err := graphx.DecodeCoderRef(ref)
Defensive patterns

Strategy: try-catch

Validate before calling

func validNullableRef(r *graphx.CoderRef) bool {
	return r != nil && r.Type == graphx.Nullable && len(r.Components) == 1
}

Type guard

func isNullableRef(r *graphx.CoderRef) bool { return r != nil && r.Type == graphx.Nullable && len(r.Components) == 1 }

Try / catch

c, err := graphx.DecodeCoderRef(ref)
if err != nil {
	if strings.Contains(err.Error(), "bad nullable") {
		return nil, fmt.Errorf("corrupt nullable coder ref %v: %w", ref, err)
	}
	return nil, err
}

Prevention

When it happens

Trigger: Decoding a CoderRef with Type nullableType whose Components has length != 1 — during pipeline deserialization from a model pipeline (e.g. loading a saved pipeline or processing a Dataflow job description).

Common situations: Hand-edited or truncated pipeline JSON/proto artifacts; cross-SDK-version coder graphs; custom graph transforms that added or dropped components on nullable coders.

Related errors


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

Appendix: source

Thrown at sdks/go/pkg/beam/core/runtime/graphx/dataflow.go:287

					return nil, err
				}

				t := typex.New(typex.CoGBKType, append([]typex.FullType{key.T}, coder.Types(values)...)...)
				return &coder.Coder{Kind: coder.CoGBK, T: t, Components: append([]*coder.Coder{key}, values...)}, nil
			}
		}

		value, err := DecodeCoderRef(elm)
		if err != nil {
			return nil, err
		}

		t := typex.New(typex.KVType, key.T, value.T)
		return &coder.Coder{Kind: coder.KV, T: t, Components: []*coder.Coder{key, value}}, nil

	case nullableType:
		if len(c.Components) != 1 {
			return nil, errors.Errorf("bad nullable: %+v", c)
		}

		inner, err := DecodeCoderRef(c.Components[0])
		if err != nil {
			return nil, err
		}

		t := typex.New(typex.NullableType, inner.T)
		return &coder.Coder{Kind: coder.Nullable, T: t, Components: []*coder.Coder{inner}}, nil

	case lengthPrefixType:
		if len(c.Components) != 1 {
			return nil, errors.Errorf("bad length prefix: %+v", c)
		}

		subC := c.Components[0]
		switch subC.Type {
		case stringType: // Needs special handling if wrapped by dataflow.

View on GitHub (pinned to 12126d8942)