Kilo is an open-source framework for creating and consuming web APIs in Java. It also includes a number of general-purpose developer productivity features. The project's name comes from the nautical K or Kilo flag, which means "I wish to communicate with you":

Classes provided by the Kilo framework include:
Each is discussed in more detail below. Java 21 or later is required.
WebService is an abstract base class for web services. It extends the similarly abstract HttpServlet class and provides a thin, resource-oriented layer on top of the standard servlet API.
For example, the following service implements some simple mathematical operations:
@WebServlet(urlPatterns = "/math/*", loadOnStartup = 0)
@Description("Math service.")
public class MathService extends WebService {
@RequestMethod("GET")
@ResourcePath("sum")
@Description("Calculates the sum of two numbers.")
public double getSum(
@Description("The first number.") double a,
@Description("The second number.") double b
) {
return a + b;
}
@RequestMethod("GET")
@ResourcePath("sum")
@Description("Calculates the sum of a list of numbers.")
public double getSum(
@Description("The numbers to add.") List<Double> values
) {
var total = 0.0;
for (var value : values) {
total += value;
}
return total;
}
}
The 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:
GET /math/sum?a=2&b=4
while this would invoke the second:
GET /math/sum?values=1&values=2&values=3
In either case, the service would return the value 6 in response.
The optional Description annotation is used to document a service implementation and is discussed in more detail later.
Method parameters may be any of the following types:
Byte/byteShort/shortInteger/intLong/longFloat/floatDouble/doubleBoolean/booleanStringjava.time.Instantjava.time.LocalDatejava.time.LocalTimejava.time.LocalDateTimejava.time.Durationjava.time.Periodjava.util.UUIDAdditionally, these types are supported for multi-value parameters:
java.util.Listjava.util.Set/SequencedSet/SortedSetFor body content, the following types are also supported:
java.util.Map/SequencedMap/SortedMapUnspecified values are automatically converted to 0 or false for primitive types. If no values are provided for a multi-value parameter, an empty instance (not null) will be passed to the method.
If an argument 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.
Note that service classes must be compiled with the -parameters flag so that parameter names are available at runtime.
Parameters 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:
@RequestMethod("GET")
public List<Pet> getPets(@Required String owner) throws SQLException {
...
}
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.
The Name annotation can be used to associate a custom name with a method parameter. For example:
@WebServlet(urlPatterns = "/members/*", loadOnStartup = 0)
public class MemberService extends WebService {
@RequestMethod("GET")
public List<Person> getMembers(
@Name("first_name") String firstName,
@Name("last_name") String lastName
) {
...
}
}
This method could be invoked as follows:
GET /members?first_name=j*&last_name=smith
Path 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:
@RequestMethod("GET")
@ResourcePath("items/?")
@Description("Returns detailed information about a specific item.")
public ItemDetails getItem(
@Description("The item ID.") Integer itemID
) throws SQLException { ... }
Path parameters must precede query parameters in the method signature and are implicitly required. Values are mapped to method arguments in declaration order.
Body content is 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 ItemDetails as a body argument:
@RequestMethod("PUT")
@ResourcePath("items/?")
@Description("Updates an item.")
public void updateItem(
@Description("The item ID.") Integer itemID,
@Description("The updated item.") ItemDetails item
) throws SQLException { ... }
Like path parameters, body parameters are implicitly required. By default, content is assumed to be JSON and is automatically converted to the appropriate type. However, a body parameter of type org.w3c.dom.Document can be used to indicate that the method expects XML content.
Requests may also be submitted as form data. WebService collects parameter values into a single body argument that is passed to the handler method. For multi-part requests, jakarta.servlet.http.Part values are translated to instances of java.nio.file.Path.
A body parameter of type Void indicates that a handler either does not accept a body or will process the input stream directly.
Return values are converted to JSON as follows:
Boolean/boolean: booleanNumber/numeric primitive: numberCharSequence: stringIterable: arrayjava.util.Map, bean, or record type: objectCharacter or char values are converted to a number. Instances of java.util.Date or java.time.Instant are converted to a number representing epoch time in milliseconds. Instances of other types are converted to their string representations.
Alternatively, a method may return an instance of org.w3c.dom.Document to produce an XML response.
By default, HTTP 200 (OK) 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.
If a service method returns null, an HTTP 404 (not found) response will be returned.
If 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:
IllegalArgumentException or UnsupportedOperationException - HTTP 403 (forbidden)NoSuchElementException - HTTP 404 (not found)IllegalStateException - HTTP 409 (conflict)For services that require database connectivity, the following method can be used to obtain a JDBC connection object associated with the current invocation:
protected static Connection getConnection() { ... }
The connection is established via the openConnection() method, which returns null by default. Service classes must override this method to provide a valid connection instance.
Auto-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.
The following methods provide access to the request and response objects associated with the current invocation:
protected static HttpServletRequest getRequest() { ... }
protected static HttpServletResponse getResponse() { ... }
For example, a service might use the request to read directly from the input stream, or use the response to return a custom header.
The 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.
A 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.
Alternatively, the Instance annotation can be used to automatically inject a service instance into a member variable:
private @Instance MathService mathService = null;
The PageServlet class facilitates generation of document-oriented content such as invoices or reports. It supports read-only database access and shares a connection instance with WebService.
The abstract execute() method is called to produce the page data. An HTML template with the same name as the implementing class will be automatically applied to the value returned by this method, unless the method commits the response by writing to the output stream directly. This allows an implementation to support alternate representations such as CSV.
As with WebService, service instances are automatically injected into annotated fields. See MathServlet or PetServlet for more information.
An index of all active services can be found at the application's context root:
GET http://localhost:8080/kilo-test
Documentation for a specific service can be viewed by appending ".html" to the service's base URL:
GET http://localhost:8080/kilo-test/catalog.html
Implementations can provide additional information about service types and operations using the Description annotation. For example:
@WebServlet(urlPatterns = "/catalog/*", loadOnStartup = 0)
@Description("Catalog service.")
public class CatalogService extends AbstractDatabaseService {
@RequestMethod("GET")
@ResourcePath("items")
@Description("Returns a list of all items in the catalog.")
public List<Item> getItems() throws SQLException {
...
}
...
}
Descriptions can also be associated with bean types, records, and enums:
@Table("item")
@Description("Represents a catalog item.")
public interface Item {
@Name("id")
@Column("id")
@PrimaryKey
@Description("The item's ID.")
Integer getID();
void setID(Integer id);
@Column("description")
@Index
@Description("The item's description.")
@Required
String getDescription();
void setDescription(String description);
@Column("price")
@Description("The item's price.")
@Required
Double getPrice();
void setPrice(Double price);
}
@Description("Represents an x/y coordinate pair.")
public record Coordinates(
@Description("The x-coordinate.")
@Required
int x,
@Description("The y-coordinate.")
@Required
int y
) {
}
@Description("Represents a size option.")
public enum Size implements Numeric {
@Description("A small size.") SMALL(10),
@Description("A medium size.") MEDIUM(20),
@Description("A large size.") LARGE(30);
private final int value;
Size(int value) {
this.value = value;
}
@Override
public int value() {
return value;
}
}
Deprecated elements will be identified as such in the output.
The 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