{"record":{"id":"e8005e8b151e9d41","repo":"apache/hadoop","slug":"unmatched","errorCode":null,"errorMessage":"Unmatched ')'","messagePattern":"Unmatched '\\)'","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/mapred/join/Parser.java","lineNumber":462,"sourceCode":"\n    public String toString() {\n      StringBuilder sb = new StringBuilder();\n      sb.append(ident + \"(\");\n      for (Node n : kids) {\n        sb.append(n.toString() + \",\");\n      }\n      sb.setCharAt(sb.length() - 1, ')');\n      return sb.toString();\n    }\n  }\n\n  private static Token reduce(Stack<Token> st, JobConf job) throws IOException {\n    LinkedList<Token> args = new LinkedList<Token>();\n    while (!st.isEmpty() && !TType.LPAREN.equals(st.peek().getType())) {\n      args.addFirst(st.pop());\n    }\n    if (st.isEmpty()) {\n      throw new IOException(\"Unmatched ')'\");\n    }\n    st.pop();\n    if (st.isEmpty() || !TType.IDENT.equals(st.peek().getType())) {\n      throw new IOException(\"Identifier expected\");\n    }\n    Node n = Node.forIdent(st.pop().getStr());\n    n.parse(args, job);\n    return new NodeToken(n);\n  }\n\n  /**\n   * Given an expression and an optional comparator, build a tree of\n   * InputFormats using the comparator to sort keys.\n   */\n  static Node parse(String expr, JobConf job) throws IOException {\n    if (null == expr) {\n      throw new IOException(\"Expression is null\");\n    }","sourceCodeStart":444,"sourceCodeEnd":480,"githubUrl":"https://github.com/apache/hadoop/blob/2add9630210752f88ceb1bb74eb65e37bf41da8e/hadoop-mapreduce-project/hadoop-mapreduce-client/hadoop-mapreduce-client-core/src/main/java/org/apache/hadoop/mapred/join/Parser.java#L444-L480","documentation":"When reduce() sees ')' it pops tokens backwards until the matching '('; if the stack empties first, there is a ')' with no opening '(' and it throws IOException(\"Unmatched ')'\"). The canonical case is one closing parenthesis too many, e.g. inner(tbl(\"a\"),tbl(\"b\"))).","triggerScenarios":"An extra trailing ')'; a stray ')' anywhere no function call is currently open; expressions assembled by concatenating fragments that each carry their own closer.","commonSituations":"Hand-balanced parentheses; composing an expression from per-source snippets like tbl(...) + \"))\" where the count drifts.","solutions":["Balance parentheses — exactly one ')' per '('","Generate the expression with CompositeInputFormat.compose() instead of manual concatenation","Dry-run setFormat(job) client-side to catch it before submission"],"exampleFix":"// before\njob.set(\"mapred.join.expr\", \"inner(tbl(F,/a),tbl(F,/b)))\");\n\n// after\njob.set(\"mapred.join.expr\", \"inner(tbl(F,/a),tbl(F,/b))\");","handlingStrategy":"validation","validationCode":"static void checkBalanced(String expr) {\n  int depth = 0;\n  boolean inQuote = false;\n  for (char c : expr.toCharArray()) {\n    if (c == '\"') inQuote = !inQuote;\n    else if (!inQuote && c == '(') depth++;\n    else if (!inQuote && c == ')' && --depth < 0) throw new IllegalArgumentException(\"unmatched )\");\n  }\n  if (depth != 0) throw new IllegalArgumentException(\"unbalanced parentheses\");\n}\ncheckBalanced(expr);\njob.set(\"mapred.join.expr\", expr);","typeGuard":null,"tryCatchPattern":"try {\n  new CompositeInputFormat<Object>().setFormat(job);\n} catch (IOException e) {\n  throw new IllegalArgumentException(\"unbalanced ')' in mapred.join.expr\", e);\n}","preventionTips":["Count parentheses when hand-writing expressions; one ')' per '('","Prefer compose() over concatenating fragments that each add their own closer","Run a paren-balance check on expressions built dynamically"],"tags":["hadoop","mapreduce","join","parser","parentheses","syntax"],"backgroundTag":"unbalanced-parentheses","analyzedSha":"2add9630210752f88ceb1bb74eb65e37bf41da8e","analyzedAt":"2026-08-22T19:55:07.957Z","schemaVersion":2},"datasetVersion":"2026-08-22T20:17:22.307Z"}