apache/beam · error · RuntimeException

Unable to construct @OnTimerFamily invoker for ${className}

Error message

Unable to construct @OnTimerFamily invoker for ${className}

What it means

ByteBuddyOnTimerInvokerFactory.forTimerFamily does for @OnTimerFamily methods what forTimer does for @OnTimer: it generates an invoker class with ByteBuddy and instantiates it reflectively. Failures in generation, classloading, constructor access, or invocation (including the same six checked/reflective exception types) are wrapped in this RuntimeException naming the DoFn class. It indicates the timer-family dispatch machinery could not be built for that DoFn.

Source

Thrown at sdks/java/core/src/main/java/org/apache/beam/sdk/transforms/reflect/ByteBuddyOnTimerInvokerFactory.java:104

  public <InputT, OutputT> OnTimerInvoker<InputT, OutputT> forTimerFamily(
      DoFn<InputT, OutputT> fn, String timerId) {

    @SuppressWarnings("unchecked")
    Class<? extends DoFn<?, ?>> fnClass = (Class<? extends DoFn<?, ?>>) fn.getClass();
    try {
      OnTimerMethodSpecifier onTimerMethodSpecifier =
          OnTimerMethodSpecifier.forClassAndTimerId(fnClass, timerId);
      Constructor<?> constructor = constructorTimerFamilyCache.get(onTimerMethodSpecifier);

      return (OnTimerInvoker<InputT, OutputT>) constructor.newInstance(fn);
    } catch (InstantiationException
        | IllegalAccessException
        | IllegalArgumentException
        | InvocationTargetException
        | SecurityException
        | ExecutionException e) {
      throw new RuntimeException(
          String.format(
              "Unable to construct @%s invoker for %s",
              DoFn.OnTimerFamily.class.getSimpleName(), fn.getClass().getName()),
          e);
    }
  }

  public static ByteBuddyOnTimerInvokerFactory only() {
    return INSTANCE;
  }

  private static final ByteBuddyOnTimerInvokerFactory INSTANCE =
      new ByteBuddyOnTimerInvokerFactory();

  private ByteBuddyOnTimerInvokerFactory() {}

  /**
   * The field name for the delegate of {@link DoFn} subclass that a bytebuddy invoker will call.

View on GitHub (pinned to 12126d8942)

Solutions

  1. Declare the DoFn as a public top-level (or public static nested) class with a visible constructor.
  2. Inspect getCause() of the thrown RuntimeException to pinpoint generation vs instantiation failure.
  3. Upgrade Beam (and its bundled ByteBuddy) or set -Dnet.bytebuddy.experimental=true for new JDK compatibility.
  4. Verify the @OnTimerFamily method's timer family ID matches a @TimerFamily field declaration in the DoFn.

Example fix

// before: private nested class
private class FamilyFn extends DoFn<KV<String,Integer>, String> {
  @TimerFamily("tf") private final TimerSpec spec = TimerSpecs.timerMap(...);
  @OnTimerFamily("tf") public void on(Map<String,Timer> ts) {...}
}
// after: public static class with public members
public static class FamilyFn extends DoFn<KV<String,Integer>, String> {
  @TimerFamily("tf") public final TimerSpec spec = TimerSpecs.timerMap(...);
  @OnTimerFamily("tf") public void on(Map<String,Timer> ts) {...}
}
Defensive patterns

Strategy: try-catch

Validate before calling

if (!Modifier.isPublic(fn.getClass().getModifiers()) || fn.getClass().isAnonymousClass()) {
  throw new IllegalStateException("@OnTimerFamily DoFn must be public and instantiable");
}
boolean hasFamilyMethod = java.util.Arrays.stream(fn.getClass().getDeclaredMethods())
    .anyMatch(m -> m.isAnnotationPresent(DoFn.OnTimerFamily.class));
if (!hasFamilyMethod) throw new IllegalStateException("No @OnTimerFamily method on " + fn.getClass());

Try / catch

try {
  invoker = ByteBuddyOnTimerInvokerFactory.forTimerFamily(fn, timerFamilyId);
} catch (RuntimeException e) {
  log.error("@OnTimerFamily invoker construction failed for " + fn.getClass().getName()
      + "; cause=" + e.getCause(), e);
  throw e;
}

Prevention

When it happens

Trigger: Calling forTimerFamily (or triggering a timer family) for a DoFn whose @OnTimerFamily invoker cannot be generated or constructed: inaccessible/non-static DoFn class, ByteBuddy generation failure on the JVM, or reflective instantiation throwing any of the wrapped exception types.

Common situations: DoFn with @OnTimerFamily defined as a private or inner class; runners with restrictive classloading (Flink user-code classloader) that cannot see generated classes; newer JDKs unsupported by the bundled ByteBuddy version; missing TimerFamily spec registration in the DoFn signature.

Related errors


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