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
- Key the input before the timer DoFn: pass beam.KV{K: key, V: value} elements (use beam.ParDo with a KV output upstream)
- Verify with pc.Type() that the input coder URN is urnKVCoder
- If the DoFn only needs timers per element, introduce an artificial key (beam.AddDummyKey) then drop it after
- 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
- Insert beam.AddDummyKey (or a real keying step) before timer-using DoFns
- Check PCollection types in pipeline tests before submission to runners
- Annotate timer DoFns with comments stating KV input requirement
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
- batch.groupIntoBatchesBufferedFn: unexpected timer family
- OnTimer and ProcessElement functions for DoFn should have…
- OnTimer and ProcessElement functions for DoFn should have…
- OnTimer function doesn't use a TimerProvider, but Timer…
- OnTimer function is defined for the DoFn but no…
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 = timerSpecsView on GitHub (pinned to 12126d8942)