apache/iceberg · error · NoSuchPlanIdException

Cannot find plan with id

Error message

Cannot find plan with id %s

What it means

InMemoryPlanningState (used by the in-memory REST test server) tracks asynchronously submitted scan plans by ID. asyncPlanStatus looks up the plan's status; if the ID is not in the map, NoSuchPlanIdException is thrown. This means the caller asked about a plan that was never submitted or has been forgotten by this state store.

Solutions

  1. Submit the plan first (via the async plan endpoint) and use the returned plan ID
  2. Verify the plan ID string matches the one returned by plan submission exactly
  3. Re-submit the plan if the in-memory state server was restarted
  4. Check client/server flow ordering: status polls must follow a successful submit

Example fix

// before
String planId = someStaleId; // may not exist
// after
String planId = restClient.post("v1/plan", planReq, PlanResponse.class).planId();
PlanStatus status = planningState.asyncPlanStatus(planId);
Defensive patterns

Strategy: try-catch

Validate before calling

if (planId == null || planningState == null) throw new IllegalStateException("Plan must be submitted before polling status");

Type guard

boolean planExists(String planId, InMemoryPlanningState s) {
  return planId != null && s != null && s.asyncPlanStatusOrNull(planId) != null; // if such accessor exists
}

Try / catch

try {
  status = planningState.asyncPlanStatus(planId);
} catch (NoSuchPlanIdException e) {
  status = PlanStatus.FAILED; // or re-submit the plan
}

Prevention

When it happens

Trigger: Polling plan status via the REST planning endpoint with an unknown/typo'd plan ID; querying a plan before submitting it; using a plan ID from a different server instance or after the in-memory state was recreated.

Common situations: Client retried a request against a restarted in-memory test server (IDs not persisted); race where a status poll fires before the SUBMITTED registration; stale plan IDs held across test setups.

Understand the failure class

Background: 'Could not be found', 'does not exist', 'not found in database': the resource-not-found family when an ID, slug, key, or URI lookup comes back empty — this error's family across 20 libraries.

Related errors


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

Appendix: source

Thrown at core/src/main/java/org/apache/iceberg/rest/InMemoryPlanningState.java:84

  void addPlanTask(String planTaskKey, List<FileScanTask> tasks) {
    planTaskToFileScanTasks.put(planTaskKey, tasks);
  }

  void addNextPlanTask(String currentTask, String nextTask) {
    planTaskToNext.put(currentTask, nextTask);
  }

  void addAsyncPlan(String plan) {
    PlanStatus existingStatus = asyncPlanningStates.get(plan);
    Preconditions.checkArgument(
        existingStatus == null, "Plan %s already exists with status %s", plan, existingStatus);
    asyncPlanningStates.put(plan, PlanStatus.SUBMITTED);
  }

  PlanStatus asyncPlanStatus(String plan) {
    PlanStatus existingStatus = asyncPlanningStates.get(plan);
    if (existingStatus == null) {
      throw new NoSuchPlanIdException("Cannot find plan with id %s", plan);
    }

    return asyncPlanningStates.get(plan);
  }

  void markAsyncPlanAsComplete(String plan) {
    PlanStatus existingStatus = asyncPlanningStates.get(plan);
    Preconditions.checkArgument(existingStatus != null, "Cannot find plan %s", plan);
    Preconditions.checkArgument(
        existingStatus == PlanStatus.SUBMITTED,
        "Cannot mark plan %s as completed as it is %s",
        plan,
        existingStatus);
    asyncPlanningStates.put(plan, PlanStatus.COMPLETED);
  }

  void markAsyncPlanFailed(String plan) {
    PlanStatus existingStatus = asyncPlanningStates.get(plan);

View on GitHub (pinned to 86d9c8fc54)