Memra

URLConnection: the lifecycle and reading data

◈ 4 cards

Obtain a connection from openConnection(), configure it while it is still unconnected, let the first read connect it implicitly, and read the body without leaking the socket.

You never construct one

URLConnection is abstract and its only constructor is protected. You obtain an instance from url.openConnection(), and what comes back is a protocol-specific subclass — for an http URL, an HttpURLConnection. Writing a subclass yourself means writing a protocol handler, which is not what this course is about; the one method a subclass must supply is connect().

The important and easily missed fact: openConnection() performs no I/O. No socket, no DNS, no packet. It builds an object and returns. Nothing has happened yet, and that is the window in which you get to make decisions.

Three states

A connection is unconnected, then configured, then connected, and it never goes back. connect() makes the transition explicit, but you will rarely call it, because getInputStream(), getContent(), getHeaderField() and — on HttpURLConnectiongetResponseCode() all call it for you if it has not happened yet.

That implicit connect is the entire trap of the class. The moment the socket is open, every setter throws IllegalStateException. Code that reads the content type, decides it wants a longer timeout, and then calls setReadTimeout() does not get a longer timeout; it gets an exception, at runtime, from a line that looks innocent.

What you configure, and why

  • setConnectTimeout(ms) / setReadTimeout(ms) — how long the socket waits for the connection, and how long a read waits for bytes. Both are milliseconds, both default to 0, and 0 means never time out. A negative value throws IllegalArgumentException.
  • setRequestProperty(name, value) — a header on the outgoing request (User-Agent, Accept, Cookie). Do not confuse it with the response-header getters of the next lesson.
  • setDoOutput(true) — you intend to write a body. For HTTP this silently makes the request a POST.
  • setUseCaches(false), setIfModifiedSince(millis) — bypass or condition on the cache.

The default timeout of zero is the one that bites in an assignment: a host that accepts your connection and then says nothing leaves your program blocked forever, with no exception and no output, and it looks exactly like an infinite loop in your own code.

Reading the body

getInputStream() returns a raw InputStream of the body — bytes, no headers, no decoding. Buffer it, because a network stream delivers whatever has arrived rather than whatever you asked for, and close it with try-with-resources so the socket is released on the exception path too.

Worked example — fetch a page with a timeout

The order below is the whole lesson: obtain, configure, then read.

URLConnection conn = new URL(args[0]).openConnection();
conn.setConnectTimeout(10_000);   // still unconnected
conn.setReadTimeout(10_000);
conn.setRequestProperty("User-Agent", "comp348/1.0");
try (InputStream in = new BufferedInputStream(conn.getInputStream())) {
  in.transferTo(System.out);      // this line connected the socket
}

Move either setTimeout call below getInputStream() and the program still compiles, still looks right in review, and throws IllegalStateException on the first run. Nothing in the type system protects the ordering — the state machine is enforced at runtime only, which is why it is worth a figure and a checkpoint rather than a footnote.

configureor skip itstreamdoneopenConnection()no I/O yetsetXxxonly hereconnect()optionalgetInputStream()connects if neededreadbody bytesclosetry-with-resources
The one-way street: every setter must run before the first read. Everything above the read is configuration and may be reordered freely; everything below it is too late. connect() is drawn as optional because getInputStream() performs it for you.

source Harold 4e ch7 §Reading Data from a Server; JDK javadoc java.io.InputStream

NORMAL ~/memra/learn/comp-348/urlconnection-lifecycle utf-8 LF