{"record":{"id":"296e06db8503038c","repo":"lingochamp/FileDownloader","slug":"the-download-runnable-must-not-be-null","errorCode":null,"errorMessage":"the download runnable must not be null!","messagePattern":"the download runnable must not be null!","errorType":"validation","errorClass":"IllegalArgumentException","httpStatus":null,"severity":"error","filePath":"library/src/main/java/com/liulishuo/filedownloader/download/DownloadLaunchRunnable.java","lineNumber":726,"sourceCode":"\n            final DownloadRunnable runnable = builder\n                    .setId(id)\n                    .setConnectionIndex(connectionModel.getIndex())\n                    .setCallback(this)\n                    .setUrl(url)\n                    .setEtag(withEtag ? etag : null)\n                    .setHeader(userRequestHeader)\n                    .setWifiRequired(isWifiRequired)\n                    .setConnectionModel(connectionProfile)\n                    .setPath(path)\n                    .build();\n\n            if (FileDownloadLog.NEED_LOG) {\n                FileDownloadLog.d(this, \"enable multiple connection: %s\", connectionModel);\n            }\n\n            if (runnable == null) {\n                throw new IllegalArgumentException(\"the download runnable must not be null!\");\n            }\n\n            downloadRunnableList.add(runnable);\n        }\n\n        if (totalOffset != model.getSoFar()) {\n            FileDownloadLog.w(this, \"correct the sofar[%d] from connection table[%d]\",\n                    model.getSoFar(), totalOffset);\n            model.setSoFar(totalOffset);\n        }\n\n        List<Callable<Object>> subTasks = new ArrayList<>(downloadRunnableList.size());\n        for (DownloadRunnable runnable : downloadRunnableList) {\n            if (paused) {\n                runnable.pause();\n                continue;\n            }\n            subTasks.add(Executors.callable(runnable));","sourceCodeStart":708,"sourceCodeEnd":744,"githubUrl":"https://github.com/lingochamp/FileDownloader/blob/6237a8cac174bcc916e4342b14ab1ab72a5768d4/library/src/main/java/com/liulishuo/filedownloader/download/DownloadLaunchRunnable.java#L708-L744","documentation":"An IllegalArgumentException thrown in DownloadLaunchRunnable.fetchWithMultipleConnection when one of the per-range DownloadRunnable instances built for a multi-connection download is null. This is an internal invariant check: every slice of a multi-connection download must have a runnable before it is added to downloadRunnableList. Seeing it usually means the connection model list or runnable creation produced a null entry, typically due to corrupted breakpoint models.","triggerScenarios":"Resuming a download with multiple connections where the stored breakpoint/connection models (ConnectionModel from the database) are inconsistent or partially deleted, so a range chunk gets no runnable assigned before fetchWithMultipleConnection iterates the chunks.","commonSituations":"Database corruption or a version upgrade of FileDownloader leaving stale/invalid breakpoint records; resuming a task whose .temp file or breakpoint DB row was deleted mid-flight; concurrent access to the same download ID from multiple processes.","solutions":["Clear the download's breakpoint data (FileDownloader.getImpl().clear(taskId, path)) and restart the download from scratch","Ensure the target file path and task are removed from the FileDownloader DB before resuming (delete the file and re-create the task)","Check for concurrent downloads of the same ID/path from multiple processes and serialize them (use FileDownloadActivity/Service or a global lock)","Upgrade to the latest FileDownloader version, which hardens breakpoint model consistency","If reproducible, file a bug with the DB contents for the task ID"],"exampleFix":"// before: blindly resuming a possibly-corrupt task\nFileDownloader.getImpl().create(url).setPath(path).start();\n// after: clear stale breakpoint state when resuming fails\nFileDownloader.getImpl().clear(taskId, path);\nnew File(path + \".temp\").delete();\nFileDownloader.getImpl().create(url).setPath(path).start();","handlingStrategy":"validation","validationCode":"// Before resuming, ensure breakpoint state is consistent\nFileDownloadTask task = FileDownloader.getImpl().create(url).setPath(path);\nFile temp = new File(path + \".temp\");\nif (!temp.exists()) {\n    FileDownloader.getImpl().clear(taskId, path); // drop stale breakpoint rows\n}","typeGuard":null,"tryCatchPattern":"try {\n    FileDownloader.getImpl().create(url).setPath(path).start(listener);\n} catch (IllegalArgumentException e) {\n    FileDownloader.getImpl().clear(taskId, path);\n    new File(path + \".temp\").delete();\n    FileDownloader.getImpl().create(url).setPath(path).start(listener); // fresh start\n}","preventionTips":["Clear breakpoint data whenever the .temp file is missing or the DB row looks stale","Avoid downloading the same task ID/path from multiple processes concurrently","Keep FileDownloader upgraded to versions with breakpoint-consistency fixes","Validate any restored task state before resuming"],"tags":["android","file-download","internal-state","multi-connection"],"backgroundTag":"internal-invariant-violation","analyzedSha":"6237a8cac174bcc916e4342b14ab1ab72a5768d4","analyzedAt":"2026-09-08T23:50:48.168Z","contentChangedAt":"2026-09-08T23:50:48.168Z","schemaVersion":2},"datasetVersion":"2026-09-16T09:17:16.951Z"}