apache/hadoop · error · IOException

No RecordReader for " + ident

Error message

No RecordReader for " + ident

What it means

WNode.getRecordReader looks up the ComposableRecordReader constructor registered for the wrapped node's identifier and throws IOException("No RecordReader for <ident>") when rrCstrMap has no entry. In the stock flow CompositeInputFormat.setFormat calls addDefaults(), registering tbl to WrappedRecordReader before any reader is built, so this fires only when the parser tree is driven without that registration — custom code reusing Parser.WNode directly, or an extension that registers a node type without its record reader.

Source

Thrown at hadoop-mapreduce-project/hadoop-mapreduce-client/hadoop-mapreduce-client-core/src/main/java/org/apache/hadoop/mapred/join/Parser.java:326

    }

    private JobConf getConf(JobConf job) {
      JobConf conf = new JobConf(job);
      FileInputFormat.setInputPaths(conf, indir);
      conf.setClassLoader(job.getClassLoader());
      return conf;
    }

    public InputSplit[] getSplits(JobConf job, int numSplits)
        throws IOException {
      return inf.getSplits(getConf(job), numSplits);
    }

    public ComposableRecordReader getRecordReader(
        InputSplit split, JobConf job, Reporter reporter) throws IOException {
      try {
        if (!rrCstrMap.containsKey(ident)) {
          throw new IOException("No RecordReader for " + ident);
        }
        return rrCstrMap.get(ident).newInstance(id,
            inf.getRecordReader(split, getConf(job), reporter), cmpcl);
      } catch (IllegalAccessException e) {
        throw (IOException)new IOException().initCause(e);
      } catch (InstantiationException e) {
        throw (IOException)new IOException().initCause(e);
      } catch (InvocationTargetException e) {
        throw (IOException)new IOException().initCause(e);
      }
    }

    public String toString() {
      return ident + "(" + inf.getClass().getName() + ",\"" + indir + "\")";
    }
  }

  /**

View on GitHub (pinned to 2add963021)

Solutions

  1. Go through CompositeInputFormat.setFormat(job) (or CompositeInputFormat.getRecordReader) so addDefaults() registers inner/outer/override/tbl first
  2. In extensions, always register node and reader together via WNode.addIdentifier / CNode.addIdentifier
  3. Ensure the join package is loaded by a single classloader in the JVM

Example fix

// before: node tree driven raw
Parser.Node root = /* obtained directly */;
root.getRecordReader(split, job, reporter); // No RecordReader for tbl

// after: let CompositeInputFormat register identifiers first
CompositeInputFormat<?> cif = new CompositeInputFormat<Object>();
cif.setFormat(job);
ComposableRecordReader<?, ?> rr = cif.getRecordReader(split, job, reporter);
Defensive patterns

Strategy: validation

Validate before calling

// Always initialize through CompositeInputFormat so identifiers are registered
CompositeInputFormat<Text> cif = new CompositeInputFormat<Text>();
cif.setFormat(job); // registers inner/outer/override/tbl via addDefaults()
ComposableRecordReader<Text, TupleWritable> rr =
    cif.getRecordReader(split, job, reporter);

Try / catch

try {
  return root.getRecordReader(split, job, reporter);
} catch (IOException e) {
  throw new IOException("identifier not registered; call CompositeInputFormat.setFormat first", e);
}

Prevention

When it happens

Trigger: Calling getRecordReader on a Parser node tree obtained outside CompositeInputFormat without calling setFormat first; a custom ComposableInputFormat that populates the node map but not the record-reader map; classpath skew where org.apache.hadoop.mapred.join classes load from two classloaders so the static maps diverge.

Common situations: Framework extensions and unit tests that hand-drive the parser; isolated-classloader setups such as uber-jars or nested containers.

Related errors


AI-assisted analysis of apache/hadoop@2add963021 (2026-08-22). Data as JSON: /api/errors/c4c1342e3d594e20. Report an issue: GitHub.