{"record":{"id":"d3bffb6d68c952ec","repo":"apache/beam","slug":"duplicate-timer-family-id-v-used-by-struct-fields-v-and-v","errorCode":null,"errorMessage":"Duplicate timer family ID %v used by struct fields %v and %v. Ensure that timer family IDs are unique per DoFn","messagePattern":"Duplicate timer family ID (.+?) used by struct fields (.+?) and (.+?)\\. Ensure that timer family IDs are unique per DoFn","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"sdks/go/pkg/beam/core/graph/fn.go","lineNumber":1437,"sourceCode":"func validateTimer(fn *DoFn, numIn mainInputs) error {\n\tpt, fieldNames := fn.PipelineTimers()\n\n\tif _, ok := fn.methods[processElementName].TimerProvider(); ok {\n\t\tif numIn == MainSingle {\n\t\t\terr := errors.Errorf(\"ProcessElement uses a TimerProvider, but is not keyed\")\n\t\t\treturn errors.SetTopLevelMsgf(err, \"ProcessElement uses a TimerProvider, but is not keyed. \"+\n\t\t\t\t\"All stateful DoFns must take a key/value pair as an input.\")\n\t\t}\n\t\tif len(pt) == 0 {\n\t\t\terr := errors.New(\"ProcessElement uses a TimerProvider, but no Timer fields are defined in the DoFn\")\n\t\t\treturn errors.SetTopLevelMsgf(err, \"ProcessElement uses a TimerProvider, but no timer fields are defined in the DoFn\"+\n\t\t\t\t\", Ensure that your DoFn exports the Timer fields used to set and clear timers.\")\n\t\t}\n\t\ttimerKeys := make(map[string]string)\n\t\tfor i, t := range pt {\n\t\t\tfor timerFamilyID := range t.Timers() {\n\t\t\t\tif timer, ok := timerKeys[timerFamilyID]; ok {\n\t\t\t\t\terr := errors.Errorf(\"Duplicate timer key %v\", timerFamilyID)\n\t\t\t\t\treturn errors.SetTopLevelMsgf(err, \"Duplicate timer family ID %v used by struct fields %v and %v. Ensure that timer family IDs are unique per DoFn\", timerFamilyID, timer, fieldNames[i])\n\t\t\t\t}\n\t\t\t\ttimerKeys[timerFamilyID] = fieldNames[i]\n\t\t\t}\n\t\t}\n\t\tif err := validateOnTimerFn(fn); err != nil {\n\t\t\treturn err\n\t\t}\n\t} else {\n\t\tif len(pt) > 0 {\n\t\t\terr := errors.Errorf(\"ProcessElement doesn't use a TimerProvider, but Timer field is attached to the DoFn: %v\", pt)\n\t\t\treturn errors.SetTopLevelMsgf(err, \"ProcessElement doesn't use a TimerProvider, but Timer field is attached to the DoFn: %v\"+\n\t\t\t\t\", Ensure that you are using the TimerProvider to set and clear the timers.\", pt)\n\t\t}\n\t\tif err := validateOnTimerFn(fn); err == nil {\n\t\t\tactualErr := errors.New(\"OnTimer function is defined for the DoFn but no TimerProvider defined in ProcessElement\")\n\t\t\treturn errors.SetTopLevelMsgf(actualErr, \"OnTimer function is defined for the DoFn but no TimerProvider defined in ProcessElement.\"+\n\t\t\t\t\"Ensure that timers.Provider is defined in the ProcessElement and OnTimer methods of DoFn.\")","sourceCodeStart":1419,"sourceCodeEnd":1455,"githubUrl":"https://github.com/apache/beam/blob/12126d8942aaf848030c478b4c6a28c6af861c66/sdks/go/pkg/beam/core/graph/fn.go#L1419-L1455","documentation":"Apache Beam Go SDK validates DoFn definitions at graph-construction time. A DoFn may declare multiple Timer struct fields, each with one or more timer family IDs exposed via its Timers() map. When two struct fields expose the same timer family ID, the SDK cannot unambiguously route timer callbacks, so fn.go rejects the DoFn with this error before the pipeline runs.","triggerScenarios":"Defining a DoFn with two or more Timer-typed fields (e.g. `daily Timer` and `retry Timer`) whose spec.TimerFamilyID values are identical, then calling beam.ParDo/Run so graph validation invokes validateTimerFields in sdks/go/pkg/beam/core/graph/fn.go.","commonSituations":"Copy-pasting a Timer field and forgetting to change its family ID constant; refactoring timer specs into a shared constant used by two fields; merging two DoFns into one struct and keeping both timer fields.","solutions":["Assign a unique timer family ID (spec.TimerFamilyID) to each Timer field in the DoFn struct.","If both fields were meant to be the same timer, delete the duplicate field and keep a single one.","Use distinct Go constants for each timer family instead of a shared one to avoid collisions.","Re-run pipeline construction; the error names the two offending struct fields to fix."],"exampleFix":"// before\ntype MyDoFn struct {\n    daily  Timer\n    backup Timer\n}\n// after: give each timer a unique family ID\nvar dailySpec = timer.NewMainInputTimerSpec(\"daily\", time.Window{})\nvar backupSpec = timer.NewMainInputTimerSpec(\"backup\", time.Window{})","handlingStrategy":"validation","validationCode":"func checkUniqueTimerFamilies(d interface{}) error {\n    seen := map[string]string{}\n    v := reflect.ValueOf(d)\n    for i := 0; i < v.NumField(); i++ {\n        t, ok := v.Field(i).Interface().(Timer)\n        if !ok { continue }\n        for id := range t.Timers() {\n            if prev := seen[id]; prev != \"\" {\n                return fmt.Errorf(\"duplicate timer family %q on %s and %s\", id, prev, v.Type().Field(i).Name)\n            }\n            seen[id] = v.Type().Field(i).Name\n        }\n    }\n    return nil\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Use one exported constant per timer family ID and never share between fields.","Review DoFn struct diffs for copy-pasted Timer fields."],"tags":["beam","go","dofn","timer","graph-validation"],"backgroundTag":"invalid-config-value","analyzedSha":"12126d8942aaf848030c478b4c6a28c6af861c66","analyzedAt":"2026-09-13T01:50:10.254Z","contentChangedAt":"2026-09-13T01:50:10.254Z","schemaVersion":2},"datasetVersion":"2026-09-14T16:17:12.679Z"}