Welcome to the Hangar Open Beta. Please report any issue you encounter on GitHub!
Avatar for PlanetDogeCodes

Changes the way Minecraft stores region (mca) files, leading to massive reductions in file size.

Report WorldRegionOptimizer?

World/Region Optimizer

A Minecraft 1.12 Paper plugin that losslessly compresses and repacks region (.mca) files, reducing world folder sizes by 40–60% with near-zero impact on gameplay, builds, entities, or world data.


Table of Contents


Overview

Minecraft stores its world data in Anvil region files (.mca). Each file contains up to 1,024 chunks compressed individually with zlib at the lowest compression level (level 1 — fastest). Over time these files accumulate wasted space from:

  • Low-effort compression (Minecraft prioritises write speed over size)
  • Empty chunk sections that were never cleaned up (all-air 16×16×16 volumes stored explicitly)
  • Redundant cached data (height maps that Minecraft recalculates anyway)
  • Sector fragmentation gaps left by incremental chunk updates

RegionOptimizer processes your world files offline (or while idle) and addresses all four sources of waste without touching a single byte of actual world content.


How It Works

Optimization Layers

All four optimizations are completely lossless — Minecraft reads the output files identically to unoptimized ones.

1 — Zlib Recompression ~Up to 20–30% savings

Minecraft writes chunk data at Deflate level 1 (fastest, worst compression). RegionOptimizer recompresses every chunk at Deflate level 9 (maximum compression), producing significantly smaller output in the same format. Minecraft reads both identically — only the compressed bytes change.

2 — Empty Section Pruning ~Up to 5–20% savings

A Minecraft chunk is divided vertically into 16 sections (each 16×16×16 blocks). The upper sections of most chunks — sky, void, unexplored heights — are entirely air. Minecraft stores these explicitly as arrays of 4,096 zero-bytes, even though the Anvil format specification guarantees that a missing section is treated as all-air.

RegionOptimizer removes any section where:

  • Blocks[4096] is entirely zero (no block IDs)
  • Add[2048] is absent or entirely zero (no extended block IDs > 255)

This is identical to how newly generated chunks are stored — Minecraft has handled missing sections as air since the Anvil format was introduced in 1.2.

3 — HeightMap Removal ~Up to 3–5% savings

Every chunk stores a HeightMap tag: a 256-integer array (1 KB) caching the topmost solid block for each column. Minecraft always recalculates this on next chunk load when the tag is absent — it's a pure performance cache, never a source of truth. Removing it has no gameplay or visual effect.

4 — Region File Repacking ~Up to 5–10% savings

Region files store chunks in 4 KB sectors. When a chunk grows (a player builds, adds entities, etc.) and needs more sectors, Minecraft allocates new sectors at the end of the file and leaves the old location empty. These gaps accumulate over time.

RegionOptimizer rewrites every region file with tightly-packed sectors in a fresh file, then atomically replaces the original. No gaps, no wasted space.


What Is Never Changed

The following data is guaranteed to be preserved verbatim through every optimization pass:

Data Notes
Block IDs and Data values Every placed block, every build, unchanged
Entities All mobs, dropped items, projectiles
Tile Entities Chests, furnaces, signs, command blocks, spawners
Biomes Grass colour, mob spawning, rain behaviour
SkyLight / BlockLight Kept to avoid lighting recalculation lag on load
TileTicks / LiquidTicks Scheduled block updates (sand falling, water flowing)
TerrainPopulated Prevents duplicate feature generation
LightPopulated Lighting state flag
InhabitedTime Time players have spent in a chunk

Safety Guarantees

RegionOptimizer is built around a safety-first architecture:

Automatic Backups

Before touching any region file, a .mca.bak backup is created automatically (configurable). If anything fails — read error, write error, verification mismatch — the backup is restored and the original file is left intact.

Post-Write Verification

After writing an optimized region, the plugin reads it back and confirms the chunk count matches the input exactly. If even one chunk is missing, the backup is restored automatically.

Atomic File Replacement

Optimized regions are written to a .tmp_XXXXXX temporary file first. Only after the write and verification succeed is the original replaced — using an atomic filesystem move where supported, with a safe fallback on unsupported filesystems. A crash or power loss during the write cannot corrupt the original.

Loaded-Chunk Safety Check

Before processing each region, the plugin queries the main thread to check whether any of the 1,024 chunks in that region are currently loaded by the server. If any are loaded, the region is skipped. This prevents races between the optimizer's atomic file-replace and Minecraft's own chunk-save writes.

Idle-Time Guard

Regions modified within the last N minutes (configurable, default: 5) are skipped automatically, providing a secondary defence against active-server writes.

Per-Chunk Error Isolation

Each chunk is validated individually before reading (bounds checks on sector offsets, data lengths, and file EOF). Invalid or truncated entries are skipped with a warning — a single corrupt chunk entry never aborts the entire region.

Chunk-Level Fallback

During NBT optimization, if anything goes wrong decompressing, parsing, or recompressing a single chunk, that chunk is returned unchanged (original compressed bytes preserved). No chunk is ever corrupted by an optimizer error.


Installation

  1. Make sure you are running Paper 1.12+ (or a compatible fork).
  2. Drop region-optimizer-1.0.0.jar into your server's plugins/ folder.
  3. Start or restart the server. A default config.yml is created automatically.
  4. Use the commands below to run your first optimization.

Recommended: Run optimizations while no players are online, or stop the server, optimize offline, and restart. The plugin has safeguards for live servers, but offline operation is always the safest approach.


Commands

Main command: /regionoptimize — Aliases: /ro, /regionopt

Permission node: regionoptimizer.use (default: op)


/regionoptimize world <worldname> [--no-backup]

Optimizes every .mca file in the given world's region/ folder.

/regionoptimize world world
/regionoptimize world world_nether
/regionoptimize world world_the_end --no-backup
  • Runs fully asynchronously — the main thread is never blocked.
  • Progress is broadcast to all players with regionoptimizer.use permission at a configurable interval.
  • If --no-backup is passed, .mca.bak files are not created. Only use this if you have your own backup solution.
  • Regions with loaded chunks or recent writes are automatically skipped and reported.
  • A detailed final report is broadcast when complete.

Example final report:

✔ Optimization complete for world 'world'!
  Regions  : 312 processed, 0 failed
  Chunks   : 89,341 optimized, 6,247 unchanged
  Size     : 4.82 GB → 2.14 GB (55.6% reduction, 2.68 GB saved)
  Empty sections removed : 1,204,816
  HeightMaps removed     : 89,341
  Time elapsed           : 16m 12s

/regionoptimize status

Shows the progress of the currently-running optimization task.

/regionoptimize status

Output example:

[RegionOptimizer] Running: Processing region 147/312: r.4.-2.mca (38.2% size reduction so far)

/regionoptimize stats [worldname]

Shows the disk usage of region files for a world (or all loaded worlds if no name is given). Also reports any backup files still on disk.

/regionoptimize stats
/regionoptimize stats world
/regionoptimize stats world_nether

Output example:

[RegionOptimizer] world: 312 regions, 4.82 GB total
[RegionOptimizer] world_nether: 48 regions, 312.4 MB total (+12 backups using 312.4 MB)

/regionoptimize cancel

Requests cancellation of the running optimization task. The current region finishes before stopping; partial results up to that point are kept.

/regionoptimize cancel

Configuration

plugins/RegionOptimizer/config.yml:

optimization:
  # Zlib compression level for chunk recompression.
  # Minecraft default is 1 (fastest). Level 9 (maximum) gives the best compression
  # at the cost of more CPU time during the one-time optimization pass.
  # This only affects the optimization run — normal gameplay uses Minecraft's own level.
  # Range: 1–9  |  Default: 9
  compression-level: 9

  # Remove empty (all-air) chunk sections from NBT data.
  # Minecraft treats a missing section as all-air — this is guaranteed Anvil behaviour.
  # Default: true
  remove-empty-sections: true

  # Remove the HeightMap array from each chunk.
  # HeightMap (256 ints = 1 KB/chunk) is always recalculated by Minecraft on next load.
  # Default: true
  remove-heightmap: true

  # Repack region files to eliminate sector fragmentation gaps.
  # Default: true
  repack-region: true

  # Create a .mca.bak backup of each region file before optimizing.
  # STRONGLY recommended. Disable only if you have external backups.
  # Default: true
  create-backups: true

  # Delete backup files after each region is successfully verified.
  # Default: false  (keep backups until manually removed)
  delete-backups-after-success: false

# Progress broadcast interval in seconds.
# Default: 10
progress-interval-seconds: 10

# Maximum number of region files processed simultaneously.
# Higher values use more CPU and memory. Recommended: 1–4.
# Default: 2
parallel-regions: 2

# Skip regions modified within this many minutes of the optimization run.
# Provides a secondary guard against races with active server writes.
# Set to 0 to disable (not recommended on live servers).
# Default: 5
min-idle-minutes: 5

Building from Source

Requirements: Java 8+, Maven 3.6+

git clone https://github.com/yourorg/region-optimizer.git
cd region-optimizer
mvn clean package

The built jar is at target/region-optimizer-1.0.0.jar.

To run the test suite (no server required):

mvn clean compile test-compile
javac -cp target/classes -d target/test-classes \
  src/test/java/com/lifestealmc/regionoptimizer/RegionOptimizerTest.java
java -cp target/classes:target/test-classes \
  com.lifestealmc.regionoptimizer.RegionOptimizerTest

The test suite runs 450 checks covering:

  • NBT binary write/read round-trip for all tag types
  • Empty section detection and removal
  • HeightMap removal
  • Compression level improvement verification
  • Non-empty section and entity/biome/TileEntity preservation
  • MCA write → read round-trip with 100 chunks
  • Negative region coordinate filename parsing (r.-1.-2.mca)
  • Corrupt/truncated chunk entry handling
  • Full pipeline data integrity with 50 chunks, 200 section removals, 50 heightmap removals

Technical Reference

MCA File Format

Byte offset   Size     Description
───────────────────────────────────────────────────────
0             4,096    Location table  — 1,024 × 4 bytes
                         Bits 31–8 : sector offset (big-endian 3-byte uint)
                         Bits  7–0 : sector count  (1-byte uint, max 255)
4,096         4,096    Timestamp table — 1,024 × 4-byte unix timestamps
8,192+        N        Chunk data blocks, each padded to 4,096-byte boundaries
                         4 bytes : payload length (includes compression type byte)
                         1 byte  : compression type (1 = gzip, 2 = zlib)
                         N bytes : compressed NBT data
───────────────────────────────────────────────────────

Sector size  = 4,096 bytes
Header size  = 8,192 bytes (2 sectors)
Max sectors  = 16,777,215 (3-byte offset field)
Max chunk sz = 1,044,480 bytes (255 sectors × 4,096)

Absent chunk: offset == 0 AND count == 0 in the location table.
Chunk index : (localX & 31) + (localZ & 31) * 32

NBT Chunk Structure

The relevant tags in a 1.12 chunk compound (simplified):

TAG_Compound ("")
  TAG_Compound ("Level")
    TAG_Int        "xPos"             chunk X coordinate
    TAG_Int        "zPos"             chunk Z coordinate
    TAG_Long       "LastUpdate"       last tick this chunk was saved
    TAG_Long       "InhabitedTime"    cumulative player-presence ticks
    TAG_Byte       "TerrainPopulated" whether terrain features have been placed
    TAG_Byte       "LightPopulated"   whether lighting has been calculated
    TAG_Byte_Array "Biomes"           [256] biome IDs — ONE PER COLUMN
    TAG_Int_Array  "HeightMap"        [256] highest solid Y per column  ← REMOVED
    TAG_List       "Sections"         list of up to 16 section compounds
      TAG_Compound
        TAG_Byte       "Y"            section index 0–15
        TAG_Byte_Array "Blocks"       [4096] block IDs          ← CHECKED FOR AIR
        TAG_Byte_Array "Add"          [2048] high block ID nibbles (optional) ← CHECKED
        TAG_Byte_Array "Data"         [2048] block data nibbles
        TAG_Byte_Array "BlockLight"   [2048] block light nibbles
        TAG_Byte_Array "SkyLight"     [2048] sky light nibbles
    TAG_List       "Entities"         entity compounds           ← PRESERVED
    TAG_List       "TileEntities"     tile entity compounds      ← PRESERVED
    TAG_List       "TileTicks"        scheduled block ticks      ← PRESERVED

Tags marked ← REMOVED are stripped during optimization.
Tags marked ← CHECKED are inspected to confirm the section is all-air before removal.
Tags marked ← PRESERVED are never read, modified, or removed.

Architecture

com.lifestealmc.regionoptimizer
│
├── RegionOptimizerPlugin.java       Plugin entry point, lifecycle management
│
├── command/
│   └── OptimizeCommand.java         /regionoptimize handler + tab completion
│
├── nbt/                             Self-contained NBT read/write library
│   ├── NBTBase.java                 Abstract base with tag type constants
│   ├── NBTTagByte/Short/Int/...     Primitive tag implementations
│   ├── NBTTagByteArray.java         Byte array tag (includes isAllZero() helper)
│   ├── NBTTagIntArray.java          Int array tag
│   ├── NBTTagString.java            UTF-8 string tag
│   ├── NBTTagList.java              Homogeneous ordered list
│   ├── NBTTagCompound.java          Key-value map with typed getters/setters
│   ├── NBTReader.java               Binary NBT deserialiser (stream → object tree)
│   └── NBTWriter.java               Binary NBT serialiser (object tree → stream)
│
├── region/
│   ├── ChunkEntry.java              Immutable value type: coordinates + compressed bytes
│   └── MCARegion.java               MCA read/write with bounds checking and atomic replace
│
├── optimizer/
│   ├── ChunkOptimizer.java          Per-chunk NBT optimisation + recompression
│   │   └── ChunkOptimizeResult      Result type: entry + sections removed + heightmap flag
│   ├── RegionOptimizer.java         Per-region pipeline: backup → optimize → verify → stats
│   └── OptimizationStats.java       Thread-safe AtomicLong counters for all metrics
│
└── task/
    └── WorldOptimizeTask.java       Async Runnable: iterates regions, checks safety,
                                     reports progress, handles parallel execution

Threading model:

  • All file I/O and NBT processing runs on a dedicated async thread pool — the Bukkit main thread is never blocked.
  • The loaded-chunk safety check (world.isChunkLoaded) is dispatched to the main thread via callSyncMethod with a 10-second timeout.
  • OptimizationStats uses AtomicLong for all counters — safe for parallel region processing.
  • Progress reporting uses runTask to deliver messages on the main thread.

FAQ

Will this corrupt my world?
No. The plugin only modifies how data is compressed and stored — not the data itself. Builds, entities, and gameplay state are preserved exactly. Post-write verification confirms every chunk survived before the original file is replaced. If verification fails, the original is restored from the backup automatically.

Can I run this while players are online?
Technically yes, but offline is always safest. The loaded-chunk check and idle-time guard provide protection on live servers, but they have inherent race windows. For the best result, run /save-all, allow any active regions to go idle, then optimize. Even better: stop the server, optimize, restart.

How long does it take?
On typical hardware with parallel-regions: 2:

  • Small world (~50 regions): 1–2 minutes
  • Medium world (~300 regions): 5–15 minutes
  • Large world (1,000+ regions): 30–60 minutes

CPU is the bottleneck (level-9 compression is ~5× slower than level-1). Disk I/O is typically not a bottleneck since each region fits in L3 cache.

What if the server crashes during optimization?
Each region is written to a .tmp_XXXXXX file and renamed atomically only after verification. The worst case is that the current region's .tmp file is left on disk — it can be safely deleted. The original .mca and its .mca.bak are untouched until the rename succeeds.

Can I optimize again after new terrain is generated?
Yes. Each optimization run is independent. New regions written by Minecraft will be at level 1 compression — you can run the optimizer again at any time to reclaim space on those regions. Already-optimized regions will see smaller savings on repeat runs (compression cannot improve beyond level 9).

Does this affect Nether and End worlds?
Yes — any world with a region/ folder can be optimized. Use the world name exactly as it appears in your server (e.g., world_nether, world_the_end).

What about backups taking up double the space?
Set delete-backups-after-success: true in config.yml to delete backups automatically after each region is successfully verified. Or set create-backups: false if you manage your own external backups (e.g., nightly snapshots).

What is the Add array in a chunk section?
In Minecraft 1.12, block IDs are stored as one byte per block in the Blocks array. For modded block IDs > 255, a second Add nibble array extends the IDs to 12 bits. When checking whether a section is all-air, the plugin checks both arrays — a section is only removed if both Blocks and Add are entirely zero.


Requirements

Requirement Version
Minecraft server 1.12.2
Server software Paper (or compatible fork)
Java 8 or higher
Permissions plugin Optional (ops have access by default)

This plugin targets the 1.12 Anvil chunk format specifically. It will not run on 1.13+ servers, which use a different chunk format (Data Version, Palette, etc.).

Information

CategoryWorld Management
Published onJune 12, 2026
LicenseApache 2.0
Downloads0
Stars1
Watchers1

Pinned Versions

Members