Wiki 9Minecraft Wiki 9Minecraft

NetherNet Protocol and Add-On Networking

How Bedrock's WebRTC transport layer impacts add-on pack transfers, Script API timing, and LAN discovery.

4Sections
3Code examples
3Known errors

NetherNet is the transport-layer networking architecture in Minecraft Bedrock Edition that supplements and replaces legacy RakNet connections with WebRTC-based peer-to-peer and relay channels. While add-on creators do not write low-level network packets directly, understanding how NetherNet handles NAT traversal, pack streaming, and packet framing is essential when optimizing large resource packs and debugging multiplayer Script API synchronization.

What is NetherNet?

Historically, Minecraft Bedrock Edition relied entirely on RakNet over UDP for all multiplayer communication, from local LAN games to dedicated servers. NetherNet introduces a modern networking stack built around WebRTC data channels, ICE (Interactive Connectivity Establishment), STUN, and TURN relays. When two players connect over Xbox Live or Nintendo Switch Online—or when a client discovers a local host—the engine negotiates a NetherNet session identified by a 64-bit network ID rather than binding strictly to raw IPv4 sockets.

At the game layer, Bedrock serializes gameplay packets (such as entity movement, block updates, and resource pack chunks) using the same internal format regardless of the underlying transport. However, the transport characteristics of NetherNet differ from raw RakNet. Because traffic often routes through TURN relay servers when symmetric NATs block direct UDP hole-punching, latency variance (jitter) and maximum transmission unit (MTU) overhead behave differently. Add-on developers feel these differences most acutely during initial world join, when the host transfers behavior and resource packs, and during high-frequency Script API updates.

Layer / FeatureLegacy RakNetNetherNet (WebRTC)
Connection AddressingIPv4 / IPv6 address and UDP port (default 19132)64-bit NetherNet ID negotiated via signaling service
NAT TraversalBasic UDP hole-punchingICE framework with STUN and fallback TURN relays
LAN DiscoveryUDP broadcast pings on local subnetLocal signaling broadcast encapsulating session descriptors
EncryptionCustom ECDH handshake post-connectionDTLS (Datagram Transport Layer Security) native to WebRTC

Connection Lifecycle and Pack Synchronization

When a client joins a host over NetherNet, the signaling service exchanges Session Description Protocol (SDP) offers and ICE candidates. Once the DTLS handshake completes and the WebRTC data channel opens, the standard Bedrock login sequence begins. Immediately after authentication, the server sends the resource pack stack info to the client.

If the host requires the client to download an add-on they do not have cached, the host slices the zipped pack into chunks and streams them across the NetherNet data channel. Over a relayed TURN connection, available bandwidth is throttled compared to a direct UDP connection, and congestion control algorithms in the WebRTC stack will aggressively back off if the game thread floods the socket. If your add-on consists of a single monolithic resource pack containing tens of megabytes of uncompressed `.tga` textures and `.wav` audio files, the NetherNet SCTP/DTLS queue can stall, leading to a connection timeout before the client ever spawns into the world.

To ensure reliable transfers over NetherNet relays, keep individual pack archives modular, use `.ogg` for audio, prefer `.png` over `.tga` where possible, and declare strict dependencies in your manifest so the client caches base assets separately from frequent script updates.

BP/manifest.json
{
  "format_version": 2,
  "header": {
    "name": "NetherNet Optimized Behavior Pack",
    "description": "Separates heavy assets into the RP to keep BP chunk transfers fast.",
    "uuid": "a1b2c3d4-e5f6-4a5b-8c9d-0e1f2a3b4c5d",
    "version": [1, 0, 0],
    "min_engine_version": [1, 20, 80]
  },
  "modules": [
    {
      "type": "data",
      "uuid": "b2c3d4e5-f6a7-4b5c-9d0e-1f2a3b4c5d6e",
      "version": [1, 0, 0]
    },
    {
      "type": "script",
      "language": "javascript",
      "uuid": "c3d4e5f6-a7b8-4c5d-0e1f-2a3b4c5d6e7f",
      "entry": "scripts/main.js",
      "version": [1, 0, 0]
    }
  ],
  "dependencies": [
    {
      "uuid": "d4e5f6a7-b8c9-4d5e-1f2a-3b4c5d6e7f80",
      "version": [1, 0, 0]
    },
    {
      "module_name": "@minecraft/server",
      "version": "1.11.0"
    }
  ]
}

Handling NetherNet Handshake Delays in Script API

A common pitfall when testing add-ons exclusively in singleplayer or on local dedicated servers is assuming that a player entity is immediately ready to receive UI forms or camera instructions the moment they join. Over NetherNet—especially on consoles or mobile devices routing through a relay—there is a measurable delay between the server allocating the player actor (`playerJoin`) and the client completing chunk generation and closing the loading screen (`playerSpawn`).

If your script attempts to send a `ActionFormData` payload or play a client-side sound during `playerJoin`, the packet is queued in the NetherNet transport buffer while the client is still processing terrain chunks. In high-latency sessions, the client may silently drop UI packets that arrive before the local HUD is initialized. Always defer initial client-bound payloads until the `playerSpawn` after-event fires with `initialSpawn` set to `true`, and consider adding a short tick delay to allow the WebRTC channel to flush initial entity state synchronization.

BP/scripts/main.js
import { world, system } from "@minecraft/server";

// Track connection timing to diagnose slow NetherNet relay handshakes
const joinTimestamps = new Map();

world.afterEvents.playerJoin.subscribe((event) => {
  // At this stage, the NetherNet session is open and pack transfer is complete,
  // but the client is still on the loading screen awaiting level chunks.
  joinTimestamps.set(event.playerId, Date.now());
});

world.afterEvents.playerSpawn.subscribe((event) => {
  const { player, initialSpawn } = event;
  if (!initialSpawn) return;

  const joinTime = joinTimestamps.get(player.id);
  joinTimestamps.delete(player.id);

  const handshakeDuration = joinTime ? Date.now() - joinTime : 0;

  // Defer heavy client-bound packets by 20 ticks (1 second) so the WebRTC
  // data channel finishes flushing initial entity and property payloads.
  system.runTimeout(() => {
    if (!player.isValid()) return;

    player.sendMessage(
      `[NetDiag] Sync complete in ${handshakeDuration}ms. Initializing add-on state.`
    );
    player.setDynamicProperty("addon:net_ready", true);
  }, 20);
});

Bandwidth Budgets for Entity Properties and Custom Events

Every time a behavior pack modifies a synced actor property (defined under `properties` in the entity description with `client_sync: true`) or triggers a client-visible state change (such as `minecraft:skin_id` or `minecraft:variant`), the server queues a delta packet for all clients within simulation distance. Over NetherNet, packets are encapsulated inside DTLS records and SCTP chunks, which adds header overhead to every transmission.

When designing custom entities that communicate state to resource pack render controllers or animations, batch your state changes rather than toggling properties every tick. For example, if an entity needs to sync a custom mana bar or charge meter to a client-side Molang query (`query.property`), updating a float property 20 times a second across 50 entities will saturate a peer-to-peer host's upload bandwidth. Instead, quantize the value into discrete integer steps or rely on client-side Molang interpolation (`math.lerp`) between infrequent server updates.

BP/entities/synced_turret.json
{
  "format_version": "1.20.80",
  "minecraft:entity": {
    "description": {
      "identifier": "custom:synced_turret",
      "is_spawnable": true,
      "is_summonable": true,
      "properties": {
        "custom:charge_stage": {
          "type": "int",
          "range": [0, 4],
          "default": 0,
          "client_sync": true
        }
      }
    },
    "components": {
      "minecraft:physics": {},
      "minecraft:Health": {
        "value": 20,
        "max": 20
      }
    }
  }
}

Which versions this applies to

Applies to Minecraft Bedrock Edition 1.19.50 and newer, where NetherNet was rolled out for Xbox Live, PlayStation, Switch, and LAN peer-to-peer transport. No experimental toggle is required, as transport selection is handled automatically by the engine.

When it does not work

What you seeWhyThe fix
Clients joining a host's world with an add-on get stuck at 'Locating Server' or disconnect with 'Connection timed out' during the 'Downloading packs' phase.The peer-to-peer connection fell back to a TURN relay with strict bandwidth limits, and the add-on archive is too large or contains thousands of tiny loose files, causing the WebRTC SCTP stream to choke and miss keep-alive heartbeats.Compress textures, convert WAV audio to OGG, remove unused development files from the RP/BP folders, and bump the manifest version so clients cleanly re-cache the smaller archive.
Custom UI forms or initial chat messages sent on join never appear for remote players, even though they work every time for the host.The script subscribed to `playerJoin` instead of `playerSpawn`, or fired immediately on `playerSpawn` while the remote client's NetherNet channel was still congested with initial chunk and entity packets.Listen to `world.afterEvents.playerSpawn`, verify `event.initialSpawn === true`, and wrap the initial client-bound calls in a brief `system.runTimeout` delay.
A second Bedrock instance on the same PC cannot see a local LAN game when testing multiplayer add-on behavior.Windows network isolation (UWP loopback restrictions) or local firewall rules are blocking the UDP signaling broadcasts that NetherNet uses to advertise local session IDs.Ensure LoopbackExempt is enabled for the Minecraft UWP app via PowerShell (`CheckNetIsolation LoopbackExempt -a -n="Microsoft.MinecraftUWP_8wekyb3d8bbwe"`) and verify that local firewall rules permit UDP traffic on the private subnet.

Reference, straight from the game files

Read out of the game itself, so the ids match the version you are building for.