Ballerina’s Built‑In OpenAPI Generation: Document Your Microservice in Seconds
Learn how Ballerina automatically emits an OpenAPI 3.0 spec from your service annotations, inspect it locally, and customize the output for real‑world microservice documentation.
08 Jul 2025, 20:36 UTC

Why Your Service Needs an Up‑to‑Date OpenAPI Spec
When a microservice is exposed to external teams, a machine‑readable contract is essential for client generation, automated testing, and API governance. Traditionally, developers write an OpenAPI document by hand or use a third‑party tool. Ballerina eliminates this extra step by generating the spec automatically from the very annotations that define the service.
What the Compiler Does for You
Every Ballerina service that uses the @http:Service and @http:Endpoint annotations triggers a compile‑time routine that emits an OpenAPI 3.0 JSON file. The file is written to target/openapi.json and matches the following mapping rules:
- Service paths and HTTP verbs become
pathsentries. - Request and response types are mapped to JSON schemas.
- Union, optional, and custom record types become
oneOf,nullable, orcomponents.schemasentries. - Pattern, minLength, enum, and other type constraints are preserved as JSON Schema keywords.
- Annotations like
@http:ServiceConfigand@http:Endpointlet you override the host, base path, and security definitions used in the spec.
Because the spec is generated from the same source that the service uses, you never risk a mismatch between implementation and documentation.
Inspecting the Generated Spec – A Concrete Walkthrough
Below is a minimal example that demonstrates the full cycle from source to served OpenAPI document.
import ballerina/http;
service /api on new http:Listener(8080) {
@http:ServiceConfig { basePath: "/v1" }
service echo {
@http:Endpoint { path: "/ping", methods: [http:GET] }
function ping() returns string {
return "pong";
}
@http:Endpoint { path: "/echo", methods: [http:POST] }
function echo(http:Request req) returns json {
json body = check req.getJsonPayload();
return { echoed: body };
}
}
}
Build the service:
$ bal build
# Build succeeded – openapi.json is now in target/
Run it:
$ bal run
Service is running at http://localhost:8080/api/v1/echo
Request the spec:
$ curl http://localhost:8080/openapi.json | head -n 20
{
"openapi": "3.0.1",
"info": {
"title": "api",
"version": "0.0.1"
},
"paths": {
"/api/v1/ping": {
"get": {
"responses": {
"200": {
"description": "Successful Response",
"content": {
"application/json": {
"schema": {
"type": "string"
}
}
}
}
}
}
},
"/api/v1/echo": {
"post": {
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"type": "object"
}
}
}
},
"responses": {
"200": {
"description": "Successful Response",
"content": {
"application/json": {
"schema": {
"type": "object"
}
}
}
}
}
}
}
}
}
Load the JSON into Swagger UI or Redoc to visually confirm that the paths, parameters, and response types match the code.
Customizing the Spec with Service Annotations
By default, the host is derived from the listener port and the base path comes from @http:ServiceConfig. You can fine‑tune these values:
@http:ServiceConfig { host: "api.example.com", basePath: "/v2" }– changes the host and base path in the generated spec.- Security can be injected using
@http:SecuritySchemeannotations on the service or endpoint. - If you need a different response schema for a particular endpoint, add a
@http:Responseannotation withschemaattribute pointing to a custom type.
All these annotations are respected by the OpenAPI generator, giving you fine‑grained control over the contract without touching the JSON file directly.
Trade‑offs and Practical Limitations
- Generic Schemas for Complex Types – If a custom record lacks
@jsonor@xmlannotations, the generator may produce a plainobjectschema, omitting field‑level constraints. Add explicit annotations to preserve validation details. - Dynamic Routing Not Captured – Endpoints that are constructed at runtime (e.g., using a list of path segments) will not appear in the static spec. For such services, maintain a separate manual spec or use a runtime discovery tool.
- Spec Size and Build Time – A service with many endpoints or deeply nested types can generate a large
openapi.json, increasing compile time. Consider splitting the service into smaller modules or using CI caching to mitigate this. - Regeneration Overhead – The spec is regenerated on every
bal build. In large projects, this can add noticeable latency; fine‑tune your build pipeline or generate the spec only in release branches.
Actionable Takeaway
Leverage Ballerina’s automatic OpenAPI generation to keep your service contract in sync with implementation. Start by adding @http:Service annotations, run bal build, and expose /openapi.json in production. Use the generated spec for client SDKs, automated contract tests, and API documentation portals. Remember to annotate complex types explicitly and be aware of dynamic routing limitations to avoid incomplete documentation.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.