apache/beam · error · ValueError

Return value not iterable: %s: %s

Error message

Return value not iterable: %s: %s

What it means

DoFn.default_type_hints strips the outer iterable from the function's output type because ParDo/FlatMap semantics require the callable's return value to be iterable of elements (core.py:826). If strip_iterable() finds the inferred output type is not iterable, the ValueError is re-raised with the DoFn in the message.

Source

Thrown at sdks/python/apache_beam/transforms/core.py:826

      if (process_batch_type_hints.output_types
          != typehints.decorators.IOTypeHints.empty().output_types):
        if (process_type_hints.output_types
            != typehints.decorators.IOTypeHints.empty().output_types and
            process_batch_type_hints.output_types
            != process_type_hints.output_types):
          raise TypeError(
              f"DoFn {self!r} yields element from both process and "
              "process_batch, but they have mismatched output typehints:\n"
              f" process: {process_type_hints.output_types}\n"
              f" process_batch: {process_batch_type_hints.output_types}")

        process_type_hints = process_type_hints.with_output_types_from(
            process_batch_type_hints)

    try:
      process_type_hints = process_type_hints.strip_iterable()
    except ValueError as e:
      raise ValueError('Return value not iterable: %s: %s' % (self, e))
    process_type_hints = process_type_hints.extract_tagged_outputs()

    # Prefer class decorator type hints for backwards compatibility.
    return get_type_hints(self.__class__).with_defaults(process_type_hints)

  # TODO(sourabhbajaj): Do we want to remove the responsibility of these from
  # the DoFn or maybe the runner
  def infer_output_type(self, input_type):
    # TODO(https://github.com/apache/beam/issues/19824): Side inputs types.
    return trivial_inference.element_type(
        _strip_output_annotations(
            trivial_inference.infer_return_type(self.process, [input_type])))

  @property
  def _process_defined(self) -> bool:
    # Check if this DoFn's process method has been overridden
    # Note that we retrieve the __func__ attribute, if it exists, to get the
    # underlying function from the bound method.

View on GitHub (pinned to 12126d8942)

Solutions

  1. Annotate the method with an iterable output type, e.g. -> Iterator[str] or -> Iterable[List[int]].
  2. Use `yield` in `process` so the inferred type is a generator (iterable).
  3. If the method intentionally returns a scalar, wrap it or restructure to yield.

Example fix

# before
class MyDoFn(DoFn):
    def process(self, x) -> str:
        return str(x)

# after
class MyDoFn(DoFn):
    def process(self, x) -> Iterator[str]:
        yield str(x)
Defensive patterns

Strategy: validation

Validate before calling

import inspect
from apache_beam.typehints import typehints
def check_iterable_return(fn):
    ret = inspect.signature(fn).return_annotation
    if ret is not inspect.Signature.empty and not str(ret).startswith(('Iterator', 'Iterable', 'Generator', 'List', 'list')):
        raise TypeError(f'{fn.__name__} should have an iterable return annotation, got {ret}')

Type guard

def yields_elements(fn) -> bool:
    import inspect
    return any('yield' in getattr(c, 'co_code', b'').decode('latin1', 'ignore') for c in inspect.unwrap(fn).__code__.co_consts if hasattr(c, 'co_code'))

Try / catch

try:
    hints = dofn.default_type_hints()
except ValueError as e:
    if 'Return value not iterable' in str(e):
        logger.error('process must yield/return an iterable: %s', e)
    raise

Prevention

When it happens

Trigger: A `process`/`process_batch` method whose annotated or inferred return type is a non-iterable, e.g. annotated `-> int` or `-> str` when Beam expects Iterator/Iterable of elements.

Common situations: Using `return` instead of `yield` with a scalar; explicit output annotations like `-> str`; wrappers returning single values instead of generators.

Understand the failure class

Background: Type mismatch errors: IllegalArgumentException, TypeError and type guards across 150 open-source libraries — this error's family across 150 libraries.

Related errors


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