Airframe 2026.2.5: sendSafe throws on connection failures

2 hours ago

Airframe 2026.2.5 is a small release built around one fix. When a request fails before the server responds, sendSafe and RPC calls now report an error with the real cause attached, instead of a made-up 500 response. On the server side, Netty streams of server-sent events now tell clients to close the connection. The rest is dependency updates, and one of those changes how Parquet writers behave.

Ask about this presentation

Chapters

  1. 0:00Airframe 2026.2.5
  2. 0:27sendSafe throws
  3. 0:54RPC reports UNAVAILABLE
  4. 1:17SSE sends Connection: close
  5. 1:42Dependency updates
  6. 2:08Before you upgrade
  7. 2:35Upgrade
Show transcript
  1. Airframe 2026.2.5 is a small release built around one fix. When a request fails before the server responds, sendSafe and RPC calls now report an error with the real cause attached, instead of a made-up 500 response. On the server side, Netty streams of server-sent events now tell clients to close the connection. The rest is dependency updates, and one of those changes how Parquet writers behave.

    Slide text
    Airframe

    sendSafe throws on connection failures

    Airframe 2026.2.5
    Netty event streams now send Connection: close

  2. This is the change you are most likely to notice. Until now, a refused connection made sendSafe return a status 500 response that the client invented, so an unreachable server looked like a crashing one. Now sendSafe throws an HttpClientException with the connection error as its cause, and the async client fails its stream the same way. A real answer, even a 503 after retries, still comes back as the response.

    Slide text
    airframe 2026.2.5

    sendSafe throws when a request fails before the server responds

    2026.2.4 · connection refused
    client.sendSafe(req)
    returns a Response, status 500
    The client made up the 500 itself.
    2026.2.5 · connection refused
    client.sendSafe(req)
    throws HttpClientException
    The connection error is its cause.

    A real answer, even a 503 after retries, still comes back as the Response.

    airframe-http · SyncClient, AsyncClient
  3. RPC calls used to report the same failure as an empty 500 response. Now they raise an RPCException with the UNAVAILABLE status, a message that starts with "No response from the server", and the original error as its cause. That holds for the sync and the async client, so error handling that checks the RPC status will see UNAVAILABLE here.

    Slide text
    airframe 2026.2.5

    RPC calls fail with UNAVAILABLE when the server is unreachable

    RPCException
    status RPCStatus.UNAVAILABLE_I2
    message "No response from the server: …"
    cause the original connection error
    airframe-http · SyncClient.rpc, AsyncClient.rpc
  4. On the server, airframe-http-netty closes the channel once an event stream writes its last chunk. But the response headers still said keep-alive, which invited clients to reuse a connection that was about to close. Now the header says close, for HTTP 1.1 and HTTP 1.0 requests alike, and a successful stream keeps the channel open until it has sent everything.

    Slide text
    airframe 2026.2.5

    Netty event streams now say close

    The server closes the channel after a stream's last chunk, so the header no longer invites clients to reuse it.

    content-type: text/event-stream
    transfer-encoding: chunked
    cache-control: no-cache
    connection: keep-alive
    connection: close
    airframe-http-netty · NettyRequestHandler
  5. Three libraries moved up: gRPC to 1.85.1, Jackson to 2.22.4, and Parquet to 1.18.1. The Parquet update matters if you use airframe-parquet. Since parquet-java 1.18, a writer is aborted after any write error, so once one write throws, the next write on that writer fails with an IOException. Open a new writer after a bad record.

    Slide text
    airframe 2026.2.5

    Parquet 1.18 ends a writer after any failed write

    gRPC 1.84.2 → 1.85.1
    Jackson 2.22.3 → 2.22.4
    Parquet 1.17.1 → 1.18.1
    airframe-parquet: open a new writer after a write throws
    The next write on the same writer fails with an IOException.
    build.sbt
  6. Before you move to 2026.2.5, look at three places. Anywhere you call sendSafe and read a 500 as the server being down, catch HttpClientException instead. Anywhere you handle RPC errors, expect UNAVAILABLE when the server can't be reached. And if you write Parquet files, open a new writer after a failed write. The method signatures stay the same; what changes is how a failure reaches you.

    Slide text
    airframe 2026.2.5

    Check these call sites before you upgrade

    sendSafe Treated a 500 as "server down"? Catch HttpClientException.
    RPC clients Expect UNAVAILABLE when the server is unreachable.
    airframe-parquet Open a new writer after a write error.
    libraryDependencies += "org.wvlet.airframe" %% "airframe-http-netty" % (version)
    version: 2026.2.5
    airframe-http · airframe-http-netty · airframe-parquet
  7. This is a narrow release. It adds zero new APIs; the changes are in error reporting, one response header, and dependency versions. If your code counts on sendSafe returning a response for every failure, read pull request 4274 before you upgrade. Otherwise, Airframe 2026.2.5 reports connection failures as errors, with the real cause attached.

    Slide text
    airframe 2026.2.5

    Connection failures now show up as errors, with their cause

    Zero new APIs. The changes are in error reporting, one response header and dependency versions.

    github.com/wvlet/airframe/releases/tag/v2026.2.5

What happened in AI today, in your inbox

One short email each evening. Read it and you’re caught up.

Built in New York with ❤️