apache/beam · error
unable to get row encoder
Error message
unable to get row encoder
What it means
jdbcio's toRow converts a typed struct into an encoded Beam row payload. It first asks the coder package for a RowEncoderForStruct for the element's reflect.Type; when that fails (the type cannot be encoded as a Beam row), it panics with 'unable to get row encoder'. This is a panic, so it aborts the bundle rather than being a returned error.
Solutions
- Use a plain exported Go struct with types supported by Beam's schema encoding (strings, ints, floats, bools, time.Time, nested structs of the same).
- Check the underlying error by temporarily replacing the panic or reviewing RowEncoderForStruct docs to see which field is unsupported.
- Flatten or convert unsupported fields (e.g. store JSON as string) before the JDBC transform.
- Ensure pointer vs value type consistency: register the same concrete struct type you pass at runtime (TypeOf(pl) is taken from the actual value).
Example fix
// before
rows := p | beam.Create(ctx, []map[string]interface{}{...})
rows | jdbcio.Write(s, jdbcio.IO{...}) // panic: unable to get row encoder
// after
type User struct {
ID int64
Name string
Email string
}
rows := p | beam.Create(ctx, []User{{ID: 1, Name: "a", Email: "a@x.com"}})
rows | jdbcio.Write(s, jdbcio.IO{...}) Defensive patterns
Strategy: type-guard
Validate before calling
func isJdbcRowType(v interface{}) bool {
t := reflect.TypeOf(v)
if t == nil || t.Kind() != reflect.Struct {
return false
}
_, err := coder.RowEncoderForStruct(t)
return err == nil
} Type guard
// Only pass exported structs with supported field types to jdbcio:
func assertStructRow(v any) {
t := reflect.TypeOf(v)
if t.Kind() != reflect.Struct {
panic("jdbcio requires a struct element type, got " + t.Kind().String())
}
if _, err := coder.RowEncoderForStruct(t); err != nil {
panic("type not encodable as Beam row: " + err.Error())
}
} Prevention
- Define plain exported structs with Beam-supported field types for JDBC IO.
- Avoid map/interface/primitive element types for xlang jdbcio transforms.
- Add a pipeline-construction test that calls RowEncoderForStruct on your row type.
- Avoid func/chan fields in row structs.
When it happens
Trigger: Using xlang/jdbcio.Read or jdbcio.Write with a Go type that coder.RowEncoderForStruct cannot handle: non-struct types (map, interface, primitive), structs with unsupported field types, or unexported/complex fields the row encoder doesn't support.
Common situations: Passing *map[string]interface{} or a custom type alias as the row type; a struct containing unsupported types (e.g. chan, func, nested unregistered types); registering a custom type that lacks required schema mapping.
Understand the failure class
Background: UnsupportedOperationException and "is not supported" errors: when a library deliberately refuses a call — this error's family across 30 libraries.
Related errors
- unable to do row encoding
- err
- err
- Failed to decode URL parameters:
- Failed to optimize MergeAccumulators for combiner
AI-assisted analysis of apache/beam@12126d8942 (2026-09-13).
Data as JSON: /api/errors/7cd876b3fb3d26e0.
Report an issue: GitHub.
Appendix: source
Thrown at sdks/go/pkg/beam/io/xlang/jdbcio/jdbc.go:109
WriteStatement *string `beam:"writeStatement"`
FetchSize *int16 `beam:"fetchSize"`
OutputParallelization *bool `beam:"outputParallelization"`
}
// jdbcConfig stores the expansion service and configuration for JDBC IO.
type jdbcConfig struct {
classpaths []string
expansionAddr string
config *config
}
// TODO(riteshghorse): update the IO to use wrapper created in BigQueryIO.
func toRow(pl any) []byte {
rt := reflect.TypeOf(pl)
enc, err := coder.RowEncoderForStruct(rt)
if err != nil {
panic(fmt.Errorf("unable to get row encoder"))
}
var buf bytes.Buffer
if err := enc(pl, &buf); err != nil {
panic(fmt.Errorf("unable to do row encoding"))
}
return buf.Bytes()
}
// Write is a cross-language PTransform which writes Rows to the specified database via JDBC.
// tableName is a required parameter, and by default, the write statement is generated from it.
// The generated write statement can be overridden by passing in a WriteStatement option.
// If an expansion service address is not provided,
// an appropriate expansion service will be automatically started; however
// this is slower than having a persistent expansion service running.
//
// If no additional classpaths are provided using jdbcio.WriteClasspaths() then the default classpath
// for that driver would be used. As of now, the default classpaths are present only for PostgreSQL and MySQL.
//View on GitHub (pinned to 12126d8942)