{"record":{"id":"46de52618fdeb8e9","repo":"apache/hadoop","slug":"can-t-have-recursive-multithreadedmapper-instances","errorCode":null,"errorMessage":"Can't have recursive MultithreadedMapper instances.","messagePattern":"Can't have recursive MultithreadedMapper instances\\.","errorType":"validation","errorClass":"IllegalArgumentException","httpStatus":null,"severity":"error","filePath":"hadoop-mapreduce-project/hadoop-mapreduce-client/hadoop-mapreduce-client-core/src/main/java/org/apache/hadoop/mapreduce/lib/map/MultithreadedMapper.java","lineNumber":120,"sourceCode":"  Class<Mapper<K1,V1,K2,V2>> getMapperClass(JobContext job) {\n    return (Class<Mapper<K1,V1,K2,V2>>) \n      job.getConfiguration().getClass(MAP_CLASS, Mapper.class);\n  }\n  \n  /**\n   * Set the application's mapper class.\n   * @param <K1> the map input key type\n   * @param <V1> the map input value type\n   * @param <K2> the map output key type\n   * @param <V2> the map output value type\n   * @param job the job to modify\n   * @param cls the class to use as the mapper\n   */\n  public static <K1,V1,K2,V2> \n  void setMapperClass(Job job, \n                      Class<? extends Mapper<K1,V1,K2,V2>> cls) {\n    if (MultithreadedMapper.class.isAssignableFrom(cls)) {\n      throw new IllegalArgumentException(\"Can't have recursive \" + \n                                         \"MultithreadedMapper instances.\");\n    }\n    job.getConfiguration().setClass(MAP_CLASS, cls, Mapper.class);\n  }\n\n  /**\n   * Run the application's maps using a thread pool.\n   */\n  @Override\n  public void run(Context context) throws IOException, InterruptedException {\n    outer = context;\n    int numberOfThreads = getNumberOfThreads(context);\n    mapClass = getMapperClass(context);\n    if (LOG.isDebugEnabled()) {\n      LOG.debug(\"Configuring multithread runner to use \" + numberOfThreads + \n                \" threads\");\n    }\n    ","sourceCodeStart":102,"sourceCodeEnd":138,"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/lib/map/MultithreadedMapper.java#L102-L138","documentation":"MultithreadedMapper.setMapperClass (MultithreadedMapper.java:114-121) validates that the user mapper class is not itself a MultithreadedMapper (or subclass) and otherwise throws IllegalArgumentException. Nesting would spawn thread pools per thread with unbounded recursion, so the check fails fast.","triggerScenarios":"Calling MultithreadedMapper.setMapperClass(job, cls) where MultithreadedMapper.class.isAssignableFrom(cls) — i.e. cls is MultithreadedMapper itself or any subclass, including a user class extending MultithreadedMapper to override hooks; also setMapperClass(MultithreadedMapper.class) by mistake instead of the application mapper.","commonSituations":"Copy-paste from a mapper that extended MultithreadedMapper (old pattern) into new API code; intending to customize threading by subclassing MultithreadedMapper and then registering the subclass as the inner mapper; confusion between job.setMapperClass(MultithreadedMapper.class) (correct, the runner) and the inner setMapperClass (must be the business mapper).","solutions":["Set the runner once: job.setMapperClass(MultithreadedMapper.class), then set the application mapper via MultithreadedMapper.setMapperClass(job, MyBusinessMapper.class)","If you subclassed MultithreadedMapper for customization, register the subclass with job.setMapperClass (the runner slot), not with MultithreadedMapper.setMapperClass","For custom threading behavior prefer mapreduce.mapper.multithreadedmapper.* settings (mapreduce.map.multithreadedmapper.threads / runners) over subclassing","Check the exception at job setup time — it is thrown client-side during configuration, so fix is always in driver code"],"exampleFix":"// before\njob.setMapperClass(MultithreadedMapper.class);\nMultithreadedMapper.setMapperClass(job, MyPoolingMapper.class); // MyPoolingMapper extends MultithreadedMapper -> throws\n\n// after\npublic class MyPoolingMapper extends MultithreadedMapper<Text, Text, Text, Text> {\n  // customization via overrides of hooks (optional)\n}\njob.setMapperClass(MyPoolingMapper.class);                        // runner in the normal slot\nMultithreadedMapper.setMapperClass(job, BusinessMapper.class);    // plain Mapper, no nesting","handlingStrategy":"validation","validationCode":"static void configureMultithreadedMapper(Job job, Class<? extends Mapper<?,?,?,?>> userMapper) {\n  if (MultithreadedMapper.class.isAssignableFrom(userMapper))\n    throw new IllegalArgumentException(\"Application mapper must not extend MultithreadedMapper: \" + userMapper.getName());\n  job.setMapperClass(MultithreadedMapper.class);\n  MultithreadedMapper.setMapperClass(job, userMapper);\n}","typeGuard":"static boolean isRecursiveMapper(Class<?> cls) { return MultithreadedMapper.class.isAssignableFrom(cls); }","tryCatchPattern":"try { MultithreadedMapper.setMapperClass(job, cls); } catch (IllegalArgumentException e) { if (e.getMessage().contains(\"recursive\")) throw new IllegalArgumentException(\"Passed a MultithreadedMapper subclass as the inner mapper — put it in job.setMapperClass instead\", e); throw e; }","preventionTips":["job.setMapperClass gets the MultithreadedMapper runner; MultithreadedMapper.setMapperClass gets the plain business mapper","Do not extend MultithreadedMapper to customize threading — use mapreduce.map.multithreadedmapper.* settings","Add a driver unit test asserting the configured mapper pair"],"tags":["hadoop","mapreduce","multithreaded-mapper","configuration","recursion-guard"],"backgroundTag":"recursive-wrapper-misconfiguration","analyzedSha":"2add9630210752f88ceb1bb74eb65e37bf41da8e","analyzedAt":"2026-08-22T19:55:07.957Z","schemaVersion":2},"datasetVersion":"2026-08-23T01:17:44.959Z"}