apache/beam · error

timer using DoFn doesn't use a KV as PCollection input…

Error message

timer using DoFn %v doesn't use a KV as PCollection input. Unable to extract key coder for timers, got %v

What it means

A DoFn that uses timers must read from a KV PCollection so the timer key coder can be extracted from the KV coder. When translating timers, the marshaller looked up the coder of the main input PCollection and found it is not a KV coder, so it cannot derive the key/window coders timers require, and fails naming the DoFn and the actual URN found.

Solutions

  1. Key the input before the timer DoFn: pass beam.KV{K: key, V: value} elements (use beam.ParDo with a KV output upstream)
  2. Verify with pc.Type() that the input coder URN is urnKVCoder
  3. If the DoFn only needs timers per element, introduce an artificial key (beam.AddDummyKey) then drop it after
  4. Check for a restructure step that flattened the KV

Example fix

// before
beam.ParDo(s, &timerFn{}, in) // in is []string
// after
keyed := beam.ParDo(s, addKeyFn, in) // emits KV<string,string>
beam.ParDo(s, &timerFn{}, keyed)
Defensive patterns

Strategy: validation

Validate before calling

// Before applying a timer DoFn, assert the input is KV-typed
if !beam.IsKV(in.Type()) {
    return errors.New("timer DoFn requires a KV<...> PCollection input")
}

Type guard

func isKVCollection(pc beam.PCollection) bool {
    return strings.HasPrefix(pc.Type().String(), "beam.KV")
}

Try / catch

if _, err := graphx.Marshal(p); err != nil {
    if strings.Contains(err.Error(), "doesn't use a KV as PCollection input") {
        return fmt.Errorf("timer DoFn input must be KV: %v", err)
    }
    return err
}

Prevention

When it happens

Trigger: Applying a timer-using DoFn (implements TimerProvider) to a non-KV input PCollection, e.g. beam.ParDo(p, timerFn, pc) where pc's element type is not KV<K,V>.

Common situations: Forgetting beam.KV wrapper before a timer DoFn; restructurings/windowing that changed the input to a plain value; copy-pasting a stateful DoFn onto an unkeyed stream.

Understand the failure class

Background: Type mismatch errors: IllegalArgumentException, TypeError and type guards across 150 open-source libraries — this error's family across 150 libraries.

Related errors


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

Appendix: source

Thrown at sdks/go/pkg/beam/core/runtime/graphx/translate.go:632

						},
					}
				default:
					return nil, errors.Errorf("State type %v not recognized for state %v", ps.StateKey(), ps)
				}
			}
			payload.StateSpecs = stateSpecs
		}
		if _, ok := edge.Edge.DoFn.ProcessElementFn().TimerProvider(); ok {
			m.requirements[URNRequiresStatefulProcessing] = true
			timerSpecs := make(map[string]*pipepb.TimerFamilySpec)
			pipelineTimers, _ := edge.Edge.DoFn.PipelineTimers()

			// All timers for a single DoFn have the same key and window coders, that match the input PCollection.
			mainInputID := inputs["i0"]
			pCol := m.pcollections[mainInputID]
			kvCoder := m.coders.coders[pCol.CoderId]
			if kvCoder.GetSpec().GetUrn() != urnKVCoder {
				return nil, errors.Errorf("timer using DoFn %v doesn't use a KV as PCollection input. Unable to extract key coder for timers, got %v", edge.Name, kvCoder.GetSpec().GetUrn())
			}
			keyCoderID := kvCoder.GetComponentCoderIds()[0]

			wsID := pCol.GetWindowingStrategyId()
			ws := m.windowing[wsID]
			windowCoderID := ws.GetWindowCoderId()

			timerCoderID := m.coders.internBuiltInCoder(urnTimerCoder, keyCoderID, windowCoderID)

			for _, pt := range pipelineTimers {
				for timerFamilyID, timeDomain := range pt.Timers() {
					timerSpecs[timerFamilyID] = &pipepb.TimerFamilySpec{
						TimeDomain:         pipepb.TimeDomain_Enum(timeDomain),
						TimerFamilyCoderId: timerCoderID,
					}
				}
			}
			payload.TimerFamilySpecs = timerSpecs

View on GitHub (pinned to 12126d8942)