apache/iceberg · error · ClassNotFoundException

Cannot find class; alternatives: ${classNames}

Error message

Cannot find class; alternatives: ${classNames}

What it means

DynClasses.Builder.buildChecked() throws ClassNotFoundException when none of the candidate class names could be loaded and nullOk is false. It is the checked variant: callers are forced to handle the missing-class case. The message lists every alternative name that was tried.

Source

Thrown at common/src/main/java/org/apache/iceberg/common/DynClasses.java:104

     *
     * @return this Builder for method chaining
     */
    public Builder orNull() {
      this.nullOk = true;
      return this;
    }

    /**
     * Returns the first implementation or throws ClassNotFoundException if one was not found.
     *
     * @param <S> Java superclass
     * @return a {@link Class} for the first implementation found
     * @throws ClassNotFoundException if no implementation was found
     */
    @SuppressWarnings("unchecked")
    public <S> Class<? extends S> buildChecked() throws ClassNotFoundException {
      if (!nullOk && foundClass == null) {
        throw new ClassNotFoundException(
            "Cannot find class; alternatives: " + Joiner.on(", ").join(classNames));
      }
      return (Class<? extends S>) foundClass;
    }

    /**
     * Returns the first implementation or throws RuntimeException if one was not found.
     *
     * @param <S> Java superclass
     * @return a {@link Class} for the first implementation found
     * @throws RuntimeException if no implementation was found
     */
    @SuppressWarnings("unchecked")
    public <S> Class<? extends S> build() {
      if (!nullOk && foundClass == null) {
        throw new RuntimeException(
            "Cannot find class; alternatives: " + Joiner.on(", ").join(classNames));
      }

View on GitHub (pinned to 86d9c8fc54)

Solutions

  1. Check the listed alternatives against the actual classpath; add the jar that provides one of them (e.g. the correct Spark/Hive version).
  2. Verify the fully-qualified class names — package renames across versions are the usual culprit.
  3. Call nullOk(true) if absence is acceptable, and handle the null Class explicitly.
  4. Use build() (RuntimeException) or a default impl(...) that always matches to avoid a hard failure.

Example fix

// before
Class<?> cls = DynClasses.builder().impl("org.apache.spark.sql.catalyst.OldClass").buildChecked();
// after
Class<?> cls = DynClasses.builder()
    .impl("org.apache.spark.sql.catalyst.NewClass")
    .impl("org.apache.spark.sql.catalyst.OldClass")
    .buildChecked();
Defensive patterns

Strategy: try-catch

Validate before calling

for (String name : new String[]{"com.example.ImplA", "com.example.ImplB"}) {
  try {
    Class.forName(name);
    return; // at least one candidate exists
  } catch (ClassNotFoundException ignored) { }
}
throw new IllegalStateException("None of the candidate classes are on the classpath; add the required dependency");

Try / catch

try {
  Class<?> cls = DynClasses.builder().impl("com.example.ImplA").impl("com.example.ImplB").buildChecked();
} catch (ClassNotFoundException e) {
  // no candidate found: log the alternatives and fall back or fail fast
}

Prevention

When it happens

Trigger: Calling DynClasses.builder().impl("a.B").impl("c.D").buildChecked() where every impl name fails to load (class absent, wrong name, or classloader can't see it).

Common situations: Detecting engine/runtime versions by trying class names (e.g. Spark 2 vs 3 classes) on a runtime that has neither; typos in fully-qualified names; shaded/missing jars on the classpath.

Related errors


AI-assisted analysis of apache/iceberg@86d9c8fc54 (2026-09-12). Data as JSON: /api/errors/420b7d56f73d4106. Report an issue: GitHub.