{"record":{"id":"f0ef61c17cf599bb","repo":"apache/hadoop","slug":"counters-version-mismatch-expected-groupfactory","errorCode":null,"errorMessage":"Counters version mismatch, expected ${groupFactory.version()} got ${version}","messagePattern":"Counters version mismatch, expected (.+?) got (.+?)","errorType":"exception","errorClass":"IOException","httpStatus":null,"severity":"error","filePath":"hadoop-mapreduce-project/hadoop-mapreduce-client/hadoop-mapreduce-client-core/src/main/java/org/apache/hadoop/mapreduce/counters/AbstractCounters.java","lineNumber":288,"sourceCode":"        group.write(out);\n      }\n    }\n    if (writeAllCounters) {\n      WritableUtils.writeVInt(out, groups.size());\n      for (G group : groups.values()) {\n        Text.writeString(out, group.getName());\n        group.write(out);\n      }\n    } else {\n      WritableUtils.writeVInt(out, 0);\n    }\n  }\n\n  @Override\n  public synchronized void readFields(DataInput in) throws IOException {\n    int version = WritableUtils.readVInt(in);\n    if (version != groupFactory.version()) {\n      throw new IOException(\"Counters version mismatch, expected \"+\n          groupFactory.version() +\" got \"+ version);\n    }\n    int numFGroups = WritableUtils.readVInt(in);\n    fgroups.clear();\n    GroupType[] groupTypes = GroupType.values();\n    while (numFGroups-- > 0) {\n      GroupType groupType = groupTypes[WritableUtils.readVInt(in)];\n      G group;\n      switch (groupType) {\n        case FILESYSTEM: // with nothing\n          group = groupFactory.newFileSystemGroup();\n          break;\n        case FRAMEWORK:  // with group id\n          group = groupFactory.newFrameworkGroup(WritableUtils.readVInt(in));\n          break;\n        default: // Silence dumb compiler, as it would've thrown earlier\n          throw new IOException(\"Unexpected counter group type: \"+ groupType);\n      }","sourceCodeStart":270,"sourceCodeEnd":306,"githubUrl":"https://github.com/apache/hadoop/blob/2add9630210752f88ceb1bb74eb65e37bf41da8e/hadoop-mapreduce-project/hadoop-mapreduce-client/hadoop-mapreduce-client-core/src/main/java/org/apache/hadoop/mapreduce/counters/AbstractCounters.java#L270-L306","documentation":"org.apache.hadoop.mapreduce.Counters (via AbstractCounters) is Hadoop Writable-serializable and writes a version number (from the GroupFactory) at the head of the byte stream. readFields throws IOException when the wire version does not equal the local groupFactory.version(), meaning the stream was written by a different counters implementation. This is a hard compatibility barrier: Writable Counters are not a stable cross-version exchange format.","triggerScenarios":"Counters.readFields() deserializing bytes produced by a different Hadoop major/minor build (version VInt differs), or by a custom AbstractCounters subclass whose GroupFactory reports a different version. Typical paths: RPC between nodes with mismatched Hadoop versions, replaying serialized counters from persisted files or captured events.","commonSituations":"Rolling upgrades where an old client/task reads counters from a newer cluster; tools that persist Writable Counters blobs and read them back after a Hadoop upgrade; custom counters frameworks overriding the GroupFactory without keeping version() consistent.","solutions":["Deserialize the counters with the exact same Hadoop build (and same GroupFactory) that serialized them.","For cross-version exchange, use stable representations instead: the JobHistoryServer REST API, 'mapreduce job -status' output, or JSON from the history viewer.","If you subclass AbstractCounters, keep groupFactory.version() aligned on both ends and change it only with a matching read path.","Re-run/recompute the counters rather than replaying old blobs after an upgrade."],"exampleFix":null,"handlingStrategy":"try-catch","validationCode":null,"typeGuard":null,"tryCatchPattern":"catch (IOException e) {\n  if (String.valueOf(e.getMessage()).contains(\"Counters version mismatch\")) {\n    // stream written by a different counters build: discard, start from new Counters()\n    counters = job.newCounters();\n  } else { throw e; }\n}","preventionTips":["Pin one Hadoop version on both ends of any channel that carries Writable Counters.","Never persist Writable Counters as a long-term exchange format; use JSON/REST outputs instead.","If subclassing AbstractCounters, keep groupFactory.version() identical across your deployed builds unless you handle migration."],"tags":["hadoop","mapreduce","counters","serialization","version-mismatch","ioexception"],"backgroundTag":"serialization-version-mismatch","analyzedSha":"2add9630210752f88ceb1bb74eb65e37bf41da8e","analyzedAt":"2026-08-22T19:55:07.957Z","schemaVersion":2},"datasetVersion":"2026-08-23T01:17:44.959Z"}