{"id":21893321,"url":"https://github.com/http-rpc/kilo","last_synced_at":"2026-03-08T16:09:44.986Z","repository":{"id":33501718,"uuid":"37147644","full_name":"HTTP-RPC/Kilo","owner":"HTTP-RPC","description":"Lightweight REST for Java","archived":false,"fork":false,"pushed_at":"2025-05-14T19:32:55.000Z","size":67291,"stargazers_count":332,"open_issues_count":0,"forks_count":62,"subscribers_count":33,"default_branch":"master","last_synced_at":"2025-05-14T20:42:25.918Z","etag":null,"topics":["csv","java","jdbc","json","rest","servlet","template-engine","web-service","xml"],"latest_commit_sha":null,"homepage":"","language":"Java","has_issues":false,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/HTTP-RPC.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null}},"created_at":"2015-06-09T17:36:39.000Z","updated_at":"2025-05-14T19:32:58.000Z","dependencies_parsed_at":"2024-03-18T13:42:23.900Z","dependency_job_id":"5b1b20df-1465-428d-82f4-2ade5e2e1a09","html_url":"https://github.com/HTTP-RPC/Kilo","commit_stats":null,"previous_names":["http-rpc/http-rpc","gk-brown/http-rpc"],"tags_count":143,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/HTTP-RPC%2FKilo","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/HTTP-RPC%2FKilo/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/HTTP-RPC%2FKilo/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/HTTP-RPC%2FKilo/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/HTTP-RPC","download_url":"https://codeload.github.com/HTTP-RPC/Kilo/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":254485053,"owners_count":22078767,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["csv","java","jdbc","json","rest","servlet","template-engine","web-service","xml"],"created_at":"2024-11-28T13:13:26.336Z","updated_at":"2026-02-12T23:09:44.173Z","avatar_url":"https://github.com/HTTP-RPC.png","language":"Java","readme":"[![Releases](https://img.shields.io/github/release/HTTP-RPC/Kilo.svg)](https://github.com/HTTP-RPC/Kilo/releases)\n[![Maven Central](https://img.shields.io/maven-central/v/org.httprpc/kilo-client.svg)](https://repo1.maven.org/maven2/org/httprpc/kilo-client/)\n[![javadoc](https://javadoc.io/badge2/org.httprpc/kilo-client/javadoc.svg)](https://javadoc.io/doc/org.httprpc/kilo-client)\n\n# Introduction\nKilo is an open-source framework for creating and consuming RESTful and REST-like web services in Java. It is extremely lightweight and requires only a Java runtime environment and a servlet container. The project's name comes from the nautical _K_ or _Kilo_ flag, which means \"I wish to communicate with you\":\n\n![](kilo.png)\n\nThis guide introduces the Kilo framework and provides an overview of its key features.\n\n# Getting Kilo\nKilo is distributed via Maven Central: \n\n* [org.httprpc:kilo-client](https://repo1.maven.org/maven2/org/httprpc/kilo-client/) - includes support for consuming web services, interacting with relational databases, and working with common file formats (Java 17 or later required)\n* [org.httprpc:kilo-server](https://repo1.maven.org/maven2/org/httprpc/kilo-server/) - depends on client; includes support for creating web services (Jakarta Servlet specification 5.0 or later required)\n\n# Kilo Classes\nClasses provided by the Kilo framework include:\n\n* [WebService](#webservice)\n* [WebServiceProxy](#webserviceproxy)\n* [JSONEncoder and JSONDecoder](#jsonencoder-and-jsondecoder)\n* [TextEncoder and TextDecoder](#textencoder-and-textdecoder)\n* [CSVEncoder](#csvencoder)\n* [TemplateEncoder](#templateencoder)\n* [BeanAdapter](#beanadapter)\n* [QueryBuilder and ResultSetAdapter](#querybuilder-and-resultsetadapter)\n* [ElementAdapter](#elementadapter)\n* [Pipe](#pipe)\n* [Collections and Optionals](#collections-and-optionals)\n\nEach is discussed in more detail below.\n\n## WebService\n`WebService` is an abstract base class for web services. It extends the similarly abstract `HttpServlet` class and provides a thin, REST-oriented layer on top of the standard [servlet API](https://jakarta.ee/specifications/servlet/5.0/). \n\nFor example, the following service implements some simple mathematical operations:\n\n```java\n@WebServlet(urlPatterns = {\"/math/*\"}, loadOnStartup = 1)\n@Description(\"Math example service.\")\npublic class MathService extends WebService {\n    @RequestMethod(\"GET\")\n    @ResourcePath(\"sum\")\n    @Description(\"Calculates the sum of two numbers.\")\n    public double getSum(\n        @Description(\"The first number.\") double a,\n        @Description(\"The second number.\") double b\n    ) {\n        return a + b;\n    }\n\n    @RequestMethod(\"GET\")\n    @ResourcePath(\"sum\")\n    @Description(\"Calculates the sum of a list of numbers.\")\n    public double getSum(\n        @Description(\"The numbers to add.\") List\u003cDouble\u003e values\n    ) {\n        var total = 0.0;\n\n        for (var value : values) {\n            total += value;\n        }\n\n        return total;\n    }\n}\n```\n\nThe `RequestMethod` annotation associates an HTTP verb such as `GET` or `POST` with a service method, or \"handler\". The optional `ResourcePath` annotation associates a handler with a specific path, or \"endpoint\", relative to the servlet. `WebService` selects the best method to execute based on the values provided by the caller. For example, this request would invoke the first method:\n\n```\nGET /math/sum?a=2\u0026b=4\n```\n \nwhile this would invoke the second:\n\n```\nGET /math/sum?values=1\u0026values=2\u0026values=3\n```\n\nIn either case, the service would return the value 6 in response. \n\nThe optional `Description` annotation is used to document a service implementation and is discussed in more detail [later](#api-documentation).\n\n### Method Parameters\nMethod parameters may be any of the following types:\n\n* `Byte`/`byte`\n* `Short`/`short`\n* `Integer`/`int`\n* `Long`/`long`\n* `Float`/`float`\n* `Double`/`double`\n* `Boolean`/`boolean`\n* `Character`/`char`\n* `String`\n* `java.net.URI`\n* `java.nio.file.Path`\n* `java.time.Instant`\n* `java.time.LocalDate`\n* `java.time.LocalTime`\n* `java.time.LocalDateTime`\n* `java.time.Duration`\n* `java.time.Period`\n* `java.util.Date`\n* `java.util.UUID`\n\nThe following multi-value types are also supported:\n\n* `java.util.List`\n* `java.util.Set`\n* array/varargs\n\nAdditionally, `java.util.Map`, bean, and record types are supported for [body content](#body-content). Arguments of type `jakarta.servlet.http.Part` may be used with `POST` requests submitted as [multi-part form data](https://jakarta.ee/specifications/servlet/5.0/jakarta-servlet-spec-5.0#_MultipartConfig).\n\nUnspecified values are automatically converted to `0`, `false`, or the null character for primitive types. `Date` values are decoded from a long value representing epoch time in milliseconds. Other values are parsed from their string representations.\n\n`List`, `Set`, and array elements are automatically converted to their declared types. If no values are provided for a list, set, or array parameter, an empty instance (not `null`) will be passed to the method.\n\nIf a provided value cannot be coerced to the expected type, an HTTP 403 (forbidden) response will be returned. If no method is found that matches the provided arguments, HTTP 405 (method not allowed) will be returned.\n\nNote that service classes must be compiled with the `-parameters` flag so that parameter names are available at runtime.\n\n#### Required Parameters\nParameters that must be provided by the caller can be indicated by the `Required` annotation. For example, the following service method accepts a single required `owner` argument:\n\n```java\n@RequestMethod(\"GET\")\npublic List\u003cPet\u003e getPets(@Required String owner) throws SQLException { \n    ... \n}\n```\n\n`List`, `Set`, and array parameters are implicitly required, since these values will never be `null` (though they may be empty). For all other parameter types, HTTP 403 will be returned if a required value is not provided.\n\n#### Custom Parameter Names\nThe `Name` annotation can be used to associate a custom name with a method parameter. For example:\n\n```java\n@WebServlet(urlPatterns = {\"/members/*\"}, loadOnStartup = 1)\npublic class MemberService extends WebService {\n    @RequestMethod(\"GET\")\n    public List\u003cPerson\u003e getMembers(\n        @Name(\"first_name\") String firstName,\n        @Name(\"last_name\") String lastName\n    ) {\n        ...\n    }\n}\n```\n\nThis method could be invoked as follows: \n\n```\nGET /members?first_name=foo*\u0026last_name=bar*\n```\n\n### Path Variables\nPath variables (or \"keys\") are specified by a \"?\" character in a handler's resource path. For example, the `itemID` argument in the method below is provided by a path variable:\n\n```java\n@RequestMethod(\"GET\")\n@ResourcePath(\"items/?\")\n@Description(\"Returns detailed information about a specific item.\")\npublic ItemDetail getItem(\n    @Description(\"The item ID.\") Integer itemID\n) throws SQLException { ... }\n```\n\nPath parameters must precede query parameters in the method signature and are implicitly required. Values are mapped to method arguments in declaration order.\n\n### Body Content\nBody content may be declared as the final parameter in a `POST` or `PUT` handler. For example, this method accepts an item ID as a path variable and an instance of `ItemDetail` as a body argument:\n\n```java\n@RequestMethod(\"PUT\")\n@ResourcePath(\"items/?\")\n@Description(\"Updates an item.\")\npublic void updateItem(\n    @Description(\"The item ID.\") Integer itemID,\n    @Description(\"The updated item.\") ItemDetail item\n) throws SQLException { ... }\n```\n\nLike path parameters, body parameters are implicitly required. By default, content is assumed to be JSON and is automatically converted to the appropriate type. Subclasses can override the `decodeBody()` method to perform custom conversions.\n\nA body parameter of type `Void` may be used to indicate that the handler will process the input stream directly, as discussed [below](#request-and-repsonse-properties).\n\n### Return Values\nReturn values are converted to JSON as follows:\n\n* `Number`/numeric primitive: number\n* `Boolean`/`boolean`: boolean\n* `CharSequence`: string\n* `java.util.Date`: number representing epoch time in milliseconds\n* `Iterable`: array\n* `java.util.Map`, bean, or record type: object\n\nAdditionally, instances of the following types are automatically converted to their string representations:\n\n* `Character`/`char`\n* `Enum`\n* `java.net.URI`\n* `java.nio.file.Path`\n* `java.time.TemporalAccessor`\n* `java.time.TemporalAmount`\n* `java.util.UUID`\n\nBy default, an HTTP 200 (OK) response is returned when a service method completes successfully. However, if the method is annotated with `Creates`, HTTP 201 (created) will be returned instead. If the method is annotated with `Accepts`, HTTP 202 (accepted) will be returned. If the handler's return type is `void` or `Void`, HTTP 204 (no content) will be returned.\n\nIf a service method returns `null`, an HTTP 404 (not found) response will be returned.\n\nAlthough return values are encoded as JSON by default, subclasses can override the `encodeResult()` method of the `WebService` class to support alternative representations. See the method documentation for more information.\n\n### Exceptions\nIf an exception is thrown by a service method and the response has not yet been committed, the exception message (if any) will be returned as plain text in the response body. Error status is determined as follows:\n\n* `IllegalArgumentException` or `UnsupportedOperationException` - HTTP 403 (forbidden)\n* `NoSuchElementException` - HTTP 404 (not found)\n* `IllegalStateException` - HTTP 409 (conflict)\n* Any other exception - HTTP 500 (internal server error)\n\nSubclasses can override the `reportError()` method to perform custom error handling.\n\n### Database Connectivity\nFor services that require database connectivity, the following method can be used to obtain a JDBC connection object associated with the current invocation:\n\n```java\nprotected static Connection getConnection() { ... }\n```\n\nThe connection is opened via a data source identified by `getDataSourceName()`, which returns `null` by default. Service classes must override this method to provide the name of a valid data source.\n\nAuto-commit is disabled so an entire request will be processed within a single transaction. If the request completes successfully, the transaction is committed. Otherwise, it is rolled back.\n\n### Request and Repsonse Properties\nThe following methods provide access to the request and response objects associated with the current invocation:\n\n```java\nprotected HttpServletRequest getRequest() { ... }\nprotected HttpServletResponse getResponse() { ... }\n```\n\nFor example, a service might use the request to read directly from the input stream, or use the response to return a custom header.\n\nThe response object can also be used to produce a custom result. If a service method commits the response by writing to the output stream, the method's return value (if any) will be ignored by `WebService`. This allows a service to return content that cannot be easily represented as JSON, such as image data.\n\n### Inter-Service Communication\nA reference to any active service can be obtained via the `getInstance()` method of the `WebService` class. This can be useful when the implementation of one service depends on functionality provided by another service, for example.\n\n### API Documentation\nAn index of all active services can be found at the application's context root:\n\n```\nGET http://localhost:8080/kilo-test/\n```\n\n\u003cimg src=\"README/api-index.png\" width=\"640px\"/\u003e\n\nDocumentation for a specific service can be viewed by appending \"?api\" to the service's base URL:\n\n```\nGET http://localhost:8080/kilo-test/catalog?api\n```\n\n\u003cimg src=\"README/catalog-api.png\" width=\"640px\"/\u003e\n\nImplementations can provide additional information about service types and operations using the `Description` annotation. For example:\n\n```java\n@WebServlet(urlPatterns = {\"/catalog/*\"}, loadOnStartup = 1)\n@Description(\"Catalog example service.\")\npublic class CatalogService extends AbstractDatabaseService {\n    @RequestMethod(\"GET\")\n    @ResourcePath(\"items\")\n    @Description(\"Returns a list of all items in the catalog.\")\n    public List\u003cItem\u003e getItems() throws SQLException {\n        ...\n    }\n    \n    ...\n}\n```\n\nDescriptions can also be associated with bean types, enums, and records:\n\n```java\n@Table(\"item\")\n@Description(\"Represents an item in the catalog.\")\npublic interface Item {\n    @Name(\"id\")\n    @Column(\"id\")\n    @PrimaryKey\n    @Description(\"The item's ID.\")\n    Integer getID();\n    void setID(Integer id);\n\n    @Column(\"description\")\n    @Index\n    @Description(\"The item's description.\")\n    @Required\n    String getDescription();\n    void setDescription(String description);\n\n    @Column(\"price\")\n    @Description(\"The item's price.\")\n    @Required\n    Double getPrice();\n    void setPrice(Double price);\n}\n```\n\n```java\n@Description(\"Represents a size option.\")\npublic enum Size implements Numeric {\n    @Description(\"A small size.\")\n    SMALL(10),\n    @Description(\"A medium size.\")\n    MEDIUM(20),\n    @Description(\"A large size.\")\n    LARGE(30);\n\n    private final int value;\n\n    Size(int value) {\n        this.value = value;\n    }\n\n    @Override\n    public int value() {\n        return value;\n    }\n}\n```  \n\n```java\n@Description(\"Represents an x/y coordinate pair.\")\npublic record Coordinates(\n    @Description(\"The x-coordinate.\") @Required int x,\n    @Description(\"The y-coordinate.\") @Required int y\n) {\n}\n```\n\nThe `FormData` annotation can be used to indicate that a method accepts [form data](https://www.w3.org/TR/html401/interact/forms.html#h-17.13.4). Types or methods annotated as `Deprecated` will be identified as such in the output.\n\nA JSON version of the generated documentation can be obtained by specifying an \"Accept\" type of \"application/json\" in the request headers. The response can be used to process an API definition programatically; for example, to generate client-side stub code. \n\n## WebServiceProxy\nThe `WebServiceProxy` class is used to submit API requests to a server. It provides the following constructor, which accepts a string representing the HTTP method to execute and the URI of the requested resource:\n\n```java\npublic WebServiceProxy(String method, URI uri) { ... }\n```\n\nQuery arguments are specified via a map passed to the `setArguments()` method. Any value may be used as an argument and will generally be encoded using its string representation. However, `Date` instances are first converted to a long value representing epoch time in milliseconds. Additionally, `Collection` or array instances represent multi-value parameters and behave similarly to `\u003cselect multiple\u003e` tags in HTML forms.\n\nBody content is specified via the `setBody()` method. By default, it will be serialized as JSON; however, the `setRequestHandler()` method can be used to facilitate alternate encodings:\n\n```java\npublic interface RequestHandler {\n    String getContentType();\n    void encodeRequest(Object body, OutputStream outputStream) throws IOException;\n}\n```\n\nFor example, the `WebServiceProxy.FormDataRequestHandler` class submits requests as form data. When using the multi-part encoding (the default), instances of `java.nio.file.Path` represent file uploads and behave similarly to `\u003cinput type=\"file\"\u003e` tags in HTML.\n\nService operations are invoked via the following method:\n\n```java\npublic Object invoke() throws IOException { ... }\n```\n\nBy default, response content is assumed to be JSON. The `setResponseHandler()` method can be used to decode alternate representations:\n\n```java\npublic interface ResponseHandler {\n    Object decodeResponse(InputStream inputStream, String contentType) throws IOException;\n}\n```\n\nIf an operation does not complete successfully, the default error handler will throw a `WebServiceException` (a subclass of `IOException`). If the type of the error response is \"text/plain\", the response content will be provided in the exception message. \n\nA custom error handler can be provided via `setErrorHandler()`:\n\n```java\npublic interface ErrorHandler {\n    void handleResponse(InputStream errorStream, String contentType, int statusCode) throws IOException;\n}\n```\n\nThe following code demonstrates how `WebServiceProxy` might be used to access the operations of the simple math service discussed [earlier](#webservice):\n\n```java\n// GET /math/sum?a=2\u0026b=4\nvar webServiceProxy = new WebServiceProxy(\"GET\", baseURI.resolve(\"math/sum\"));\n\nwebServiceProxy.setArguments(mapOf(\n    entry(\"a\", 4),\n    entry(\"b\", 2)\n));\n\nSystem.out.println(webServiceProxy.invoke()); // 6.0\n```\n\n```java\n// GET /math/sum?values=1\u0026values=2\u0026values=3\nvar webServiceProxy = new WebServiceProxy(\"GET\", baseURI.resolve(\"math/sum\"));\n\nwebServiceProxy.setArguments(mapOf(\n    entry(\"values\", listOf(1, 2, 3))\n));\n\nSystem.out.println(webServiceProxy.invoke()); // 6.0\n```\n\n`POST`, `PUT`, and `DELETE` operations are also supported. The `listOf()` and `mapOf()` methods are discussed in more detail [later](#collections-and-optionals).\n\n### Typed Invocation\n`WebServiceProxy` additionally provides the following methods to facilitate convenient, type-safe access to web APIs:\n\n```java\npublic static \u003cT\u003e T of(Class\u003cT\u003e type, URI baseURI) { ... }\npublic static \u003cT\u003e T of(Class\u003cT\u003e type, URI baseURI, Map\u003cString, Object\u003e headers) { ... }\n```\n\nBoth versions return an implementation of a given interface that submits requests to the provided URI. An optional map accepted by the second version can be used to provide common request headers.\n\nThe optional `ServicePath` annotation can be used to associate a base path with a proxy type. The `RequestMethod` and `ResourcePath` annotations are used as described [earlier](#webservice) for `WebService`. Proxy methods must include a throws clause that declares `IOException`, so that callers can handle unexpected failures. For example:\n\n```java\n@ServicePath(\"math\")\npublic interface MathServiceProxy {\n    @RequestMethod(\"GET\")\n    @ResourcePath(\"sum\")\n    double getSum(double a, double b) throws IOException;\n\n    @RequestMethod(\"GET\")\n    @ResourcePath(\"sum\")\n    double getSum(List\u003cDouble\u003e values) throws IOException;\n\n    default double getAverage(List\u003cDouble\u003e values) throws IOException {\n        return getSum(values) / values.size();\n    }\n}\n```\n\n```java\nvar mathServiceProxy = WebServiceProxy.of(MathServiceProxy.class, baseURI);\n\nSystem.out.println(mathServiceProxy.getSum(4, 2)); // 6.0\nSystem.out.println(mathServiceProxy.getSum(listOf(1.0, 2.0, 3.0))); // 6.0\n\nSystem.out.println(mathServiceProxy.getAverage(listOf(1.0, 2.0, 3.0, 4.0, 5.0))); // 3.0\n```\n\nThe [`Name`](#custom-parameter-names) and [`Required`](#required-parameters) annotations may also be applied to proxy method parameters. \n\nPath variables and body content are handled as described for [`WebService`](#webservice). Body parameters are required for `POST` and `PUT` methods. A body parameter of type `Void` may be used to indicate that a method does not accept a body.\n\nNote that proxy types must be compiled with the `-parameters` flag so their method parameter names are available at runtime.\n\n## JSONEncoder and JSONDecoder\nThe `JSONEncoder` and `JSONDecoder` classes are used internally by `WebService` and `WebServiceProxy` to process request and response data. However, they can also be used directly by application logic. For example:\n\n```java\nvar map = mapOf(\n    entry(\"vegetables\", listOf(\n        \"carrots\", \n        \"peas\", \n        \"potatoes\"\n    )),\n    entry(\"desserts\", listOf(\n        \"cookies\",\n        \"cake\",\n        \"ice cream\"\n    ))\n);\n\ntry (var outputStream = Files.newOutputStream(file)) {\n    var jsonEncoder = new JSONEncoder();\n\n    jsonEncoder.write(map, outputStream);\n}\n\ntry (var inputStream = Files.newInputStream(file)) {\n    var jsonDecoder = new JSONDecoder();\n\n    map = (Map\u003cString, List\u003cString\u003e\u003e)jsonDecoder.read(inputStream);\n}\n\nSystem.out.println(map.get(\"vegetables\").get(0)); // carrots\n```\n\n## TextEncoder and TextDecoder\nThe `TextEncoder` and `TextDecoder` classes can be used to write and read plain text content, respectively. For example:\n\n```java\nvar text = \"Hello, World!\";\n\ntry (var outputStream = Files.newOutputStream(file)) {\n    var textEncoder = new TextEncoder();\n\n    textEncoder.write(text, outputStream);\n}\n\ntry (var inputStream = Files.newInputStream(file)) {\n    var textDecoder = new TextDecoder();\n\n    text = textDecoder.read(inputStream);\n}\n\nSystem.out.println(text); // Hello, World!\n```\n\n## CSVEncoder\nThe `CSVEncoder` class serializes a sequence of map or bean values to CSV. The list passed to the constructor represents both the names of the columns in the output document and the keys or properties to which those columns correspond. For example:\n\n```java\nvar maps = listOf(\n    mapOf(\n        entry(\"a\", \"hello\"),\n        entry(\"b\", 123),\n        entry(\"c\", true)\n    ),\n    mapOf(\n        entry(\"a\", \"goodbye\"),\n        entry(\"b\", 456),\n        entry(\"c\", false)\n    )\n);\n\nvar csvEncoder = new CSVEncoder(listOf(\"a\", \"b\", \"c\"));\n\ncsvEncoder.write(maps, System.out);\n```\n\nThis code would produce the following output:\n\n```csv\n\"a\",\"b\",\"c\"\n\"hello\",123,true\n\"goodbye\",456,false\n```\n\n## TemplateEncoder\nThe `TemplateEncoder` class transforms an object hierarchy (known as a \"data dictionary\") into an output format using a [template document](template-reference.md). Template syntax is based loosely on the [Mustache](https://mustache.github.io) specification and supports most Mustache features. \n\n`TemplateEncoder` provides the following constructors:\n\n```java\npublic TemplateEncoder(URI uri) { ... }\npublic TemplateEncoder(Class\u003c?\u003e type, String name) { ... }\n```\n\nThe first accepts the location of a template document as a `URI`. The second determines the location of the template via the provided type and resource name. \n\nFor example, this code applies a template named \"example.html\" to a map instance:\n\n```java\nvar map = mapOf(\n    entry(\"a\", \"hello\"),\n    entry(\"b\", 123),\n    entry(\"c\", true)\n);\n\nvar templateEncoder = new TemplateEncoder(Examples.class, \"example.html\");\n\ntemplateEncoder.write(map, System.out);\n```\n\nGiven the following template as input:\n\n```html\n\u003chtml\u003e\n\u003cbody\u003e\n\u003cp\u003e{{a}}\u003c/p\u003e\n\u003cp\u003e{{b}}\u003c/p\u003e\n\u003cp\u003e{{c}}\u003c/p\u003e\n\u003c/body\u003e\n\u003c/html\u003e\n```\n\nthe code would produce this output:\n\n```html\n\u003chtml\u003e\n\u003cbody\u003e\n\u003cp\u003ehello\u003c/p\u003e\n\u003cp\u003e123\u003c/p\u003e\n\u003cp\u003etrue\u003c/p\u003e\n\u003c/body\u003e\n\u003c/html\u003e\n```\n\n## BeanAdapter\nThe `BeanAdapter` class provides access to Java bean properties via the `Map` interface. For example:\n\n```java\nvar course = new Course();\n\ncourse.setName(\"CS 101\");\ncourse.setBuilding(\"Technology Lab\");\ncourse.setRoomNumber(210);\n\nvar courseAdapter = new BeanAdapter(course);\n\nSystem.out.println(courseAdapter.get(\"name\")); // CS 101\nSystem.out.println(courseAdapter.get(\"building\")); // Technology Lab\nSystem.out.println(courseAdapter.get(\"roomNumber\")); // 210\n```\n\n`BeanAdapter` can also be used to facilitate type-safe access to loosely typed data structures:\n\n```java\nvar map = mapOf(\n    entry(\"name\", \"CS 101\"),\n    entry(\"building\", \"Technology Lab\"),\n    entry(\"roomNumber\", 210)\n);\n\nvar course = BeanAdapter.coerce(map, Course.class);\n\nSystem.out.println(course.getName()); // CS 101\nSystem.out.println(course.getBuilding()); // Technology Lab\nSystem.out.println(course.getRoomNumber()); // 210\n```\n\nAn interface can be used instead of a class to provide a strongly typed \"view\" of the underlying data. For example:\n\n```java\npublic interface Weather {\n    LocalDate getDate();\n    String getConditions();\n    double getHigh();\n    double getLow();\n}\n```\n\n```java\nvar map = mapOf(\n    entry(\"date\", \"2024-04-08\"),\n    entry(\"conditions\", \"cloudy\"),\n    entry(\"high\", 52.1),\n    entry(\"low\", 43.5)\n);\n\nvar weather = BeanAdapter.coerce(map, Weather.class);\n\nSystem.out.println(weather.getDate()); // 2024-04-08\nSystem.out.println(weather.getConditions()); // cloudy\nSystem.out.println(weather.getHigh()); // 52.1\nSystem.out.println(weather.getLow()); // 43.5\n```\n\n### Required Properties\nThe `Required` annotation introduced [previously](#required-parameters) can also be used to indicate that a property must contain a value. For example:\n\n```java\npublic class Vehicle {\n    private String manufacturer;\n    private Integer year;\n\n    @Required\n    public String getManufacturer() {\n        return manufacturer;\n    }\n\n    public void setManufacturer(String manufacturer) {\n        this.manufacturer = manufacturer;\n    }\n\n    @Required\n    public Integer getYear() {\n        return year;\n    }\n\n    public void setYear(Integer year) {\n        this.year = year;\n    }\n}\n```\n\nBecause both \"manufacturer\" and \"year\" are required, an attempt to coerce an empty map to a `Vehicle` instance would produce an `IllegalArgumentException`:\n\n```java\nvar vehicle = BeanAdapter.coerce(mapOf(), Vehicle.class); // throws\n```\n\nAdditionally, although the annotation will not prevent a caller from programmatically assigning a `null` value to either property, attempting to dynamically set an invalid value will generate an `IllegalArgumentException`:\n\n```java\nvar vehicle = new Vehicle();\n\nvar vehicleAdapter = new BeanAdapter(vehicle);\n\nvehicleAdapter.put(\"manufacturer\", null); // throws\n```\n\nSimilarly, attempting to dynamically access an invalid value will result in an `UnsupportedOperationException`:\n\n```java\nvehicleAdapter.get(\"manufacturer\"); // throws\n```\n\n### Custom Property Names\nThe `Name` annotation introduced [previously](#custom-parameter-names) can also be used with bean properties. For example:\n\n```java\npublic class Person {\n    private String firstName = null;\n    private String lastName = null;\n\n    @Name(\"first_name\")\n    public String getFirstName() {\n        return firstName;\n    }\n\n    public void setFirstName(String firstName) {\n        this.firstName = firstName;\n    }\n\n    @Name(\"last_name\")\n    public String getLastName() {\n        return lastName;\n    }\n\n    public void setLastName(String lastName) {\n        this.lastName = lastName;\n    }\n}\n```\n\nThe preceding class would be serialized to JSON like this:\n\n```json\n{\n  \"first_name\": \"John\",\n  \"last_name\": \"Smith\"\n}\n```\n\nrather than this:\n\n```json\n{\n  \"firstName\": \"John\",\n  \"lastName\": \"Smith\"\n}\n```\n\n## QueryBuilder and ResultSetAdapter\nThe `QueryBuilder` class provides support for programmatically constructing and executing SQL queries. For example, given the following tables (adapted from the MySQL tutorial):\n\n```sql\ncreate table owner (\n    name varchar(20),\n    primary key (name)\n);\n```\n\n```sql\ncreate table pet (\n    name varchar(20),\n    owner varchar(20),\n    species varchar(20),\n    sex char(1),\n    birth date,\n    death date,\n    primary key (name),\n    foreign key (owner) references owner(name)\n);\n```\n\nthis code could be used to create a query that returns all rows associated with a particular owner:\n\n```java\nvar queryBuilder = new QueryBuilder();\n\nqueryBuilder.appendLine(\"select * from pet where owner = :owner order by name\");\n```\n\nThe colon character identifies \"owner\" as a parameter, or variable. Parameter values, or arguments, can be passed to `QueryBuilder`'s `executeQuery()` method as shown below:\n\n```java\ntry (var statement = queryBuilder.prepare(getConnection());\n    var results = queryBuilder.executeQuery(statement, mapOf(\n        entry(\"owner\", owner)\n    ))) {\n    ...\n}\n```\n\nThe `ResultSetAdapter` type returned by `executeQuery()` provides access to the contents of a JDBC result set via the `Iterable` interface. Individual rows are represented by `Map` instances produced by the adapter's iterator. The results could be coerced to a list of `Pet` instances and returned to the caller, or used as the data dictionary for a template document:\n\n```java\nreturn results.stream().map(result -\u003e BeanAdapter.coerce(result, Pet.class)).toList();\n```\n\n```java\nvar templateEncoder = new TemplateEncoder(getClass(), \"pets.xml\");\n\ntemplateEncoder.write(results, response.getOutputStream());\n```\n\n### Schema Annotations\n`QueryBuilder` also offers a simplified approach to query construction using \"schema annotations\". For example, given these type definitions:\n\n```java\n@Table(\"owner\")\npublic interface Owner {\n    @Column(\"name\")\n    @PrimaryKey\n    @Index\n    String getName();\n}\n```\n\n```java\n@Table(\"pet\")\npublic interface Pet {\n    @Column(\"name\")\n    @PrimaryKey\n    @Index\n    String getName();\n\n    @Column(\"owner\")\n    @ForeignKey(Owner.class)\n    String getOwner();\n\n    @Column(\"species\")\n    String getSpecies();\n\n    @Column(\"sex\")\n    String getSex();\n\n    @Column(\"birth\")\n    LocalDate getBirth();\n\n    @Column(\"death\")\n    LocalDate getDeath();\n}\n```\n\nthe preceding query could be written as follows:\n\n```java\nvar queryBuilder = QueryBuilder.select(Pet.class)\n    .filterByForeignKey(Owner.class, \"owner\")\n    .ordered(true);\n```\n\nThe `Table` annotation associates an entity type with a database table. Similarly, the `Column` annotation associates a property with a column in the table. Both are used to create the \"select\" statement in the preceding example. The `PrimaryKey` and `ForeignKey` annotations represent relationships between entity types and are used to construct the \"where\" clause. The `Index` annotation indicates that a property is part of the default sort order for an entity and is used to construct the \"order by\" clause.\n\nThis code creates a query that selects all actors appearing in a particular film, identified by the \"filmID\" parameter:\n\n```java\nvar queryBuilder = QueryBuilder.select(Actor.class)\n    .join(FilmActor.class, Actor.class)\n    .filterByForeignKey(FilmActor.class, Film.class, \"filmID\")\n    .ordered(true);\n```\n\nPrimary and foreign key annotations associated with the [`Actor`](kilo-test/src/main/java/org/httprpc/kilo/test/Actor.java), [`Film`](kilo-test/src/main/java/org/httprpc/kilo/test/Film.java), and [`FilmActor`](kilo-test/src/main/java/org/httprpc/kilo/test/FilmActor.java) types are used to construct the \"join\" clause. The resulting query is functionally equivalent to the following SQL:\n\n```sql\nselect actor.* from actor \njoin film_actor on actor.actor_id = film_actor.actor_id \nwhere film_actor.film_id = :filmID\norder by last_name asc, first_name asc\n```\n\nInsert, update, and delete operations are also supported. See the [pet](kilo-test/src/main/java/org/httprpc/kilo/test/PetService.java), [catalog](kilo-test/src/main/java/org/httprpc/kilo/test/CatalogService.java), and [film](kilo-test/src/main/java/org/httprpc/kilo/test/FilmService.java) service examples for more information.\n\n## ElementAdapter\nThe `ElementAdapter` class provides access to the contents of an XML DOM `Element` via the `Map` interface. For example, the following markup might be used to represent the status of a bank account:\n\n```xml\n\u003caccount id=\"101\"\u003e\n    \u003cholder\u003e\n        \u003cfirstName\u003eJohn\u003c/firstName\u003e\n        \u003clastName\u003eSmith\u003c/lastName\u003e\n    \u003c/holder\u003e\n    \u003ctransactions\u003e\n        \u003ccredit\u003e\n            \u003camount\u003e100.00\u003c/amount\u003e\n            \u003cdate\u003e10/5/2024\u003c/date\u003e\n        \u003c/credit\u003e\n        \u003ccredit\u003e\n            \u003camount\u003e50.00\u003c/amount\u003e\n            \u003cdate\u003e10/12/2024\u003c/date\u003e\n        \u003c/credit\u003e\n        \u003cdebit\u003e\n            \u003camount\u003e25.00\u003c/amount\u003e\n            \u003cdate\u003e10/14/2024\u003c/date\u003e\n        \u003c/debit\u003e\n        \u003ccredit\u003e\n            \u003camount\u003e75.00\u003c/amount\u003e\n            \u003cdate\u003e10/19/2024\u003c/date\u003e\n        \u003c/credit\u003e\n    \u003c/transactions\u003e\n\u003c/account\u003e\n```\n\nThis code could be used to load the document and adapt the root element: \n\n```java\nvar documentBuilderFactory = DocumentBuilderFactory.newInstance();\n\ndocumentBuilderFactory.setExpandEntityReferences(false);\ndocumentBuilderFactory.setIgnoringComments(true);\n\nvar documentBuilder = documentBuilderFactory.newDocumentBuilder();\n\nDocument document;\ntry (var inputStream = getClass().getResourceAsStream(\"account.xml\")) {\n    document = documentBuilder.parse(inputStream);\n}\n\nvar accountAdapter = new ElementAdapter(document.getDocumentElement());\n```\n\nAttribute values can be obtained by prepending an \"@\" symbol to the attribute name:\n\n```java\nvar id = accountAdapter.get(\"@id\");\n\nSystem.out.println(id); // 101\n```\n\nIndividual sub-elements can be accessed by name. The text content of an element can be obtained by calling `toString()` on the returned value; for example:\n\n```java\nvar holder = (Map\u003cString, Object\u003e)accountAdapter.get(\"holder\");\n\nvar firstName = holder.get(\"firstName\");\nvar lastName = holder.get(\"lastName\");\n\nSystem.out.println(String.format(\"%s, %s\", lastName, firstName)); // Smith, John\n```\n\nAn element's text content can also be accessed via the reserved \".\" key.\n\nMultiple sub-elements can be obtained by appending an asterisk to the element name:\n\n```java\nvar transactions = (Map\u003cString, Object\u003e)accountAdapter.get(\"transactions\");\nvar credits = (List\u003cMap\u003cString, Object\u003e\u003e)transactions.get(\"credit*\");\n\nfor (var credit : credits) {\n    System.out.println(credit.get(\"amount\"));\n    System.out.println(credit.get(\"date\"));\n}\n```\n\n`ElementAdapter` also supports `put()` and `remove()` for modifying an element's contents.\n\n## Pipe\nThe `Pipe` class provides a vehicle by which a producer thread can submit a sequence of elements for retrieval by a consumer thread. It implements the `Iterable` interface and returns values as they become available, blocking if necessary.\n\nFor example, the following code executes a SQL query that retrieves all rows from an `employees` table:\n\n```java\n@Table(\"employees\")\npublic interface Employee {\n    @Column(\"emp_no\")\n    @PrimaryKey\n    Integer getEmployeeNumber();\n\n    @Column(\"first_name\")\n    @Required\n    String getFirstName();\n\n    @Column(\"last_name\")\n    @Required\n    String getLastName();\n\n    @Column(\"gender\")\n    @Required\n    String getGender();\n\n    @Column(\"birth_date\")\n    @Required\n    LocalDate getBirthDate();\n\n    @Column(\"hire_date\")\n    @Required\n    LocalDate getHireDate();\n}\n```\n\n```java\nvar queryBuilder = QueryBuilder.select(Employee.class);\n\ntry (var statement = queryBuilder.prepare(getConnection());\n    var results = queryBuilder.executeQuery(statement)) {\n    return results.stream().map(result -\u003e BeanAdapter.coerce(result, Employee.class)).toList();\n}\n```\n\nAll of the rows are read and added to the list before anything is returned to the caller. For small result sets, the latency and memory implications associated with this approach might be acceptable. However, for larger data volumes the following alternative may be preferable. The query is executed on a background thread, and the transformed results are streamed back to the caller via a pipe:\n\n```java\nvar pipe = new Pipe\u003cEmployee\u003e(4096, 15000);\n\nvar connection = getConnection();\n\nexecutorService.submit(() -\u003e {\n    var queryBuilder = QueryBuilder.select(Employee.class);\n\n    try (var statement = queryBuilder.prepare(connection);\n        var results = queryBuilder.executeQuery(statement)) {\n        pipe.accept(results.stream().map(result -\u003e BeanAdapter.coerce(result, Employee.class)));\n    } catch (SQLException exception) {\n        throw new RuntimeException(exception);\n    }\n});\n\nreturn pipe;\n```\n\nThe pipe is configured with a capacity of 4K elements and a timeout of 15s. Limiting the capacity ensures that the producer does not do more work than necessary if the consumer fails to retrieve all of the data. Similarly, specifying a timeout ensures that the consumer does not wait indefinitely if the producer stops submitting data.\n\nThis implementation is slightly more verbose than the first one. However, because no intermediate buffering is required, results are available to the caller sooner, and CPU and memory load is reduced.\n\nFor more information, see the [employee service](kilo-test/src/main/java/org/httprpc/kilo/test/EmployeeService.java) example.\n\n## Collections and Optionals\nThe `Collections` class provides a set of static utility methods for declaratively instantiating list, map, and set values:\n\n```java\npublic static \u003cE\u003e List\u003cE\u003e listOf(E... elements) { ... }\npublic static \u003cK, V\u003e Map\u003cK, V\u003e mapOf(Map.Entry\u003cK, V\u003e... entries) { ... }\npublic static \u003cK, V\u003e Map.Entry\u003cK, V\u003e entry(K key, V value) { ... }\npublic static \u003cE\u003e Set\u003cE\u003e setOf(E... elements) { ... }\n```\n\nThey offer an alternative to similar methods defined by the `List`, `Map`, and `Set` interfaces, which produce immutable instances and do not permit `null` values. The following immutable variants are provided as well:\n\n```java\npublic static \u003cE\u003e List\u003cE\u003e immutableListOf(E... elements) { ... }\npublic static \u003cK, V\u003e Map\u003cK, V\u003e immutableMapOf(Map.Entry\u003cK, V\u003e... entries) { ... }\npublic static \u003cE\u003e Set\u003cE\u003e immutableSetOf(E... elements) { ... }\n```\n\n`Collections` also includes support for declaring empty lists, maps, and sets:\n\n```java\npublic static \u003cE\u003e List\u003cE\u003e emptyListOf(Class\u003cE\u003e elementType) { ... }\npublic static \u003cK, V\u003e Map\u003cK, V\u003e emptyMapOf(Class\u003cK\u003e keyType, Class\u003cV\u003e valueType) { ... }\npublic static \u003cE\u003e Set\u003cE\u003e emptySetOf(Class\u003cE\u003e elementType) { ... }\n```\n\nThese methods can be used in place of similar methods defined by the `java.util.Collections` class:\n\n```java\nvar list1 = java.util.Collections.\u003cInteger\u003eemptyList();\nvar list2 = emptyListOf(Integer.class);\n\nvar map1 = java.util.Collections.\u003cString, Integer\u003eemptyMap();\nvar map2 = emptyMapOf(String.class, Integer.class);\n\nvar set1 = java.util.Collections.\u003cInteger\u003eemptySet();\nvar set2 = emptySetOf(Integer.class);\n```\n\nThe `Optionals` class contains methods for working with optional (or \"nullable\") values:\n\n```java\npublic static \u003cT\u003e T coalesce(T value, Supplier\u003c? extends T\u003e supplier) { ... }\npublic static \u003cT, U\u003e U map(T value, Function\u003c? super T, ? extends U\u003e transform) { ... }\npublic static \u003cT\u003e void perform(T value, Consumer\u003c? super T\u003e action) { ... }\n```\n\nThese are provided as a less verbose alternative to similar methods defined by the `java.util.Optional` class:\n\n```java\nvar value = 123;\n\nvar a = Optional.ofNullable(null).orElse(value); // 123\nvar b = coalesce(null, () -\u003e value); // 123\n```\n\n```java\nvar value = \"hello\";\n\nvar a = Optional.ofNullable(value).map(String::length).orElse(null); // 5\nvar b = map(value, String::length); // 5\n```\n\n```java\nvar stringBuilder = new StringBuilder();\n\nOptional.ofNullable(\"abc\").ifPresent(stringBuilder::append); // abc\nperform(\"def\", stringBuilder::append); // abcdef\n```\n\n`Optionals` also provides the following method, which performs a \"safe\" cast:\n\n```java\npublic static \u003cT\u003e T cast(Object value, Class\u003cT\u003e type) {\n```\n\nIf the given value is an instance of the requested type, the cast will succeed; otherwise, the method will return `null`. For example:\n\n```java\nvar text = cast(\"abc\", String.class); // abc\n\nvar number = cast(\"abc\", Double.class); // null\n```\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fhttp-rpc%2Fkilo","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fhttp-rpc%2Fkilo","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fhttp-rpc%2Fkilo/lists"}