apache/beam · error

cannot make a state provider for an unkeyed input

Error message

cannot make a state provider for an unkeyed input %v

What it means

userStateAdapter.NewStateProvider creates a state provider bound to a keyed element. Keyed state requires a key coder (kc); when the input was registered without a key (unkeyed), no state can be scoped to an element key, so the call fails naming the element. It mirrors the keyed side-input check: state APIs are only valid for keyed PCollections.

Solutions

  1. Key the input PCollection before the stateful transform (emit KVs, e.g. via beam.ParDo mapping element -> KV(key, element)).
  2. Remove state usage from the DoFn if per-key state is not actually needed.
  3. Check the state spec's key coder is populated in the pipeline proto (stateIDToKeyCoder).
  4. Verify the DoFn's main input type is a KV matching the declared key coder.

Example fix

// before: stateful DoFn over unkeyed elements
beam.ParDo(s, statefulFn, unkeyedPCol)
// after: key elements first
keyed := beam.ParDo(s, func(v string) (string, string) { return keyFor(v), v }, unkeyedPCol)
beam.ParDo(s, statefulFn, keyed)
Defensive patterns

Strategy: validation

Validate before calling

if adapter.Kc() == nil { return errors.New("state requires a keyed input; key the PCollection first") }

Type guard

func supportsState(a *exec.UserStateAdapter) bool { return a != nil && a.HasKeyCoder() }

Try / catch

sp, err := adapter.NewStateProvider(ctx, reader, w, element)
if err != nil && strings.Contains(err.Error(), "unkeyed input") {
    return nil, fmt.Errorf("stateful DoFn used over unkeyed input: %w", err)
}

Prevention

When it happens

Trigger: Calling NewStateProvider on a userStateAdapter built with kc == nil — i.e. using stateful DoFn state APIs (bag, map, ordered list, combining state) against a main input that has no key.

Common situations: Adding state annotations (SetupProcessBundle state specs) to a DoFn whose main input is not a KV; building a pipeline in Go where the input PCollection was never keyed (missing ParDo/KV step); custom runners wiring state adapters for unkeyed inputs.

Understand the failure class

Background: "is not a compatible type" / "cannot merge" errors: when a value's type doesn't match what the library requires — this error's family across 65 libraries.

Related errors


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

Appendix: source

Thrown at sdks/go/pkg/beam/core/runtime/exec/userstate.go:662

// NewUserStateAdapter returns a user state adapter for the given StreamID and coder.
// It expects a W<V> or W<KV<K,V>> coder, because the protocol requires windowing information.
func NewUserStateAdapter(sid StreamID, c *coder.Coder, stateIDToCoder map[string]*coder.Coder, stateIDToKeyCoder map[string]*coder.Coder, stateIDToCombineFn map[string]*graph.CombineFn) UserStateAdapter {
	if !coder.IsW(c) {
		panic(fmt.Sprintf("expected WV coder for user state %v: %v", sid, c))
	}

	wc := MakeWindowEncoder(c.Window)
	var kc ElementEncoder
	if coder.IsKV(coder.SkipW(c)) {
		kc = MakeElementEncoder(coder.SkipW(c).Components[0])
	}
	return &userStateAdapter{sid: sid, wc: wc, kc: kc, c: c, stateIDToCoder: stateIDToCoder, stateIDToKeyCoder: stateIDToKeyCoder, stateIDToCombineFn: stateIDToCombineFn}
}

// NewStateProvider creates a stateProvider with the ability to talk to the state API.
func (s *userStateAdapter) NewStateProvider(ctx context.Context, reader StateReader, w typex.Window, element any) (stateProvider, error) {
	if s.kc == nil {
		return stateProvider{}, fmt.Errorf("cannot make a state provider for an unkeyed input %v", element)
	}
	elementKey, err := EncodeElement(s.kc, element.(*MainInput).Key.Elm)
	if err != nil {
		return stateProvider{}, err
	}

	win, err := EncodeWindow(s.wc, w)
	if err != nil {
		return stateProvider{}, err
	}
	sp := stateProvider{
		ctx:                      ctx,
		sr:                       reader,
		SID:                      s.sid,
		elementKey:               elementKey,
		window:                   win,
		transactionsByKey:        make(map[string][]state.Transaction),
		initialValueByKey:        make(map[string]any),

View on GitHub (pinned to 12126d8942)