{"record":{"id":"f8ae1e63428211e8","repo":"chinabugotech/hutool","slug":"file-length-is-but-read","errorCode":null,"errorMessage":"File length is [{}] but read [{}]!","messagePattern":"File length is \\[(.+?)\\] but read \\[(.+?)\\]!","errorType":"exception","errorClass":"IOException","httpStatus":null,"severity":"error","filePath":"hutool-core/src/main/java/cn/hutool/core/io/file/FileReader.java","lineNumber":126,"sourceCode":"\t * 文件的长度不能超过 {@link Integer#MAX_VALUE}\n\t *\n\t * @return 字节码\n\t * @throws IORuntimeException IO异常\n\t */\n\tpublic byte[] readBytes() throws IORuntimeException {\n\t\tlong len = file.length();\n\t\tif (len >= Integer.MAX_VALUE) {\n\t\t\tthrow new IORuntimeException(\"File is larger then max array size\");\n\t\t}\n\n\t\tbyte[] bytes = new byte[(int) len];\n\t\tFileInputStream in = null;\n\t\tint readLength;\n\t\ttry {\n\t\t\tin = new FileInputStream(file);\n\t\t\treadLength = in.read(bytes);\n\t\t\tif(readLength < len){\n\t\t\t\tthrow new IOException(StrUtil.format(\"File length is [{}] but read [{}]!\", len, readLength));\n\t\t\t}\n\t\t} catch (Exception e) {\n\t\t\tthrow new IORuntimeException(e);\n\t\t} finally {\n\t\t\tIoUtil.close(in);\n\t\t}\n\n\t\treturn bytes;\n\t}\n\n\t/**\n\t * 读取文件内容\n\t *\n\t * @return 内容\n\t * @throws IORuntimeException IO异常\n\t */\n\tpublic String readString() throws IORuntimeException{\n\t\treturn new String(readBytes(), this.charset);","sourceCodeStart":108,"sourceCodeEnd":144,"githubUrl":"https://github.com/chinabugotech/hutool/blob/8870454b2a0c29cc6ffd31dcf5667c8ceb2fc442/hutool-core/src/main/java/cn/hutool/core/io/file/FileReader.java#L108-L144","documentation":"After allocating a buffer of exactly file.length() bytes and calling FileInputStream.read(bytes), Hutool verifies that the number of bytes actually read equals the file length. If readLength < len it throws an IOException with the formatted length/read mismatch, which the surrounding catch (Exception e) wraps into IORuntimeException. This guards against three root causes: the file was truncated or rewritten between the length() call and the read (TOCTOU), the filesystem reports an inaccurate length (special files such as /proc/*, named pipes, device files), or a single read() returned early without filling the buffer.","triggerScenarios":"Calling FileReader.readBytes() (directly or via readString()) when: the file is concurrently modified/truncated between file.length() and in.read(); the path points at a special/proc or pseudo-file whose size() is wrong; FileInputStream.read(byte[]) returns fewer bytes than requested in a single invocation on some network/overlay filesystems; the file is actively being written/downloaded and shrinks mid-read.","commonSituations":"Concurrent log rotation truncating a file during read; reading Linux pseudo-files (/proc, /sys) that report 0 or stale lengths; reading a file still being streamed/downloaded; race conditions in multi-process pipelines; FUSE/network filesystems where read() may short-return.","solutions":["Ensure the file is not concurrently modified during the read (lock it, or copy it to a stable temp file first).","For special/pseudo files, stream with getInputStream()/readLines() instead of readBytes(), since their reported length is unreliable.","Wait until the file is fully written (download complete / writer closed) before reading.","If transient, retry the read once the file stabilizes; on persistent mismatch, inspect getCause() of the IORuntimeException for the underlying IOException."],"exampleFix":"// before: assumes file is stable and length() is accurate\nbyte[] data = FileReader.create(file).readBytes();\n\n// after: stream from a possibly-unstable / special file\ntry (InputStream in = FileReader.create(stableCopy).getInputStream()) {\n    // read incrementally; never trusts a single length() value\n    byte[] chunk = in.readNBytes(8192);\n}","handlingStrategy":"try-catch","validationCode":"// For special/proc files, skip readBytes entirely:\nif (file.getPath().startsWith(\"/proc\") || file.getPath().startsWith(\"/sys\")\n        || !file.isFile()) {\n    // stream instead; do NOT trust file.length()\n    return;\n}\n// For regular files that may be concurrently written, copy to a stable temp first:\nPath tmp = Files.createTempFile(\"read-\", \".tmp\");\ntry {\n    Files.copy(file.toPath(), tmp, StandardCopyOption.REPLACE_EXISTING);\n    byte[] data = FileReader.create(tmp.toFile()).readBytes();\n} finally {\n    Files.deleteIfExists(tmp);\n}","typeGuard":"boolean lengthIsReliable(File f) {\n    if (f == null || !f.isFile()) return false;\n    String p = f.getPath();\n    // pseudo-filesystems report inaccurate lengths\n    return !p.startsWith(\"/proc\") && !p.startsWith(\"/sys\") && !p.startsWith(\"/dev\");\n}","tryCatchPattern":"try {\n    byte[] data = FileReader.create(f).readBytes();\n} catch (IORuntimeException e) {\n    Throwable cause = e.getCause();\n    if (cause instanceof IOException && String.valueOf(cause.getMessage())\n            .contains(\"File length is\") && cause.getMessage().contains(\"but read\")) {\n        // likely truncation/short-read: stream instead, or retry after stabilization\n        try (InputStream in = new BufferedInputStream(new FileInputStream(f))) {\n            // consume fully with a read-loop\n        }\n    } else {\n        throw e;\n    }\n}","preventionTips":["Avoid concurrent truncation/rewrite of files you are reading with readBytes().","Never call readBytes() on /proc, /sys, /dev, pipes, or sockets.","Copy actively-written files to a temp location before reading them whole.","Wait for download/writer completion before reading.","Prefer streaming reads when the file's lifetime is not under your control."],"tags":["io","concurrency","race-condition","special-files","toctou","hutool"],"backgroundTag":null,"analyzedSha":"8870454b2a0c29cc6ffd31dcf5667c8ceb2fc442","analyzedAt":"2026-08-14T04:01:12.892Z","schemaVersion":2},"datasetVersion":"2026-08-14T05:17:29.042Z"}