apache/beam · critical

Creating CustomCoder for type failed

Error message

Creating %v CustomCoder for type %v failed

What it means

Inside the registry's lazy per-type factory, NewCustomCoder is re-invoked with the concrete type rt; if it fails the code panics with "Creating %v CustomCoder for type %v failed". The source comment notes this should be unreachable after Register-time validation, so it signals an internal invariant violation — usually the concrete type rt differs from the registered type in a way validation did not anticipate.

Solutions

  1. Register the coder for the exact concrete type being looked up
  2. Treat as a Beam bug: verify the reproduction and file/check an issue
  3. Verify no duplicate registrations with mismatched funcs for related types
Defensive patterns

Strategy: try-catch

Try / catch

defer func() {
    if r := recover(); r != nil {
        if strings.Contains(fmt.Sprint(r), "CustomCoder for type") {
            log.Fatalf("registry invariant violated: %v", r)
        }
        panic(r)
    }
}()

Prevention

When it happens

Trigger: Looking up a coder by concrete type rt where re-creating the CustomCoder with rt fails despite passing validation at registration time.

Common situations: Registering for a base/interface type then looking up with a related concrete type where the encode/decode funcs no longer type-check; Beam-internal bugs after version upgrades.

Understand the failure class

Background: "This is a bug, please report it": internal invariant violations, unreachable panics, and SNH errors explained — this error's family across 47 libraries.

Related errors


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

Appendix: source

Thrown at sdks/go/pkg/beam/core/graph/coder/registry.go:75

			for i, iT := range interfaceOrdering {
				if iT == t {
					index = i
					break
				}
			}
			interfaceOrdering = append(interfaceOrdering[:index], interfaceOrdering[index+1:]...)
		}
		// Either way, always append.
		interfaceOrdering = append(interfaceOrdering, t)
	}
	name := t.String() // Use the real type names for coders.
	coderRegistry[t] = func(rt reflect.Type) *CustomCoder {
		// We need to provide the concrete type, so that coders that use
		// the reflect.Type have the proper instance.
		cc, err := NewCustomCoder(name, rt, enc, dec)
		if err != nil {
			// An error on look up shouldn't happen after the validation.
			panic(errors.Wrapf(err, "Creating %v CustomCoder for type %v failed", name, rt))
		}
		return cc
	}
}

// RegisterDeterministicCoderWithFuncs is like RegisterDeterministicCoder
// but accepts pre-wrapped reflectx.Func values (typically built via
// reflectx.MakeFuncWithName) so the caller controls the function name
// used during cross-worker serialization. This is required for
// closures inside Go generic functions where different type
// instantiations produce closures with the same compiler name.
func RegisterDeterministicCoderWithFuncs(t reflect.Type, encFn, decFn *funcx.Fn) {
	name := t.String()
	coderRegistry[t] = func(rt reflect.Type) *CustomCoder {
		return NewCustomCoderWithFuncs(name, rt, encFn, decFn)
	}
	deterministicRegistry[t] = true
}

View on GitHub (pinned to 12126d8942)