Does OkHttp support WebSocket connections?

Yes. OkHttp supports WebSocket connections as a built-in feature of the client, not as an add-on. From the same client you use for ordinary requests, you can open a socket, exchange messages in both directions over a single long-lived connection, and close it cleanly when you are done. OkHttp WebSocket support exists for exactly the jobs ordinary request-and-response calls handle badly: pushing live updates to the app and sending small messages with almost no overhead.

A normal HTTP call is a question followed by an answer, after which the connection’s work is finished. A WebSocket is closer to an open phone line. Once it is established, either side may speak at any moment — the server can push a price change, a game state or a new message the instant it happens, without the app repeatedly asking whether anything is new.

This article walks the full lifetime of a socket: how the connection starts, what the listener callbacks mean, how sending and queuing behave, how the connection is kept alive, and how it ends — both gracefully and when the network disappears.

What a WebSocket is for

WebSockets suit features where data changes continuously and waiting is wasteful. Live feeds are the classic case: scores, sensor readings, multiplayer state and incoming messages all arrive on the server’s schedule, not the client’s. Polling for them with repeated requests burns battery, bandwidth and server capacity mostly to learn that nothing changed.

With a socket held open, an update crosses the moment it exists, and the channel carries traffic upwards just as easily. A client can report its own state, send a message or acknowledge receipt without building a new request each time.

Not everything belongs on a socket. Ordinary fetches — loading a profile, submitting a form, downloading a document — are simpler and more robust as normal calls, and moving them onto a WebSocket adds connection management for no benefit. The technology earns its complexity only where genuine two-way, low-latency traffic justifies keeping a line open.

How the connection begins: an HTTP upgrade

A WebSocket connection starts life as an ordinary HTTP request. The client builds a request to the server’s socket endpoint and asks, via an upgrade header, to switch protocols. If the server agrees, it answers with a status confirming the switch, the connection stays open, and from that moment the two sides speak the WebSocket protocol instead of HTTP.

Because the opening is a real request, it can carry the things requests normally carry: an authorisation header, cookies, a subprotocol choice. Authentication therefore happens at the door, using the same mechanisms as the rest of the app’s networking, rather than through some socket-specific side channel.

In code, opening one is a single call that takes the request and a listener, and it returns immediately:

val request = Request.Builder()
    .url("https://example.com/socket")
    .build()
val socket = client.newWebSocket(request, listener)

The call returns a handle representing the socket, through which messages are later sent and the connection is closed. Everything that subsequently happens on the connection — opening, messages, closure, failure — is reported through the listener, which makes the listener the heart of any real implementation.

The listener: how the connection reports its life

The listener is a small set of callbacks the library invokes as the connection’s state changes. The first is the open callback, delivered when the upgrade has succeeded and the socket is ready to carry messages. Only from this point is the connection usable, a detail behind one of the most common beginner mistakes.

Message callbacks arrive in two flavours: one for text messages, delivered as a string, and one for binary messages, delivered as bytes. An application typically uses one or the other depending on its protocol, but the socket itself carries both on the same connection, and the listener distinguishes them.

The remaining callbacks describe the ending. A closing callback signals that the peer has begun a graceful shutdown and will send no more messages; a closed callback confirms the shutdown completed, together with the close code and reason the peer supplied. A failure callback is the unruly path: an error — network, protocol or otherwise — has torn the connection down, and the exception explains what happened.

Sending messages, text and binary

Sending is a single method call on the socket handle, given either a string for a text message or bytes for a binary one. It returns a boolean rather than throwing in the failure case: true means the message was accepted into the queue, false means it was not — because the queue is full, the socket is closing, or it has already failed.

That return value is worth more attention than it usually gets. Code that ignores it can believe it sent messages that never left the device. Checking it, especially for messages the user experience depends on, turns silent loss into a state the app can react to.

Messages are delivered in the order they were accepted. The socket does not ask the application to manage framing, chunking or ordering; a message handed over as one unit arrives at the peer as one unit — one send corresponds to one logical message.

The send queue and backpressure

Underneath the simple send method sits a queue. When the network is slower than the application, accepted messages wait their turn in an in-memory buffer. OkHttp caps that buffer — once roughly sixteen megabytes of messages are waiting unsent, further sends are refused and return false, and the socket eventually fails if the backlog cannot drain.

This is backpressure made visible. A client producing messages faster than a struggling connection can deliver them cannot simply accumulate them forever; something has to give. The design chooses to refuse new messages rather than grow without limit, but the app has to notice.

Well-behaved senders work with the queue instead of against it. Apps sending high-frequency updates often drop intermediate values deliberately — the newest reading matters, the tenth-oldest does not — and treat a refused send as a signal to slow down or coalesce updates. Treating every message as precious is the design error, not the queue limit.

Keeping the connection alive with ping and pong

An idle socket raises a practical question: is the other end still there? Connections pass through proxies, routers and mobile network equipment that silently discard state for connections with no traffic, and a server or client may vanish without any closing handshake. The WebSocket protocol answers with control frames: a ping asks, and a pong replies automatically.

The client can be configured with a ping interval, after which it sends pings on an otherwise idle connection and expects pongs in return. If a pong does not arrive within a comparable period, the connection is considered dead and fails — which, although it sounds harsh, is far better than an app believing for an hour that a dead line is live.

Choosing the interval is a balance. Very frequent pings keep the connection provably alive but cost radio wake-ups and battery on mobile devices, and some intermediaries still time out regardless. Intervals in the tens of seconds are a common compromise. The ping mechanism also interacts with timeouts: read and write timeouts designed for short calls do not simply apply to a long-lived socket.

Closing gracefully versus failing abruptly

There are two ways a socket ends, and the listener reports them differently. A graceful close is a handshake: one side announces it is done, supplying a numeric code and an optional short reason, the other side acknowledges, remaining queued messages are flushed, and both sides agree the conversation is over. The client initiates this by calling close on the socket.

An abrupt failure skips the courtesies. The network drops, the server process dies, a protocol error occurs, or a ping goes unanswered, and the failure callback fires with the cause. Anything still sitting in the send queue at that moment is lost, and the app cannot know which of its recent messages arrived — an uncertainty that protocols must tolerate.

Applications usually treat a graceful close as final and a failure as a question: does the user still need this connection? If yes, reconnection begins. Which brings up the part of the lifecycle the library deliberately leaves to you.

Reconnecting after a failure

OkHttp does not automatically reconnect a failed socket. Once the failure callback fires, that socket is finished; there is no resume, and queued-but-unsent messages are gone. Reconnecting means building a new socket with a fresh request and listener, exactly as the first one was built, and re-establishing whatever state the application protocol keeps, such as subscriptions or the last message received.

Leaving this to the application is a deliberate design choice, because reconnection policy is application policy. Should it retry immediately or wait? Give up after three attempts or persist for hours? Re-authenticate first? The answers differ between a trading screen and a background sync tool, and a library that guessed would guess wrong for someone.

Sensible policies share a shape: wait before retrying, increase the wait after each failure, and stop when the user leaves the feature that needs the connection. Immediate, unlimited retries against a downed server create a thundering herd of reconnecting clients and drain the user’s battery. A token or session that expired during the outage should be refreshed before the new handshake, since the opening request carries the credentials.

Cancelling, and mistakes worth avoiding

A socket the app no longer needs can be cancelled outright rather than closed politely. Cancellation discards the connection immediately — no handshake, no flushing of queued messages — and suits teardown moments such as the user leaving a screen. Closing is for ending a conversation; cancelling is for abandoning one, and choosing between them is about whether the queued messages still matter.

The recurring mistakes are easy to list. Sending before the open callback fires races the handshake and loses messages. Never closing sockets when a feature is left leaks connections that keep the radio awake and the server’s table full. And doing heavy work directly inside a message callback blocks the thread that delivers subsequent callbacks, stalling the pipeline the app depends on — callbacks should hand work off and return quickly.

That last point generalises. A WebSocket is event-driven machinery bolted onto a client otherwise used for discrete requests, and it behaves less like synchronous and asynchronous calls and more like a small state machine the app must supervise: connecting, open, closing, gone. Teams that draw those states explicitly — and code each transition — end up with connections that survive real networks. The starting point is the client described on the OkHttp homepage.

Frequently Asked Questions

Can messages be sent while the socket is still connecting?

They can be offered, and the socket will hold them in its outgoing queue, sending them once the connection opens. If the connection then fails during the handshake, those queued messages are lost with it. For messages that must not vanish, waiting for the open callback before sending remains the safer discipline.

Is there a maximum size for a single message?

The socket streams frames rather than imposing a small fixed cap, so sizeable messages are possible. In practice, memory is the limit: very large payloads usually belong in an ordinary upload, with the socket used to announce them. Huge messages also delay everything queued behind them.

Does the socket keep the app connected in the background?

No. A WebSocket lives and dies with the process and network conditions hosting it, and mobile operating systems suspend background apps, taking their sockets down with them. Apps needing reliable delivery while backgrounded should treat disconnections as normal, reconnect when foregrounded, and reconcile missed updates on resuming rather than assuming the stream was continuous.

How are binary messages different from text messages?

Text messages are delivered to the listener as strings and are expected to be valid text; binary messages arrive as raw bytes and can carry anything — encoded structures, images, or fragments of files. The choice is a protocol decision: text suits human-readable message formats, while binary suits compact or non-textual payloads, and both can share one connection.

Conclusion

OkHttp WebSocket support turns the HTTP client into a two-way channel: a connection opened with an HTTP upgrade, driven by listener callbacks, carrying text and binary messages for as long as it stays alive. Pings keep idle connections honest, a bounded send queue makes backpressure explicit, and closure can be either a polite handshake or an abrupt failure the app must handle.

The library provides the machinery and leaves the policy — when to reconnect, what to resend, when to give up — to the application. Teams that respect that division, and design their protocol to survive disconnection, get real-time features that behave as well on a train as they do on a desk.

1 thought on “Does OkHttp support WebSocket connections?”

  1. Pingback: Should you make OkHttp calls synchronously? - OkHttp

Leave a Comment

Your email address will not be published. Required fields are marked *

Scroll to Top