Using Ballerina’s HTTP Client for Typed Data Binding and Union‑Typed Errors
See how Ballerina’s http:Client maps JSON payloads directly to records while forcing explicit error handling, reducing boilerplate in integration code.
07 Dec 2025, 22:03 UTC

The problem: repetitive JSON parsing and status‑code checks
When calling a REST service from Ballerina, developers often write manual JSON decoding, inspect HTTP status codes, and propagate errors with try‑catch blocks. This adds noise and makes it easy to forget a failure path.
Thesis: let the type system do the work
Ballerina’s http:Client remote methods can bind the response payload directly to a declared type. The call returns a union of that type and error, so the compiler forces you to handle transport failures, timeouts, and binding problems explicitly. The check expression propagates errors concisely, keeping the happy‑path readable.
Typed data binding basics
Define a record that mirrors the JSON shape you expect. If you mark the record as open, extra fields in the payload are ignored, which protects against schema drift from third‑party APIs.
type User record {\n string name;\n int age;\n string? email;\n};
When you declare the remote method return type as User|error, the runtime validates the JSON, converts it to a User value, or returns an error if the payload cannot be mapped.
Union‑typed errors and the check expression
A remote call looks like:
User|resp = check client->get("/users/1");
The check keyword unwraps the union: on success resp is a User; on any error (connection timeout, non‑2xx status, binding mismatch) the error is returned from the enclosing function. This eliminates the need for explicit if resp is error checks.
Worked example: calling a public test API
Suppose we want to fetch a simple JSON object from https://httpbin.org/json, which returns:
{\n "slideshow": {\n "author": "Yours Truly",\n "date": "date of publication",\n "slides": [\n {"title": "Wake up to WonderWidgets!", "type": "all"},\n {\t"title": "Overview", \t"type": "all"}\n ]\n }\n}
We only need the author and date fields, so we define an open record:
type Slideshow record {\n string author;\n string date;\n};
Now the client call:
import ballerina/http;\nimport ballerina/log;\n\nfunction getSlideshowAuthor() returns string|error {\n http:Client client = new("https://httpbin.org");\n // The remote method returns Slideshow|error\n Slideshow|resp = check client->get("/json");\n // resp is guaranteed to be Slideshow here\n return resp.author;\n}\n\npublic function main() {\n string|err = getSlideshowAuthor();\n if err is error {\n log:printError("Failed to fetch slideshow", err = err);\n } else {\n log:printInfo(`Author: ${getSlideshowAuthor()}`);\n }\n}
To run this:
- Install a recent Ballerina distribution (e.g., Swan Lake 2201.4.0).
- Save the code to
slideshow.bal. - Execute
bal run slideshow.balfrom a terminal with network access. - On success you see a log line with the author; on failure you see the error details.
If you deliberately change the record to expect an int for author and re‑run, the call returns a binding error (http:Error) because the runtime cannot convert the JSON string to an integer.
Trade‑off: convenience defaults vs. explicit resiliency
The http:Client constructor accepts parameters for timeout, retry, and circuit‑breaker behavior. Leaving them unspecified uses sensible defaults that work for quick scripts, but production services often need explicit tuning (e.g., a 2‑second connect timeout, exponential backoff retries). The blog post should note that while data binding reduces boilerplate, you still must decide on resiliency settings appropriate to your SLA.
Limitation: binding failures are runtime errors
Type safety applies only to the shape of the payload; the compiler cannot verify that a remote API will always return fields matching your record. Schema drift or unexpected null values cause a binding error at runtime. Defensive practices include:
- Using
openrecords to ignore unknown fields. - Providing sensible defaults with
string? field = "". - Checking for
nilafter binding when a field is optional.
Actionable takeaway
Adopt Ballerina’s typed HTTP client when you control the consumer side of a REST integration. Define open records that match the needed subset of the response, rely on union returns and check to enforce error handling, and tune timeouts/retry policies separately for production resilience. This approach replaces manual JSON parsing and status‑code switches with concise, type‑driven code.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.