Java Networking Fundamentals: URI, URL and Sockets

Addresses, sockets and streams. Underneath every network library in Java is a socket carrying bytes.

The layers you actually touch

Your code        HttpClient, a database driver, a message client
      |
Sockets          Socket, ServerSocket, SocketChannel
      |
TCP or UDP       the transport, provided by the operating system
      |
IP               addressing and routing

Most application code stays at the top layer. Understanding the socket underneath explains timeouts, connection refusals and why a stream sometimes returns fewer bytes than asked for.

URI and URL

URI uri = URI.create("https://example.com:8443/notes/42?tab=history#top");

System.out.println(uri.getScheme());     // https
System.out.println(uri.getHost());       // example.com
System.out.println(uri.getPort());       // 8443
System.out.println(uri.getPath());       // /notes/42
System.out.println(uri.getQuery());      // tab=history
System.out.println(uri.getFragment());   // top

URI resolved = uri.resolve("/notes/43");
URI relative = URI.create("/notes").resolve("42");
URIURL
PurposeParsing and manipulating an identifierLocating and opening a resource
ValidatesSyntax onlyRequires a known protocol handler
equalsTextual comparisonMay perform a DNS lookup, which is slow
RecommendationUse thisAvoid except when a legacy API demands it
Never put a URL in a Set or use it as a map key. Its equals and hashCode may resolve host names over the network, so a simple lookup can block. URI has no such behaviour.

Encoding

String term = "java streams & lambdas";
String encoded = URLEncoder.encode(term, StandardCharsets.UTF_8);
URI search = URI.create("https://example.com/search?q=" + encoded);

String decoded = URLDecoder.decode(encoded, StandardCharsets.UTF_8);

Always encode values placed into a query string. Concatenating raw user input produces broken URLs at best and an injected parameter at worst.

Addresses

InetAddress address = InetAddress.getByName("example.com");
System.out.println(address.getHostAddress());
System.out.println(InetAddress.getLocalHost());

InetSocketAddress endpoint = new InetSocketAddress("example.com", 443);

A TCP client socket

try (Socket socket = new Socket()) {
    socket.connect(new InetSocketAddress("example.com", 80), 5000);   // connect timeout
    socket.setSoTimeout(10_000);                                       // read timeout

    var out = new PrintWriter(
            new OutputStreamWriter(socket.getOutputStream(), StandardCharsets.UTF_8), true);
    var in = new BufferedReader(
            new InputStreamReader(socket.getInputStream(), StandardCharsets.UTF_8));

    String crlf = "" + (char) 13 + (char) 10;   // HTTP requires CR LF line endings

    out.print("GET / HTTP/1.1" + crlf);
    out.print("Host: example.com" + crlf);
    out.print("Connection: close" + crlf);
    out.print(crlf);
    out.flush();

    String line;
    while ((line = in.readLine()) != null) {
        System.out.println(line);
    }
}

A socket is a two way byte pipe with an input stream and an output stream. Everything above it, HTTP included, is a convention about what those bytes mean.

A TCP server

public class EchoServer {

    public static void main(String[] args) throws IOException {
        try (ServerSocket server = new ServerSocket(9090);
             ExecutorService executor = Executors.newVirtualThreadPerTaskExecutor()) {

            System.out.println("Listening on port 9090");

            while (true) {
                Socket client = server.accept();     // blocks until a connection arrives
                executor.submit(() -> handle(client));
            }
        }
    }

    private static void handle(Socket client) {
        try (client;
             var in = new BufferedReader(new InputStreamReader(client.getInputStream()));
             var out = new PrintWriter(client.getOutputStream(), true)) {

            String line;
            while ((line = in.readLine()) != null) {
                out.println("echo: " + line);
            }
        } catch (IOException e) {
            System.err.println("Client failed: " + e.getMessage());
        }
    }
}

One virtual thread per connection is now practical, which is exactly the simple blocking model that used to be too expensive.

UDP

try (DatagramSocket socket = new DatagramSocket()) {
    byte[] payload = "ping".getBytes(StandardCharsets.UTF_8);
    DatagramPacket packet = new DatagramPacket(
            payload, payload.length, InetAddress.getByName("example.com"), 9999);
    socket.send(packet);
}
TCPUDP
ConnectionEstablished firstNone
DeliveryReliable and orderedBest effort
OverheadHigherLower
SuitsWeb, databases, most thingsMetrics, discovery, streaming media

Timeouts

Socket socket = new Socket();
socket.connect(endpoint, 5000);   // fail fast if the host does not answer
socket.setSoTimeout(10_000);      // a read that stalls throws SocketTimeoutException
A socket with no timeout can block forever. Every network call in a real system needs both a connect timeout and a read timeout, or a single unresponsive server can exhaust your threads.

Common exceptions

ExceptionUsual cause
UnknownHostExceptionDNS could not resolve the name
ConnectExceptionNothing is listening, or a firewall refused
SocketTimeoutExceptionConnect or read exceeded the timeout
SocketException: Connection resetThe peer closed abruptly
BindExceptionThe port is already in use
SSLHandshakeExceptionCertificate or protocol mismatch

Reading is not message oriented

// Wrong: read may return fewer bytes than requested
byte[] buffer = new byte[1024];
in.read(buffer);                    // how many were actually read?

// Right
int total = 0;
while (total < expected) {
    int read = in.read(buffer, total, expected - total);
    if (read == -1) {
        throw new EOFException("Stream ended early");
    }
    total += read;
}

byte[] all = in.readAllBytes();      // when the size is unknown and bounded

TCP is a byte stream, not a sequence of messages. A protocol must define its own framing, whether by a length prefix, a delimiter, or closing the connection.

NIO channels, briefly

try (SocketChannel channel = SocketChannel.open(new InetSocketAddress("example.com", 80))) {
    ByteBuffer buffer = ByteBuffer.allocate(1024);
    channel.read(buffer);
    buffer.flip();
}

Channels and selectors allow one thread to manage many connections without blocking. That model existed because threads were expensive; with virtual threads, straightforward blocking code is once again a reasonable choice for most servers.

Common mistakes

  • Omitting timeouts.
  • Not closing sockets, leaking file descriptors.
  • Assuming one read returns a whole message.
  • Using URL in collections and triggering DNS lookups.
  • Building URLs by concatenating unencoded input.
  • Writing HTTP by hand when HttpClient exists.

Best practices

  • Set both connect and read timeouts on every connection.
  • Use try with resources for sockets and streams.
  • Use URI rather than URL, and encode query values.
  • Buffer socket streams.
  • Define explicit framing in any protocol you design.
  • Use HttpClient for HTTP rather than raw sockets.

Practice

  1. Parse a URL with a query and fragment and print every component.
  2. Write an echo server and connect to it with two clients at once.
  3. Explain why one read may return fewer bytes than requested, and fix a loop accordingly.
  4. Set a one second read timeout and observe the exception.
  5. Why should a URL never be used as a map key?

Conclusion

A socket is a byte pipe, and every higher protocol is a convention on top of it. Use URI for addresses, always set timeouts, frame your own protocols explicitly, and prefer the standard clients to hand written ones.

Written by Lorens Mishra

Default administrator account created by the installer.

Continue reading

All Java notes →
Java

Introduction to Java

Java is a statically typed, object oriented language that compiles to bytecode and runs on a virtual machine, which is what makes it portable.

Read more

Discussion

0 comments
Sign in to join the discussion.

No comments yet. Be the first to say something.