apache/beam · error · RuntimeException

QueryPlanner class %s does not have an accessible static fie

Error message

QueryPlanner class %s does not have an accessible static field 'FACTORY' of type QueryPlanner.Factory

What it means

After loading the planner class reflectively, instantiatePlanner reads its public static FACTORY field of type QueryPlanner.Factory. If the field is missing, inaccessible, or of the wrong shape (NoSuchFieldException/IllegalAccessException/failed checkStateNotNull cast context), it throws a RuntimeException stating the class must expose an accessible static FACTORY.

Source

Thrown at sdks/java/extensions/sql/src/main/java/org/apache/beam/sdk/extensions/sql/impl/BeamSqlEnv.java:351

      } catch (ClassNotFoundException exc) {
        throw new RuntimeException(
            "Cannot find requested QueryPlanner class: " + queryPlannerClassName, exc);
      }

      // This try/catch kept deliberately tight to ensure that we _only_ catch exceptions due to
      // this reflective access.
      QueryPlanner.Factory factory;
      try {
        // See https://github.com/typetools/jdk/pull/235#pullrequestreview-3400922783
        @SuppressWarnings("nullness")
        Object queryPlannerFactoryObj =
            checkStateNotNull(
                queryPlannerClass.getField("FACTORY").get(null),
                "Static field %s.FACTORY is null. It must be a QueryPlanner.Factory instance.",
                queryPlannerClass);
        factory = (QueryPlanner.Factory) queryPlannerFactoryObj;
      } catch (NoSuchFieldException | IllegalAccessException exc) {
        throw new RuntimeException(
            String.format(
                "QueryPlanner class %s does not have an accessible static field 'FACTORY' of type QueryPlanner.Factory",
                queryPlannerClassName),
            exc);
      }

      return factory.createPlanner(jdbcConnection, ruleSets);
    }
  }
}

View on GitHub (pinned to 12126d8942)

Solutions

  1. Add a public static field named FACTORY of type QueryPlanner.Factory to the planner class
  2. Ensure the field is accessible (public, non-null, initialized)
  3. If the class can't provide a FACTORY, wrap it or use the default BeamQueryPlanner
  4. Check the beam-sdks-java-extensions-sql version for the expected planner contract

Example fix

// before
public class MyPlanner implements QueryPlanner { ... } // no FACTORY
// after
public class MyPlanner implements QueryPlanner {
  public static final QueryPlanner.Factory FACTORY = ...;
  ...
}
Defensive patterns

Strategy: validation

Validate before calling

Class<?> c = Class.forName(plannerClassName);
c.getField("FACTORY"); // throws NoSuchFieldException early
assert QueryPlanner.Factory.class.isAssignableFrom(c.getField("FACTORY").getType());

Type guard

boolean hasFactoryField(Class<?> c) {
  try { return QueryPlanner.Factory.class.isAssignableFrom(c.getField("FACTORY").getType()); }
  catch (NoSuchFieldException e) { return false; }
}

Try / catch

try { env = builder.build(); } catch (RuntimeException e) { if (e.getMessage().contains("FACTORY")) { /* planner class is not a valid planner */ } throw e; }

Prevention

When it happens

Trigger: Providing a custom QueryPlanner class that lacks 'public static final QueryPlanner.Factory FACTORY', has it private/package-private, or names a non-planner class entirely.

Common situations: Implementing a custom planner against an older Beam API where FACTORY naming changed, pointing the planner config at a helper or wrapper class, refactors dropping the field.

Understand the failure class

Background: "is required", "must be set", "missing required field": configuration validation errors across open-source libraries — this error's family across 36 libraries.

Related errors


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