OpenAPITools/openapi-generator · error · ResponseStatusException

File not found

Error message

File not found

What it means

Thrown by GET /api/gen/download/{fileId} when the fileMap entry exists and is fresh, but Files.readAllBytes (GenApiService.java:103) throws FileNotFoundException because the zip is no longer on disk. The key detail is that downloadFile deletes the file's parent directory right after a successful read (GenApiService.java:110), which makes every fileId strictly one-shot: the first GET succeeds and destroys the artifact, so any second GET for the same id fails here even within the 24h TTL.

Source

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

        return Optional.ofNullable(request);
    }

    @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);
    }

View on GitHub (pinned to fcec517be3)

Solutions

  1. Treat every download link as single-use: never re-GET a fileId that already returned 200
  2. If you need the artifact again, call the generate endpoint to obtain a fresh fileId
  3. On 404 from download, fall back to regenerate-then-download rather than re-fetching the same URL
  4. If self-hosting, keep tmp cleanup jobs away from codegen-tmp-* directories

Example fix

# before
curl -sOJ "$host/api/gen/download/$code"   # 200, and the server deletes the file
curl -sOJ "$host/api/gen/download/$code"   # 404 File not found (one-shot link)

# after
if [ ! -f "$code.zip" ]; then
  curl -sOJ "$host/api/gen/download/$code"   # fetch once, keep the local copy
fi
# need it again? generate a new one
code=$(curl -s -X POST "$host/api/gen/clients/java" -H 'Content-Type: application/json' \
  -d "{\"spec\":$SPEC}" | sed -E 's/.*"code":"([^"]+)".*/\1/')
Defensive patterns

Strategy: validation

Validate before calling

// links are one-shot: the server deletes the artifact after the first 200
Set<String> consumed = ConcurrentHashMap.newKeySet();
if (!consumued.add(fileId)) {
    throw new IllegalStateException("fileId already downloaded once; request a new generation");
}

Try / catch

catch (HttpClientErrorException.NotFound e) {
    if (e.getResponseBodyAsString().contains("File not found")) {
        // map entry fresh but zip gone -> it was already downloaded; regenerate
        fileId = generate(host, language, spec);
    }
}

Prevention

When it happens

Trigger: Calling GET /api/gen/download/{code} a second time for the same fileId (the first call already deleted the directory); an external process/cron wiping java.io.tmpdir where codegen-tmp-* directories live; the hourly cleaner racing between the map lookup and the read.

Common situations: Retry logic in scripts that re-fetch the same link after a flaky network response; two team members (or two jobs) sharing one generated link; browsers or proxies that re-issue the GET; curl without -o writing the first attempt somewhere unexpected and the user re-running it.

Understand the failure class

Background: "File not found" and ENOENT errors: why libraries can't find a file that should exist — this error's family across 50 libraries.

Related errors


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