UDP Sockets in Java: DatagramSocket, DatagramPacket & Multicast
UDP (User Datagram Protocol) is connectionless and unreliable (packets may be lost, reordered, or duplicated) but it is fast and lightweight. It is the foundation of DNS, DHCP, video streaming, and gaming protocols. Java exposes UDP through DatagramSocket and DatagramPacket.
UDP vs TCP: When to Use Each
| Characteristic | UDP | TCP |
|---|---|---|
| Connection setup | None (connectionless) | 3-way handshake |
| Reliability | Best-effort (no retransmit) | Guaranteed delivery |
| Ordering | Not guaranteed | In-order delivery |
| Overhead | 8-byte header | 20–60 byte header |
| Broadcast / Multicast | ✅ Supported | ❌ Not supported |
| Use cases | DNS, DHCP, VoIP, video, games | HTTP, FTP, SMTP, databases |
Unicast UDP Server & Client
A UDP "server" receives datagrams on a fixed port; a UDP "client" sends datagrams and optionally waits for a reply.
// UDP Server — receives a datagram and echoes it back
import java.net.*;
public class UdpEchoServer {
public static void main(String[] args) throws Exception {
try (DatagramSocket socket = new DatagramSocket(9999)) {
System.out.println("UDP server listening on port 9999");
byte[] buf = new byte[1024];
while (true) {
DatagramPacket request = new DatagramPacket(buf, buf.length);
socket.receive(request); // blocks until datagram arrives
String msg = new String(request.getData(), 0, request.getLength());
System.out.println("Received: " + msg);
// echo back to sender
byte[] response = ("ECHO: " + msg).getBytes();
DatagramPacket reply = new DatagramPacket(
response, response.length,
request.getAddress(), request.getPort());
socket.send(reply);
}
}
}
}
// UDP Client — sends a message and waits for the echo
import java.net.*;
public class UdpEchoClient {
public static void main(String[] args) throws Exception {
try (DatagramSocket socket = new DatagramSocket()) {
socket.setSoTimeout(3000); // 3 s receive timeout
InetAddress server = InetAddress.getByName("localhost");
byte[] data = "Hello UDP!".getBytes();
// send
DatagramPacket packet = new DatagramPacket(data, data.length, server, 9999);
socket.send(packet);
// receive echo
byte[] buf = new byte[1024];
DatagramPacket reply = new DatagramPacket(buf, buf.length);
socket.receive(reply);
System.out.println(new String(reply.getData(), 0, reply.getLength()));
}
}
}
Connected UDP Socket
Calling socket.connect(address, port) on a DatagramSocket restricts it to a single peer: datagrams from other addresses are silently discarded. It also lets the OS detect ICMP "port unreachable" errors as PortUnreachableException.
DatagramSocket socket = new DatagramSocket();
socket.connect(InetAddress.getByName("192.168.1.100"), 9999);
// send without specifying address — uses connected peer
byte[] data = "ping".getBytes();
socket.send(new DatagramPacket(data, data.length));
// receive — only accepts packets from connected peer
byte[] buf = new byte[256];
DatagramPacket pkt = new DatagramPacket(buf, buf.length);
socket.receive(pkt);
socket.disconnect(); // back to unconnected mode
UDP Broadcast
Broadcast sends a datagram to all hosts on the local subnet. The destination address is the subnet broadcast address (e.g. 255.255.255.255). setBroadcast(true) must be called on the sender.
// Broadcast sender
try (DatagramSocket socket = new DatagramSocket()) {
socket.setBroadcast(true);
byte[] data = "DISCOVER".getBytes();
InetAddress broadcast = InetAddress.getByName("255.255.255.255");
socket.send(new DatagramPacket(data, data.length, broadcast, 8888));
System.out.println("Broadcast sent");
}
// Broadcast receiver (binds to the broadcast port)
try (DatagramSocket socket = new DatagramSocket(8888)) {
socket.setBroadcast(true);
byte[] buf = new byte[256];
DatagramPacket pkt = new DatagramPacket(buf, buf.length);
socket.receive(pkt);
System.out.println("Discovered: " + pkt.getAddress());
}
IP Multicast with MulticastSocket
Multicast delivers datagrams to a group of subscribed hosts identified by a Class D IP address (224.0.0.0 – 239.255.255.255). Use MulticastSocket, a subclass of DatagramSocket, to join and leave groups.
// Multicast receiver — joins a group on a specific network interface
import java.net.*;
public class MulticastReceiver {
private static final String GROUP = "239.1.2.3";
private static final int PORT = 7777;
public static void main(String[] args) throws Exception {
InetAddress group = InetAddress.getByName(GROUP);
NetworkInterface ni = NetworkInterface.getByName("eth0"); // use your interface
try (MulticastSocket socket = new MulticastSocket(PORT)) {
socket.joinGroup(new InetSocketAddress(group, PORT), ni);
System.out.println("Joined multicast group " + GROUP);
byte[] buf = new byte[1024];
for (int i = 0; i < 5; i++) {
DatagramPacket pkt = new DatagramPacket(buf, buf.length);
socket.receive(pkt);
System.out.println("Got: " + new String(pkt.getData(), 0, pkt.getLength()));
}
socket.leaveGroup(new InetSocketAddress(group, PORT), ni);
}
}
}
// Multicast sender
public class MulticastSender {
public static void main(String[] args) throws Exception {
try (DatagramSocket socket = new DatagramSocket()) {
InetAddress group = InetAddress.getByName("239.1.2.3");
byte[] data = "Hello Multicast!".getBytes();
for (int i = 0; i < 5; i++) {
socket.send(new DatagramPacket(data, data.length, group, 7777));
Thread.sleep(500);
}
}
}
}
socket.setTimeToLive(n). TTL=1 stays on the local subnet; TTL=32 crosses regional networks.
Packet Size Limits & Application-Level Reliability
UDP datagrams are limited to 65,507 bytes (IPv4: 65,535 − 20 IP header − 8 UDP header). In practice keep payloads under 1,472 bytes to avoid IP fragmentation on a typical Ethernet MTU of 1,500 bytes.
| Concern | Solution |
|---|---|
| Packet loss | Application-level ACK + retransmit (like QUIC / custom ARQ) |
| Reordering | Sequence numbers in the payload; buffer and reorder on receiver |
| Duplication | Track seen sequence numbers; discard duplicates |
| Large messages | Fragmentation at application layer with reassembly buffer |
// Simple sequenced packet — prepend a 4-byte sequence number
ByteBuffer buf = ByteBuffer.allocate(4 + payload.length);
buf.putInt(seqNum++);
buf.put(payload);
byte[] data = buf.array();
socket.send(new DatagramPacket(data, data.length, address, port));
// Receiver: extract sequence number
DatagramPacket pkt = new DatagramPacket(new byte[1024], 1024);
socket.receive(pkt);
ByteBuffer received = ByteBuffer.wrap(pkt.getData(), 0, pkt.getLength());
int seq = received.getInt();
byte[] body = new byte[received.remaining()];
received.get(body);
Common Pitfalls
| Pitfall | Fix |
|---|---|
| Buffer too small → truncated data | Always allocate a buffer at least as large as the max expected datagram |
| Assuming delivery → silent data loss | Design for best-effort; add ACK if reliability matters |
| Multicast on wrong interface | Specify NetworkInterface explicitly in joinGroup |
| Firewall blocks multicast / broadcast | Open UDP ports; test on loopback first |
Not calling setBroadcast(true) | Broadcast sends silently fail or throw on some JVMs |
| Reusing DatagramPacket without resetting length | Reset pkt.setLength(buf.length) before each receive call |