t8y2/dbx · error · IOException

H2 MVStore file is too small to contain valid headers: " + f

Error message

H2 MVStore file is too small to contain valid headers: " + file

What it means

readMvStoreFormat reads the first two 4KB blocks of a .mv.db file because MVStore stores a header in each of the first two blocks. If fewer than BLOCK_SIZE*2 bytes can be read the file cannot hold valid headers, so an IOException is thrown instead of parsing garbage.

Source

Thrown at agents/drivers/h2/src/main/java/com/dbx/agent/h2/H2FileFormatDetector.java:46

        if (pageStoreExists && mvStoreExists) {
            throw new IOException("Both H2 PageStore and MVStore files exist for " + base + "; choose an explicit H2 driver profile");
        }
        if (pageStoreExists) {
            return OptionalInt.of(1);
        }
        if (!mvStoreExists) {
            return OptionalInt.empty();
        }
        return OptionalInt.of(readMvStoreFormat(mvStore));
    }

    private static int readMvStoreFormat(Path file) throws IOException {
        byte[] bytes;
        try (InputStream input = Files.newInputStream(file)) {
            bytes = input.readNBytes(BLOCK_SIZE * 2);
        }
        if (bytes.length < BLOCK_SIZE * 2) {
            throw new IOException("H2 MVStore file is too small to contain valid headers: " + file);
        }
        List<StoreHeader> headers = new ArrayList<>();
        for (int block = 0; block < 2; block++) {
            StoreHeader header = parseHeader(bytes, block * BLOCK_SIZE);
            if (header != null) {
                headers.add(header);
            }
        }
        return headers.stream()
            .max(Comparator.comparingLong(StoreHeader::version))
            .orElseThrow(() -> new IOException("Cannot determine the H2 MVStore format for " + file + "; choose an explicit H2 driver profile"))
            .format();
    }

    private static StoreHeader parseHeader(byte[] fileBytes, int offset) {
        byte[] block = java.util.Arrays.copyOfRange(fileBytes, offset, offset + BLOCK_SIZE);
        int start = 0;
        int end = block.length;

View on GitHub (pinned to c0390bff16)

Solutions

  1. Restore the .mv.db file from backup or re-copy it in full
  2. Delete the truncated file if it is a leftover from a failed start and let H2 recreate the database
  3. Check available disk space and re-run the copy with verification (checksum)

Example fix

// before
ls -la /data/app.mv.db  # 1024 bytes (truncated)
// after
cp /backups/app.mv.db /data/app.mv.db  # full copy, > 8KB
Defensive patterns

Strategy: validation

Validate before calling

Path mv = Path.of(base + ".mv.db");
if (Files.isRegularFile(mv) && Files.size(mv) < 8192) {
    throw new IllegalStateException(mv + " is truncated/empty; restore from backup");
}

Try / catch

try {
    return H2Agent.connect(params);
} catch (IOException e) {
    if (e.getMessage().contains("too small to contain valid headers")) {
        // restore from backup or remove leftover file
    }
    throw e;
}

Prevention

When it happens

Trigger: detect() found a .mv.db file but readMvStoreFormat read < 8192 bytes — i.e. the file exists but is truncated, empty, or nearly empty.

Common situations: Zero-byte .mv.db created by a failed H2 startup; copy of the database interrupted mid-transfer; disk-full during a write truncated the file; empty placeholder file created by a script.

Related errors


AI-assisted analysis of t8y2/dbx@c0390bff16 (2026-09-05). Data as JSON: /api/errors/5886a066ed2e5ef9. Report an issue: GitHub.