{"record":{"id":"0cec87865b92b2c4","repo":"apache/hadoop","slug":"malformed-plan","errorCode":"MALFORMED_PLAN","errorMessage":"Parsing plan failed.","messagePattern":"Parsing plan failed\\.","errorType":"exception","errorClass":"DiskBalancerException","httpStatus":null,"severity":"error","filePath":"hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/hdfs/server/datanode/DiskBalancer.java","lineNumber":464,"sourceCode":"    if (plan == null || plan.length() == 0) {\n      LOG.error(\"Disk Balancer -  Invalid plan.\");\n      throw new DiskBalancerException(\"Invalid plan.\",\n          DiskBalancerException.Result.INVALID_PLAN);\n    }\n\n    if ((planID == null) ||\n        (planID.length() != sha1Length) ||\n        !DigestUtils.sha1Hex(plan.getBytes(StandardCharsets.UTF_8))\n            .equalsIgnoreCase(planID)) {\n      LOG.error(\"Disk Balancer - Invalid plan hash.\");\n      throw new DiskBalancerException(\"Invalid or mis-matched hash.\",\n          DiskBalancerException.Result.INVALID_PLAN_HASH);\n    }\n\n    try {\n      return NodePlan.parseJson(plan);\n    } catch (IOException ex) {\n      throw new DiskBalancerException(\"Parsing plan failed.\", ex,\n          DiskBalancerException.Result.MALFORMED_PLAN);\n    }\n  }\n\n  /**\n   * Verifies that this plan is not older than 24 hours.\n   *\n   * @param plan - Node Plan\n   */\n  private void verifyTimeStamp(NodePlan plan) throws DiskBalancerException {\n    long now = Time.now();\n    long planTime = plan.getTimeStamp();\n\n    if ((planTime + planValidityInterval) < now) {\n      String planValidity = config.get(\n          DFSConfigKeys.DFS_DISK_BALANCER_PLAN_VALID_INTERVAL,\n          DFSConfigKeys.DFS_DISK_BALANCER_PLAN_VALID_INTERVAL_DEFAULT);\n      if (planValidity.matches(\"[0-9]$\")) {","sourceCodeStart":446,"sourceCodeEnd":482,"githubUrl":"https://github.com/apache/hadoop/blob/2add9630210752f88ceb1bb74eb65e37bf41da8e/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/hdfs/server/datanode/DiskBalancer.java#L446-L482","documentation":"Thrown by the DataNode-side DiskBalancer when NodePlan.parseJson() raises an IOException while parsing a submitted plan. The check runs after the planID sha1 verification, so the hash matched the bytes but the bytes themselves are not a valid diskbalancer NodePlan JSON document. The DiskBalancerException carries Result.MALFORMED_PLAN and chains the original IOException as its cause.","triggerScenarios":"Calling DiskBalancer.submitPlan(planID, version, plan, skipDateCheck) with a plan string that is truncated, hand-edited, empty, non-JSON, or generated by an incompatible HDFS version whose NodePlan schema differs. Also produced by passing the plan file's path instead of its contents, or by a transfer step that corrupts the file (shell heredoc mangling, encoding change).","commonSituations":"Manually editing plan.json to tweak bytesToMove and breaking JSON syntax; copying plan files between hosts with truncation; running `hdfs diskbalancer -plan` with a client whose version differs from the DataNode executing the plan; scripts that read the wrong file or only part of it.","solutions":["Regenerate the plan on the target node with `hdfs diskbalancer -plan <datanode-host:ipc-port>` and submit the fresh file unchanged.","Validate the file parses as JSON before submitting: `jq . <planfile>.plan.json` must succeed.","Confirm you pass the file's full contents (not its path) and that the planID equals sha1Hex of exactly those bytes.","Align the `hdfs` client version used for -plan with the DataNode version that will execute the plan."],"exampleFix":"// before: submit whatever string was read earlier\ndiskBalancer.submitPlan(planID, version, planString, false);\n\n// after: parse and hash-check before submitting\nNodePlan parsed;\ntry {\n  parsed = NodePlan.parseJson(planString);\n} catch (IOException e) {\n  throw new IllegalArgumentException(\"plan is not valid NodePlan JSON; regenerate it\", e);\n}\nif (!DigestUtils.sha1Hex(planString.getBytes(StandardCharsets.UTF_8))\n    .equalsIgnoreCase(planID)) {\n  throw new IllegalArgumentException(\"planID does not match plan content hash\");\n}\ndiskBalancer.submitPlan(planID, version, planString, false);","handlingStrategy":"validation","validationCode":"// Run before submitPlan\ntry {\n  NodePlan.parseJson(planString);\n} catch (IOException e) {\n  // plan content is malformed: regenerate instead of submitting\n}\nif (!DigestUtils.sha1Hex(planString.getBytes(StandardCharsets.UTF_8)).equalsIgnoreCase(planID)) {\n  // planID and plan content disagree: re-derive planID or regenerate plan\n}","typeGuard":null,"tryCatchPattern":"try {\n  diskBalancer.submitPlan(planID, version, plan, false);\n} catch (DiskBalancerException e) {\n  if (e.getResult() == DiskBalancerException.Result.MALFORMED_PLAN) {\n    // content corrupt: regenerate the plan, never resubmit the same bytes\n  } else {\n    throw e;\n  }\n}","preventionTips":["Never hand-edit generated plan JSON — regenerate with `hdfs diskbalancer -plan` instead","Keep the plan-generating `hdfs` client and the executing DataNode on matching versions","Verify planID equals sha1Hex of the exact plan bytes before submitting"],"tags":["disk-balancer","json","plan-validation","hdfs","datanode"],"backgroundTag":"json-parse-error","analyzedSha":"2add9630210752f88ceb1bb74eb65e37bf41da8e","analyzedAt":"2026-08-22T19:55:07.957Z","schemaVersion":2},"datasetVersion":"2026-08-22T20:17:22.307Z"}