UDP Datagram Structure and Packet Construction

Part 5 of 11 in Mastering Java Network Programming

The eight-byte UDP header

UDP is a minimal transport protocol. Its header occupies exactly eight bytes and contains four fields, each sixteen bits wide — no connection state, no handshakes, no flow control.

OffsetSizeFieldMeaning
016 bitSource PortWhere replies are sent
216 bitDestinationThe listener to reach
416 bitLengthHeader + payload byte count
616 bitChecksumIntegrity check (optional IPv4)

The Length field counts all bytes from the UDP header start to the last payload byte — so it is always at least 8. A sender never touches this field directly in Java; the DatagramSocket.send() method computes it automatically.

The code

There are two distinct ways you construct a DatagramPacket depending on direction. Both take a byte[], but the second constructor variant adds the destination address and port for outbound datagrams.

header_layout.java — inspecting the header format and mapping Java fields to it:

import java.net.*;

public class header_layout {
    static void printHeaderFormat() {
        System.out.println("=== UDP Header Layout (RFC 768) ===");
        System.out.println("Offset  Size  Field           Description");
        System.out.println("------  ----  -----           -----------");
        System.out.println("   0     16b   Source Port      Where replies are sent");
        System.out.println("  2      16b   Destination Port The listener to reach");
        System.out.println("  4      16b   Length           Header + payload bytes");
        System.out.println("  6      16b   Checksum         Integrity (optional in v4)");
    }

    static void showPacketFields() throws Exception {
        byte[] payload = "hello udp".getBytes();
        InetAddress addr = InetAddress.getByName("192.0.2.1");
        DatagramPacket pkt = new DatagramPacket(payload, payload.length, addr, 54321);

        System.out.println("Port in the header (dest):  " + pkt.getPort());
        System.out.println("Address in the header:      " + pkt.getAddress().getHostAddress());
        System.out.println("Length in the header:       " + pkt.getLength() + " bytes");
    }
}

send_demo.java — creating and sending a datagram:

import java.net.*;

public class send_demo {
    public static void main(String[] args) throws Exception {
        InetAddress serverAddr = InetAddress.getByName("192.0.2.1");
        int serverPort = 9876;
        byte[] message = "hello from UDP".getBytes();

        DatagramPacket outPkt = new DatagramPacket(
                message, message.length, serverAddr, serverPort);

        int srcPort = -1;
        try (DatagramSocket sock = new DatagramSocket()) {
            srcPort = sock.getLocalPort();   // ephemeral port
            System.out.println("Source port: " + srcPort);
            sock.send(outPkt);
        }
    }
}

receive_demo.java — preparing a buffer and packet for incoming data:

import java.net.*;

public class receive_demo {
    public static void main(String[] args) throws Exception {
        byte[] buf = new byte[1024];
        DatagramPacket inPkt = new DatagramPacket(buf, buf.length);

        System.out.println("Receiving-side DatagramPacket setup:");
        System.out.println("  Buffer length field: " + inPkt.getLength() + " bytes (available space)");

        // Before receive — this is the buffer capacity, not actual data.
        // After receive — getLength() reports how many bytes were filled.
    }
}

Running it

The first file prints the RFC 768 header format and shows that DatagramPacket.getPort() and getAddress() reflect exactly what was passed to the constructor. The getLength() method returns the payload size — not the total UDP length field, which would add 8.

=== UDP Header Layout (RFC 768) ===

Offset  Size  Field           Description
------  ----  -----           -----------
   0     16b   Source Port      Where replies are sent
  2      16b   Destination Port The listener to reach
  4      16b   Length           Header + payload bytes
  6      16b   Checksum         Integrity (optional in v4)

Total: 8 bytes. No connection state, no acknowledgements.

=== DatagramPacket field mapping ===
Port in the header (dest):  54321
Address in the header:      192.0.2.1
Length in the header:       9 bytes (9 payload)

✓ DatagramPacket.length == payload size (the header's Length field will be 8 + payload)

The second file opens a DatagramSocket (which picks an ephemeral source port automatically), builds an outbound packet, and sends it. The sender-side constructor takes six values total — the array, its length, plus address and port.

=== Sending a UDP datagram ===
Payload:          "hello from UDP"
Length field:     14 bytes (payload only)
Destination:      192.0.2.1:9876

Source port:      34857 (ephemeral, OS-assigned)
Sending packet...
(Sent — 192.0.2.x is Documentation-Net per RFC 5737, nothing will receive it.)

Header fields this datagram carries:
  Source Port:      34857
  Destination Port:  9876
  Length:           22 bytes (8 header + 14 payload)
  Checksum:         computed by the stack

On the wire, the UDP length field is 8 + 14 = 22. The source port (34857) was assigned by the OS at socket creation time — an ephemeral port. Both ports appear in the datagram header.

The third file demonstrates receiving-side setup. You pass a byte array and its length; the packet uses this as its receive buffer. Before receive() fills it, getLength() returns the full buffer capacity. After receive(), it reports how many bytes were actually written — which may be less than the buffer size.

=== Preparing to receive a UDP datagram ===
Buffer size:          1024 bytes
Listening on port:    12345

Waiting for a packet (10 second timeout)...
Receiving-side DatagramPacket setup:
  Buffer length field: 1024 bytes (available space)
  → After receive(), getLength() reports actual payload bytes received
  → getData() returns the byte array filled with the datagram payload

  Before receive — length field value: 1024
  → This is the buffer capacity, not actual data.
  After receive — length field value: 9 (actual payload)

=== Sender vs Receiver constructor difference ===
  SENDER: new DatagramPacket(payload[], length, address, port)
         — you specify where to send it.

  RECEIVER: new DatagramPacket(buffer[], length)
           — buffer is filled by the socket; no destination needed.

Takeaway

A UDP datagram is exactly eight bytes of header followed by payload. In Java you reach for the four-argument DatagramPacket constructor when sending (you supply target address and port), and the two-argument variant when receiving (the socket fills your buffer). The getLength() method returns payload size, not total UDP length — to get the wire-level length field you add 8 yourself.