apache/hadoop · error · IOException
Identifier expected
Error message
Identifier expected
What it means
After popping back to '(', reduce() requires the next token down the stack to be the operator identifier (TType.IDENT). A '(' with no function name before it throws IOException("Identifier expected") — the common case is wrapping the whole expression in redundant parentheses, since the grammar supports function calls only: parentheses are always calls, never grouping.
Source
Thrown at hadoop-mapreduce-project/hadoop-mapreduce-client/hadoop-mapreduce-client-core/src/main/java/org/apache/hadoop/mapred/join/Parser.java:466
for (Node n : kids) {
sb.append(n.toString() + ",");
}
sb.setCharAt(sb.length() - 1, ')');
return sb.toString();
}
}
private static Token reduce(Stack<Token> st, JobConf job) throws IOException {
LinkedList<Token> args = new LinkedList<Token>();
while (!st.isEmpty() && !TType.LPAREN.equals(st.peek().getType())) {
args.addFirst(st.pop());
}
if (st.isEmpty()) {
throw new IOException("Unmatched ')'");
}
st.pop();
if (st.isEmpty() || !TType.IDENT.equals(st.peek().getType())) {
throw new IOException("Identifier expected");
}
Node n = Node.forIdent(st.pop().getStr());
n.parse(args, job);
return new NodeToken(n);
}
/**
* Given an expression and an optional comparator, build a tree of
* InputFormats using the comparator to sort keys.
*/
static Node parse(String expr, JobConf job) throws IOException {
if (null == expr) {
throw new IOException("Expression is null");
}
Class<? extends WritableComparator> cmpcl =
job.getClass("mapred.join.keycomparator", null, WritableComparator.class);
Lexer lex = new Lexer(expr);
Stack<Token> st = new Stack<Token>();View on GitHub (pinned to 2add963021)
Solutions
- Remove redundant parentheses — every '(' must directly follow an operator identifier
- Nest inner calls as arguments instead of grouping them with parens
- Dry-run setFormat(job) client-side
Example fix
// before
job.set("mapred.join.expr", "(inner(tbl(F,/a),tbl(F,/b)))");
// after
job.set("mapred.join.expr", "inner(tbl(F,/a),tbl(F,/b))"); Defensive patterns
Strategy: validation
Validate before calling
// the grammar is function calls only: every '(' must directly follow an identifier
if (expr.matches(".*(^|[^a-zA-Z0-9_$.])\\(")) {
throw new IllegalArgumentException(
"'(' without an operator name; remove grouping parentheses");
}
job.set("mapred.join.expr", expr); Try / catch
try {
new CompositeInputFormat<Object>().setFormat(job);
} catch (IOException e) {
throw new IllegalArgumentException(
"no grouping parens in join expressions; every '(' needs an operator name before it", e);
} Prevention
- Never wrap the expression or its sub-parts in grouping parentheses
- Nest inner calls as arguments instead
- Dry-run setFormat(job) for expressions produced by new config templates
When it happens
Trigger: (inner(tbl("a"),tbl("b"))) — outer redundant parens; a nested '(' immediately after another '(' as in inner((tbl("a")),...); a quoted string or number directly before '('.
Common situations: Wrapping the expression in parens 'for safety'; formatting or indentation edits that insert parentheses; assuming algebraic grouping is allowed.
Related errors
AI-assisted analysis of apache/hadoop@2add963021 (2026-08-22).
Data as JSON: /api/errors/4d35b21a8677812c.
Report an issue: GitHub.