Skip to content

Latest commit

 

History

History
136 lines (101 loc) · 6.29 KB

File metadata and controls

136 lines (101 loc) · 6.29 KB

openapi-httpserver-java

Quality Gate Status Coverage Code Smells Duplicated Lines (%) WorkFlow

OpenAPI Server Library

A lightweight Java library for creating HTTP servers based on OpenAPI specifications.

Overview

This library provides a simple way to create an HTTP server that implements OpenAPI specifications.

It is designed to be simple to use while providing the essential features needed for creating efficient HTTP servers in Java.

Getting Started

Prerequisites

  • Java SDK 25 or later
  • A serialization library, e.g. Gson or Jackson
  • OpenAPI specification file in JSON format (openapi.json)

Basic Usage

  1. Create an OpenAPI specification file named openapi.json in your project resources.
  2. Define your HTTP handlers by implementing the HttpHandler interface:
public class GetDataHandler implements HttpHandler {
  @Override
  public void handle(HttpExchange exchange) throws IOException {
    try (exchange) {
      byte[] bytes = """
      {
        "id": "some-id"
      }""".getBytes();

      var responseHeaders = exchange.getResponseHeaders();
      responseHeaders.add("content-type", "application/json");

      exchange.sendResponseHeaders(HTTP_OK, bytes.length);

      try (var os = exchange.getResponseBody()) {
        os.write(bytes);
      }
    }
  }
}

public class PostDataHandler implements HttpHandler {
  @Override
  public void handle(HttpExchange exchange) throws IOException {
    try (exchange) {
      // Access the raw request body bytes.
      byte[] body = Request.bytes(exchange);
      // Or get the already-parsed object (Map or List) produced by your JsonMapper.
      Object parsed = Request.parsed(exchange);

      exchange.sendResponseHeaders(HTTP_OK, -1);
    }
  }
}
  1. Initialize the server (using Gson in this example):
public class YourServerLauncher {
  public static void main(String[] args) throws Exception {
    Gson gson = new Gson();

    // Parse spec to a generic Map (works for JSON; for YAML use SnakeYAML).
    String text = Files.readString(Path.of("openapi.json"));
    Map<String, Object> raw = (Map<String, Object>) gson.fromJson(text, Map.class);
    Spec spec = Spec.from(raw);

    // Body parser. Returns a Map for objects, List for arrays.
    JsonMapper mapper = body -> gson.fromJson(new String(body), Object.class);

    // Handlers by operationId.
    Map<String, HttpHandler> handlers = new HashMap<>();
    handlers.put("get-data", new GetDataHandler());
    handlers.put("post-data", new PostDataHandler());

    new OpenApiServer(spec, mapper, handlers, Handlers.defaultExceptionHandler());
  }
}

YAML specifications

For YAML, replace the JSON parsing line with SnakeYAML:

Map<String, Object> raw = new Yaml().load(Files.newInputStream(Path.of("openapi.yaml")));

The rest is identical.

Features

  • OpenAPI specification support
  • Automatic request body parsing for JSON arrays and objects
  • Custom HTTP handler support
  • Built on Java's native HttpServer with Thread-Per-Request behaviour using Virtual Threads.
  • Custom integration for JSON serialization/deserialization

Handler Registration

Handlers are registered using string keys that correspond to your OpenAPI operation IDs.

JSON Mapping

The library uses a flexible JSON mapping system that automatically detects and parses (using a mapper of choice):

  • JSON arrays ([...])
  • JSON objects ({...})

Local development

To test the server in isolation, you can start an example server (src/test/java/com/retailsvc/http/start/ServerLauncher.java). Schemas are located under test resources folder.

  • Example requests can be found under acceptance/k6 that can be a base for exploring the functionality.
  • The logger in the configuration needs to be enabled to get some insight into the code.

Performance and caveats

The library wraps the JDK's bundled com.sun.net.httpserver.HttpServer and uses a virtual-thread-per-request executor. On a developer laptop (Apple Silicon, single instance, default JVM flags) it sustains roughly:

  • ~32k requests/second for small JSON GETs and POSTs (~300 byte bodies), measured via k6 at 30 sustained VUs over 45 seconds (1.4M requests, 100% of checks passing, 0% HTTP failures).

A few things to know:

  • Single-process model. No horizontal scaling primitives are bundled; run multiple instances behind a load balancer for production scale.
  • JDK HttpServer is the throughput ceiling. It's documented as a low-throughput / dev-test server. If you need to go materially above the rates above, deploy the same filter/validator/router stack on Jetty, Helidon Níma, or Netty — the spec and validation code is server-agnostic.
  • Per-request state uses ScopedValue (Java 25, JEP 506), not HttpExchange.setAttribute. This matters if a handler offloads work to an executor that's not a StructuredTaskScope-managed child thread: the ScopedValue is not visible there, so the handler must capture the values it needs (e.g. byte[] body = Request.bytes();) before submitting.
  • HttpExchange.sendResponseHeaders(rCode, length) gotcha. When a handler has no response body, pass -1 (Content-Length: 0, no body); passing 0 produces a chunked response with zero chunks, which is technically non-conformant.

Known limitations or missing features