apache/beam · error · IllegalArgumentException

Unknown type of fn class

Error message

Unknown type of fn class %s

What it means

MapElements.expand only knows how to adapt two kinds of wrapped functions (aSerializableFunction vs ProcessFunction). If the internal fn object is neither, it cannot produce a DoFn and throws. This is a defensive branch that should be unreachable for fn objects created by MapElements' own factories.

Solutions

  1. Create MapElements only via the provided static via(...) factories (lambda, MethodReference, InferableFunction, or ProcessFunction)
  2. Do not subclass MapElements or set its fn field directly
  3. Check Beam version consistency between pipeline construction and deserialization

Example fix

// before
new MapElements<InputT, OutputT>(customFnWrapper) { ... }
// after
MapElements<InputT, OutputT> m = MapElements.via((InputT x) -> transform(x));
Defensive patterns

Strategy: validation

Validate before calling

if (!(fn instanceof SerializableFunction) && !(fn instanceof ProcessFunction)) {
  throw new IllegalArgumentException("fn must be created via MapElements.via");
}

Type guard

boolean isSupportedFn(Object fn) {
  return fn instanceof ProcessFunction || fn instanceof SerializableFunction;
}

Try / catch

try {
  return mapElements.expand(input);
} catch (IllegalArgumentException e) {
  if (e.getMessage().startsWith("Unknown type of fn class")) {
    throw new IllegalStateException("Rebuild MapElements via MapElements.via(...)" , e);
  }
  throw e;
}

Prevention

When it happens

Trigger: Calling expand() on a MapElements whose fn was constructed as a type that is neither SerializableFunction-based nor ProcessFunction-based — typically only possible via reflection, custom subclasses, or corrupted serialization.

Common situations: Custom MapElements subclasses that bypass the standard factory methods (MapElements.via(...)); deserialization across incompatible Beam versions changing the fn wrapper class.

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/0e6e33ed33335075. Report an issue: GitHub.

Appendix: source

Thrown at sdks/java/core/src/main/java/org/apache/beam/sdk/transforms/MapElements.java:155

                              .getClosure()
                              .apply(c.element(), Fn.Context.wrapProcessContext(c)));
                    }
                  })
              .withSideInputs(
                  ((Contextful<Fn<InputT, OutputT>>) fn).getRequirements().getSideInputs()));
    } else if (fn instanceof ProcessFunction) {
      return input.apply(
          "Map",
          ParDo.of(
              new MapDoFn() {
                @ProcessElement
                public void processElement(
                    @Element InputT element, OutputReceiver<OutputT> receiver) throws Exception {
                  receiver.output(((ProcessFunction<InputT, OutputT>) fn).apply(element));
                }
              }));
    } else {
      throw new IllegalArgumentException(
          String.format("Unknown type of fn class %s", fn.getClass()));
    }
  }

  /** A DoFn implementation that handles a trivial map call. */
  @SuppressWarnings("unused") // for outer
  private abstract class MapDoFn extends DoFn<InputT, OutputT> {

    /** Holds {@link MapDoFn#outer instance} of enclosing class, used by runner implementations. */
    final MapElements<InputT, OutputT> outer = MapElements.this;

    @Override
    public void populateDisplayData(DisplayData.Builder builder) {
      builder.delegate(MapElements.this);
    }

    @Override
    public TypeDescriptor<InputT> getInputTypeDescriptor() {

View on GitHub (pinned to 12126d8942)