“The Day ‘Am I Online?’ Broke Our Java Service”
DNS, HTTP, captive portals, and the quiet assumptions that fail in production systems.
A few months back I was working on a small service that had to behave differently when it had no internet connection. At first glance it sounds trivial: if your laptop fetches GitHub just fine, you’re online. But real systems aren’t that simple. Maybe DNS doesn’t resolve. Maybe TCP opens but stalls. Maybe your home router sits behind a captive portal (hello airports). I wanted a check that exercised the real stack, DNS, TCP, and HTTP, not just ICMP ping.
I started by Googling for reliable endpoints. That led me to something Chrome itself uses under the hood: a tiny request to a URL that returns 204 No Content. It’s fast, it’s compact, and most importantly it behaves like a real HTTP request.
Here’s the exact URL Chrome hits:
http://google.com/generate_204
https://google.com/generate_204Food for thought: you don’t actually care what it returns beyond the status code. If it comes back quickly with 200 or 204 you have a working internet path.
There are also plenty of similar URLs from other providers, like Cloudflare, Microsoft, Ubuntu, Apple, etc. You can choose whichever makes sense for your users and threat model.
Why ICMP Isn’t Enough
When you send a ping to, say, 8.8.8.8, you learn only one thing: that you can reach that host via ICMP. Firewalls, captive portals, or even corporate proxies can block or rewrite ICMP without affecting HTTP. In contrast, an HTTP GET to a minimal endpoint surfaces all the layers your app actually depends on:
DNS resolution (can I resolve the host?)
TCP handshake (is there a working route?)
HTTP negotiation (are proxies/captive portals rewriting traffic?)
By the time you get a 200 or 204 back — you’ve exercised the same stack your users’ browsers and fetch calls use daily.
A Pragmatic Java Check You Can Run with JBang
You’re writing a Quarkus microservice or a CLI; a tiny online check is useful everywhere. Here’s a simple snippet that works with JBang.
///usr/bin/env jbang "$0" "$@" ; exit $?
import java.net.HttpURLConnection;
import java.net.URI;
import java.net.URL;
import java.time.Duration;
public class IsOnline {
public static boolean isOnline(Duration timeout) {
try {
URL url = URI.create("http://google.com/generate_204").toURL();
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.setConnectTimeout((int) timeout.toMillis());
conn.setReadTimeout((int) timeout.toMillis());
conn.setRequestMethod("GET");
conn.connect();
int code = conn.getResponseCode();
return code == 200 || code == 204;
} catch (Exception e) {
return false;
}
}
public static void main(String[] args) {
boolean online = isOnline(Duration.ofSeconds(1));
System.out.println("Online? " + online);
}
}I picked 1 second as a rough timeout because anything longer starts to feel sluggish in real apps. You can always replace the URL with another provider if your network environment dislikes Google endpoints (e.g., China, enterprise proxies, etc.).
When you run this with JBang, it just works:
jbang IsOnline.javaI use this to gate certain features in my apps — logging remote deps, checking for updates, failing fast if a heavy fetch is pointless without connectivity.
DNS Fails Before the Network Does
One of the first surprises you hit in real environments is that DNS failure is its own failure mode. Corporate VPNs, split DNS setups, and misconfigured resolvers can all break name resolution while the network itself is otherwise fine.
When DNS fails, your HTTP request doesn’t fail loudly. It just waits. That means your “online check” can silently stall threads until a timeout expires. If this happens during startup or on a shared thread pool, a simple boolean check can suddenly affect system responsiveness.
At this point, “am I online?” is no longer just a question. It’s a potential source of latency.
Captive Portals Lie Convincingly
Captive portals deserve special mention because they fail politely. Instead of rejecting your request, they often return 200 OK with an HTML login page. From the perspective of our code, everything looks fine.
The result is a false positive. Your application declares itself online, then every subsequent request gets intercepted, redirected, or rewritten.
This is why Chrome uses a 204 endpoint in the first place. No body, no redirects, and a very specific expected behavior. Even then, some portals still manage to fake it.
In environments where this matters, checking only the status code is not always enough. Headers, redirects, and content length suddenly become part of your definition of “online”.
Blocking Is the Silent Footgun
Another thing the snippet above doesn’t tell you is where it is safe to run.
In a plain JVM application, blocking for a second might be acceptable. In Quarkus, Vert.x, or any reactive runtime, blocking calls on event loop threads are poison. One innocent connectivity check executed in the wrong place can stall request handling for the entire instance.
This is how small utility methods turn into production incidents. Not because the code is wrong, but because the context changed.
The check itself isn’t the problem. Where and when you run it is.
Startup Is the Wrong Time to Ask This Question
It’s tempting to run an online check at startup and fail fast if the internet isn’t reachable. That works beautifully on laptops and fails spectacularly in Kubernetes.
Outbound traffic might be blocked by policy. DNS might not be ready yet. Network routes might be initialized after the process starts. If your app treats “offline at startup” as fatal, your pod never becomes Ready and your deployment never stabilizes.
In production systems, “am I online?” is usually not a startup requirement. It’s a capability check that should run in the background and influence behavior, not liveness.
One Endpoint Is a Dependency
Using Google’s endpoint is pragmatic, but it is still a dependency. Some environments block Google entirely. Others rewrite DNS responses. In regulated setups, even making unsolicited outbound requests can violate policy.
At some point, a hardcoded URL becomes a liability. Configuration, rotation, or multiple probes become necessary. The more critical this check is to your system, the more deliberate that choice needs to be.
A Variation That Uses Java’s HttpClient
If you’re already on Java 11+ and prefer the modern non-blocking API:
///usr/bin/env jbang "$0" "$@" ; exit $?
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.time.Duration;
public class IsOnlineClient {
public static boolean isOnline(Duration timeout) {
try {
HttpClient http = HttpClient.newBuilder()
.connectTimeout(timeout)
.build();
HttpRequest req = HttpRequest.newBuilder()
.uri(URI.create("http://google.com/generate_204"))
.timeout(timeout)
.GET()
.build();
HttpResponse<Void> resp = http.send(req, HttpResponse.BodyHandlers.discarding());
return resp.statusCode() == 200 || resp.statusCode() == 204;
} catch (Exception e) {
return false;
}
}
public static void main(String[] args) {
System.out.println("Online? " + isOnline(Duration.ofSeconds(1)));
}
}This version plays nicely with async patterns too, if you want to wrap it in an event loop spinner or background scheduler.
The Trade-Off You Should Understand
There’s no single canonical way to answer “am I online”. Here’s what this approach does tell you:
You can resolve the host you pointed at
You can establish a TCP connection
You can complete an HTTP request and get a valid status
What it doesn’t tell you:
That arbitrary hosts are reachable (maybe only certain domains work)
That your gateway isn’t performing transparent proxy magic
That your HTTP headers aren’t being rewritten
For most apps this is “good enough”. If you need stronger guarantees (like arbitrary host reachability), you’ll need more elaborate probing.
Is The Internet There?
By the time I finished this exercise, I stopped thinking about “online” as a binary truth. It’s not a fact about the universe. It’s a policy decision.
Do you require DNS? Which DNS? Do redirects count? Is a captive portal acceptable? Can this check block? Is failure fatal or advisory?
The code is the easy part. The hard part is deciding what you’re willing to believe.
And that’s why “am I online?” turned out to be a much more interesting question than it first appeared.



Really sharp breakdown of connectivity checks. The captive portal issue is somthing I've debugged way too many times, and the framing of online as a policy decisoin rather than a binary state is spot-on. Had a similar situation last sprint where startup checks in k8s blocked pod readiness becuase DNS wasn't fully initialized yet.