apache/beam · error

Logical Types may not be registered with empty interface typ

Error message

Logical Types may not be registered with empty interface types. %v has no methods.

What it means

A logical type provider registered on an interface with zero methods would match every Go value, since all types satisfy the empty interface. To prevent this pathological catch-all registration, the library panics when rt.NumMethod() == 0.

Source

Thrown at sdks/go/pkg/beam/core/runtime/graphx/schema/logicaltypes.go:112

	_, err := r.reflectTypeToFieldType(st)
	if err != nil {
		panic(fmt.Sprintf("LogicalType[%v] has an invalid StorageType %v: %v", lt.ID(), st, err))
	}
	if len(lt.ID()) == 0 {
		panic(fmt.Sprintf("invalid logical type, bad id: %v -> %v", lt.GoType(), lt.StorageType()))
	}
	// TODO add duplication checks.
	r.logicalTypeIdentifiers[lt.GoType()] = lt.ID()
	r.logicalTypes[lt.ID()] = lt
}

// RegisterLogicalTypeProvider allows registration of providers for interface types.
func (r *Registry) RegisterLogicalTypeProvider(rt reflect.Type, ltp LogicalTypeProvider) {
	if rt.Kind() != reflect.Interface {
		panic(fmt.Sprintf("Logical Types must be registered with interface types. %v is not an interface type.", rt))
	}
	if rt.NumMethod() == 0 {
		panic(fmt.Sprintf("Logical Types may not be registered with empty interface types. %v has no methods.", rt))
	}
	r.logicalTypeProviders[rt] = ltp
	r.logicalTypeInterfaces = append(r.logicalTypeInterfaces, rt)
}

// LogicalType is a mapping between custom Go types, and their schema equivalent storage types.
//
// A LogicalType is a way to define a type that can be stored in a schema field
// using a known underlying type for storage. The storage type must be comprised of
// known schema field types, or pre-registered LogicalTypes.
//
// LogicalTypes may not be mutually recursive at any level of indirection.
// LogicalTypes must map from a Go type to a single Schema Equivalent storage type.
type LogicalType struct {
	identifier          string
	goT, storageT, argT reflect.Type
	argV                reflect.Value
}

View on GitHub (pinned to 12126d8942)

Solutions

  1. Give the interface at least one method that your values implement
  2. Register concrete types via RegisterLogicalType instead of an empty-interface provider
  3. Add a marker method to the interface to narrow matching

Example fix

// before
type MyTag interface{}
// after
type MyTag interface{ IsMyType() }
Defensive patterns

Strategy: validation

Validate before calling

if rt.Kind() == reflect.Interface && rt.NumMethod() == 0 {
    return errors.New("provider interface must declare at least one method")
}

Type guard

func isNonEmptyInterface(rt reflect.Type) bool { return rt != nil && rt.Kind() == reflect.Interface && rt.NumMethod() > 0 }

Try / catch

defer func() { if r := recover(); r != nil { err = fmt.Errorf("register provider: %v", r) } }()

Prevention

When it happens

Trigger: Calling RegisterLogicalTypeProvider with an interface type that declares no methods, e.g. reflect.TypeOf((*interface{})(nil)).Elem() or a marker interface without methods.

Common situations: Using interface{} or empty marker interfaces to register a provider; designing a tagged interface where methods were removed during refactoring.

Understand the failure class

Background: "Must be a positive integer", "Invalid value", "Unsupported": the invalid-argument-value error family, when a library rejects the value you pass — this error's family across 35 libraries.

Related errors


AI-assisted analysis of apache/beam@12126d8942 (2026-09-13). Data as JSON: /api/errors/e69d384c8d93b812. Report an issue: GitHub.