OpenAPITools/openapi-generator · error · ResponseStatusException

I/O error while reading file

Error message

I/O error while reading file

What it means

Thrown by GET /api/gen/download/{fileId} when Files.readAllBytes (GenApiService.java:103) fails with an IOException other than FileNotFoundException, e.g. EACCES/permission denial, the path being unreadable, or an OS-level I/O error. The fileMap entry passed both the existence and TTL checks, so this points at server-side filesystem state rather than client input. It maps to HTTP 500 because the caller did nothing wrong.

Source

Thrown at modules/openapi-generator-online/src/main/java/org/openapitools/codegen/online/service/GenApiService.java:107

    @Override
    public ResponseEntity<Resource> downloadFile(String fileId) {
        Generated g = fileMap.get(fileId);
        LOGGER.debug("looking for fileId {}", fileId);
        if (g == null || g.getCreatedAt().plusMillis(FILE_TTL_MS).isBefore(Instant.now())) {
            throw new ResponseStatusException(HttpStatus.NOT_FOUND, "File not found or has expired");
        }
        LOGGER.debug("got filename {}", g.getFilename());

        File file = new File(g.getFilename());
        Path path = Paths.get(file.getAbsolutePath());
        ByteArrayResource resource;
        try {
            resource = new ByteArrayResource(Files.readAllBytes(path));
        } catch (FileNotFoundException e) {
            throw new ResponseStatusException(HttpStatus.NOT_FOUND, "File not found", e);
        } catch (IOException e) {
            throw new ResponseStatusException(HttpStatus.INTERNAL_SERVER_ERROR, "I/O error while reading file", e);
        }
        try {
            FileUtils.deleteDirectory(file.getParentFile());
        } catch (IOException e) {
            LOGGER.error("failed to delete file {}", file.getAbsolutePath());
        }
        return ResponseEntity
                .ok()
                .contentType(MediaType.valueOf("application/zip"))
                .contentLength(resource.contentLength())
                .header("Content-Disposition",
                        "attachment; filename=\"" + g.getFriendlyName() + "-generated.zip\"")
                .header("Accept-Range", "bytes")
                .body(resource);
    }

    @Override
    public ResponseEntity<ResponseCode> generateClient(String language, GeneratorInput generatorInput) {

View on GitHub (pinned to fcec517be3)

Solutions

  1. Retry once by regenerating and downloading a fresh fileId (transient races with the cleaner resolve themselves)
  2. If self-hosting, verify the service user can read java.io.tmpdir/codegen-tmp-* (ls -ld, run as that user)
  3. Mount a dedicated writable volume or tmpfs at the tmp location with enough size for generated zips
  4. Check server logs for the stack trace carried in the ResponseStatusException cause to confirm the exact errno

Example fix

# before
curl -fOJ "$host/api/gen/download/$code" || exit 1   # 500 I/O error aborts the pipeline

# after: one regenerate+download attempt, then give up with a clear message
zip=$(curl -sfOJ "$host/api/gen/download/$code" && echo ok)
if [ -z "$zip" ]; then
  code=$(curl -s -X POST "$host/api/gen/clients/java" -H 'Content-Type: application/json' \
    -d "{\"spec\":$SPEC}" | sed -E 's/.*"code":"([^"]+)".*/\1/')
  curl -fOJ "$host/api/gen/download/$code" || { echo "generator download failed twice" >&2; exit 1; }
fi
Defensive patterns

Strategy: retry

Try / catch

catch (HttpServerErrorException e) {
    // transient server-side fs race: one fresh generate+download usually clears it
    if (e.getStatusCode() == HttpStatus.INTERNAL_SERVER_ERROR
            && e.getResponseBodyAsString().contains("I/O error while reading file")) {
        fileId = generate(host, language, spec);
        retryDownload(fileId);
    } else { throw e; }
}

Prevention

When it happens

Trigger: The tmp directory or zip file permissions changed between generation and download; the directory was deleted by the hourly cleaner or another process in the race window after the map lookup but before/during the read; tmpfs exhaustion or disk fault while reading; security policy (SELinux/AppArmor) blocking reads from java.io.tmpdir.

Common situations: Containers where /tmp is a size-limited tmpfs that evicts files under pressure; hardened hosts restricting the service user's tmp access; multiple instances sharing a volume while fileMap is per-JVM; disk-full conditions on the server.

Related errors


AI-assisted analysis of OpenAPITools/openapi-generator@fcec517be3 (2026-08-22). Data as JSON: /api/errors/4249459315ad9196. Report an issue: GitHub.