apache/hadoop · error · IOException

Unmatched ')'

Error message

Unmatched ')'

What it means

Parser.reduce (Parser.java:525) processes a ')' by popping tokens off the parse stack until it finds the matching '('. If the stack empties first, the ')' has no opener and 'Unmatched \')\'' is thrown — the join expression has more closing parentheses than opening ones.

Source

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

    public String toString() {
      StringBuilder sb = new StringBuilder();
      sb.append(ident + "(");
      for (Node n : kids) {
        sb.append(n.toString() + ",");
      }
      sb.setCharAt(sb.length() - 1, ')');
      return sb.toString();
    }
  }

  private static Token reduce(Stack<Token> st, Configuration conf) 
      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, conf);
    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, Configuration conf) throws IOException {
    if (null == expr) {
      throw new IOException("Expression is null");
    }

View on GitHub (pinned to 2add963021)

Solutions

  1. Balance the parentheses: count '(' and ')' in the expression and make them equal and properly nested
  2. Restructure using CompositeInputFormat.compose(...) so parentheses are generated correctly
  3. Unit-test with Parser.parse(expr, conf) before submitting the job
  4. Lint hand-written expressions in code review — the grammar is tiny; each operator call is ident(args...)

Example fix

// before
String expr = "inner(tbl(fmt, \"/a\"), tbl(fmt, \"/b\")))";

// after
String expr = "inner(tbl(fmt, \"/a\"), tbl(fmt, \"/b\"))";
Defensive patterns

Strategy: validation

Validate before calling

static void requireBalancedParens(String expr) {
  int depth = 0;
  for (char c : expr.toCharArray()) {
    if (c == '(') depth++;
    else if (c == ')') { depth--; if (depth < 0) throw new IllegalArgumentException("Unmatched ')' in join expression"); }
  }
  if (depth != 0) throw new IllegalArgumentException("Unbalanced parentheses in join expression");
}

Try / catch

try { Parser.parse(expr, conf); } catch (IOException e) { throw new IllegalArgumentException("Unbalanced join expression: " + expr, e); }

Prevention

When it happens

Trigger: Any expression where a ')' appears before its '(': 'inner(tbl(fmt,"/a")))' (extra trailing paren), 'tbl(fmt,"/a"))(...)', or a copy-paste duplication of a closing fragment. During parse (Parser.java:557-561) each RPAREN token triggers reduce(), which throws when the stack is empty.

Common situations: Hand-editing mapreduce.join.expr and adding a stray ')'; string concatenation bugs that append an extra closer; copying multi-line expressions and duplicating the last line's parenthesis.

Related errors


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