{"record":{"id":"1ead3193597e644e","repo":"apache/beam","slug":"type-has-unexported-field-v","errorCode":null,"errorMessage":"type has unexported field: %v","messagePattern":"type has unexported field: (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"sdks/go/pkg/beam/core/runtime/graphx/serialize.go","lineNumber":442,"sourceCode":"\t\treturn &v1pb.Type{Kind: v1pb.Type_FLOAT64}, nil\n\tcase reflect.String:\n\t\treturn &v1pb.Type{Kind: v1pb.Type_STRING}, nil\n\n\tcase reflect.Slice:\n\t\telm, err := encodeType(t.Elem())\n\t\tif err != nil {\n\t\t\twrapped := errors.Wrap(err, \"bad element type\")\n\t\t\treturn nil, errors.WithContextf(wrapped, \"encoding slice %v\", t)\n\t\t}\n\t\treturn &v1pb.Type{Kind: v1pb.Type_SLICE, Element: elm}, nil\n\n\tcase reflect.Struct:\n\t\tvar fields []*v1pb.Type_StructField\n\t\tfor i := 0; i < t.NumField(); i++ {\n\t\t\tf := t.Field(i)\n\n\t\t\tif f.PkgPath != \"\" {\n\t\t\t\twrapped := errors.Errorf(\"type has unexported field: %v\", f.Name)\n\t\t\t\treturn nil, errors.WithContextf(wrapped, \"encoding struct %v\", t)\n\t\t\t}\n\n\t\t\tfType, err := encodeType(f.Type)\n\t\t\tif err != nil {\n\t\t\t\twrapped := errors.Wrap(err, \"bad field type\")\n\t\t\t\treturn nil, errors.WithContextf(wrapped, \"encoding struct %v\", t)\n\t\t\t}\n\n\t\t\tfield := &v1pb.Type_StructField{\n\t\t\t\tName:      f.Name,\n\t\t\t\tPkgPath:   f.PkgPath,\n\t\t\t\tType:      fType,\n\t\t\t\tTag:       string(f.Tag),\n\t\t\t\tOffset:    int64(f.Offset),\n\t\t\t\tIndex:     encodeInts(f.Index),\n\t\t\t\tAnonymous: f.Anonymous,\n\t\t\t}","sourceCodeStart":424,"sourceCodeEnd":460,"githubUrl":"https://github.com/apache/beam/blob/12126d8942aaf848030c478b4c6a28c6af861c66/sdks/go/pkg/beam/core/runtime/graphx/serialize.go#L424-L460","documentation":"While encoding a Go struct type for the pipeline wire format, encodeType rejects structs containing unexported fields (fields with non-empty PkgPath), because they cannot be reconstructed on the decoding side. This is a deliberate serialization-safety check in the Beam Go SDK's graph export path.","triggerScenarios":"Any pipeline serialization that encounters a struct type with at least one lowercase (unexported) field: DoFn structs, PCollection element types, or fields nested inside function signatures, during graphx export.","commonSituations":"User DoFns or event types modeled with unexported fields (idiomatic Go encapsulation, e.g. mutexes sync.Mutex or private caches) submitted to remote runners.","solutions":["Export all struct fields (capitalize field names) for types used in Beam signatures and PCollections.","Move unexported state (mutexes, caches) into separate non-serialized helper types outside the encoded struct.","Encode the private state as an exported field with a custom coder instead.","Use a distinct serializable struct for data flow and keep the encapsulating struct out of the graph."],"exampleFix":"// before\ntype Event struct {\n    TS   int64\n    once sync.Once\n}\n// after\ntype Event struct {\n    TS int64\n}","handlingStrategy":"validation","validationCode":"func hasUnexportedFields(t reflect.Type) bool {\n    if t.Kind() != reflect.Struct { return false }\n    for i := 0; i < t.NumField(); i++ {\n        if t.Field(i).PkgPath != \"\" { return true }\n    }\n    return false\n}","typeGuard":"func beamSafeStruct(v any) bool { return !hasUnexportedFields(reflect.TypeOf(v)) }","tryCatchPattern":"if hasUnexportedFields(reflect.TypeOf(evt)) {\n    return fmt.Errorf(\"type %T has unexported fields and cannot be serialized by Beam\", evt)\n}","preventionTips":["Use only exported fields in types flowing through PCollections","Keep mutexes, sync primitives, and caches out of data structs","Add a unit test asserting pipeline exportability of all record types"],"tags":["go","apache-beam","serialization","struct"],"backgroundTag":"unexported-struct-field","analyzedSha":"12126d8942aaf848030c478b4c6a28c6af861c66","analyzedAt":"2026-09-13T01:50:10.254Z","contentChangedAt":"2026-09-13T01:50:10.254Z","schemaVersion":2},"datasetVersion":"2026-09-20T03:17:13.778Z"}