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

  1. 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).
  2. Check the underlying error by temporarily replacing the panic or reviewing RowEncoderForStruct docs to see which field is unsupported.
  3. Flatten or convert unsupported fields (e.g. store JSON as string) before the JDBC transform.
  4. 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

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


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)