SDKs
Java / Kotlin SDK
The taxiql-jvm library lets you execute TaxiQL queries from Java and Kotlin applications, using query classes generated from your .taxi files at build time.
Installation
All modules are published to Maven Central under the org.taxilang group. Add the core module plus whichever transport suits your stack.
Maven
<!-- Core interfaces (required) -->
<dependency>
<groupId>org.taxilang</groupId>
<artifactId>taxiql-jvm-core</artifactId>
<version>VERSION</version>
</dependency>
<!-- Pick one transport -->
<dependency>
<groupId>org.taxilang</groupId>
<artifactId>taxiql-transport-ktor</artifactId>
<version>VERSION</version>
</dependency>Gradle
implementation("org.taxilang:taxiql-jvm-core:VERSION")
implementation("org.taxilang:taxiql-transport-ktor:VERSION")Replace VERSION with the latest release:
Releases are published to Maven Central.
Snapshot versions are published to the Orbital snapshot repository. To use a snapshot, add the repository to your pom.xml:
<repositories>
<repository>
<id>orbital-snapshots</id>
<url>https://repo.orbitalhq.com/snapshot</url>
<snapshots>
<enabled>true</enabled>
</snapshots>
</repository>
</repositories>Quick start
Given these queries in a .taxi file:
namespace com.acme.flights
query FindFlights {
find { Flight[] } as {
id : FlightNumber
departs : Origin
arrives : Destination
}[]
}
query FindFlightDetails(flightNumber: FlightNumber) {
find { Flight(FlightNumber == flightNumber) } as {
id : FlightNumber
departs : Origin
arrives : Destination
}
}The taxiql-maven-plugin generates FindFlightsQuery and FindFlightDetailsQuery at build time. You can use them directly:
import com.acme.flights.FindFlightDetailsQuery
import com.acme.flights.FindFlightsQuery
import lang.taxilang.jvm.transport.ktor.KtorTaxiTransport
suspend fun main() {
val client = KtorTaxiTransport("https://orbital.example.com")
// Fetch all flights
val flights = client.executeForResult(FindFlightsQuery()).getOrThrow()
println("Found ${flights.size} flights")
// Stream flights one by one
client.flow(FindFlightsQuery())
.onEach { println("${it.id}: ${it.departs} → ${it.arrives}") }
.collect()
// Fetch a single flight by number
val flight = client.executeForResult(FindFlightDetailsQuery("QF-1")).getOrThrow()
println("${flight.departs} → ${flight.arrives}")
}Code generation
Maven plugin
Add taxiql-maven-plugin to your pom.xml. It runs automatically during generate-sources and registers the output as a compile source root, so the generated classes are available to your code without any extra steps.
<plugin>
<groupId>org.taxilang</groupId>
<artifactId>taxiql-maven-plugin</artifactId>
<version>VERSION</version>
<executions>
<execution>
<goals>
<goal>generate-sources</goal>
</goals>
</execution>
</executions>
</plugin>Project structure
The plugin reads your taxi.conf to find query files. By default it looks for the conf file at src/main/taxi/taxi.conf:
# taxi.conf
name: com.acme/flights
version: 0.1.0
sourceRoot: src/Named queries anywhere under sourceRoot are picked up and compiled. A common layout:
src/main/java ← java source root
src/main/taxi ← taxi source root
src/
schema.taxi ← type definitions
queries/
FindFlights.taxi ← named queries
FindFlightDetails.taxi
taxi.conf
Running mvn generate-sources (or any lifecycle phase that includes it - like mvn compile) writes a Java class per query to target/generated-sources/taxi/.
Loading types from a remote Orbital server
If your types are defined in a running Orbital instance rather than local .taxi files, point the plugin at the server and it will pull the schema automatically:
<configuration>
<remoteUrl>https://orbital.example.com</remoteUrl>
<!-- optional - provide authentication headers -->
<remoteAuthHeaderValue>Bearer ${orbital.token}</remoteAuthHeaderValue>
</configuration>The remote schema is used to resolve types referenced in your queries. The generated classes still come from the queries in your local source tree.
What gets generated
For each named query, the plugin produces a Java class that:
- implements
UnaryTaxiQlQuery<List<T>>andStreamableTaxiQlQuery<T>forfind { T[] }queries, so you can either fetch the full list at once or stream results one by one with the same class - implements
UnaryTaxiQlQuery<T>forfind { T }queries - implements
StreamableTaxiQlQuery<T>forstream { T }queries - has a typed constructor for each query parameter
See reference docs below for examples.
Gradle plugin
A Gradle plugin is planned. If you’d like to use one, please reach out on Slack.
Transports
Ktor
Best choice for Kotlin applications. KtorTaxiTransport uses coroutines throughout and exposes executeForResult() and flow() as the primary API.
<dependency>
<groupId>org.taxilang</groupId>
<artifactId>taxiql-transport-ktor</artifactId>
<version>VERSION</version>
</dependency>val client = KtorTaxiTransport(
baseUrl = "https://orbital.example.com",
authHeaderProvider = { mapOf("Authorization" to "Bearer ${token()}") }
)KtorTaxiTransport implements AutoCloseable — use it in a use block or inject it as a long-lived singleton and call close() on shutdown.
Fetching a single result
val result = client.executeForResult(FindFlightsQuery())
result.onSuccess { flights -> println("Found ${flights.size} flights") }
result.onFailure { error -> println("Failed: ${error.message}") }executeForResult() is a suspend function that returns Result<T>. It surfaces server-side failures without throwing, which makes it composable in coroutine pipelines.
Streaming results
client.flow(FindFlightsQuery())
.onEach { println("${it.id}: ${it.departs} → ${it.arrives}") }
.collect()flow() returns a Kotlin Flow<T> where T is the element type. Errors during the stream (including server-reported errors) are thrown as exceptions and can be caught in the normal coroutine way.
Spring WebClient
Best choice for Spring WebFlux applications. stream() returns a Flux<T> and mono() returns a Mono<T>, making it a natural fit for reactive pipelines.
<dependency>
<groupId>org.taxilang</groupId>
<artifactId>taxiql-transport-webclient</artifactId>
<version>VERSION</version>
</dependency>Create a WebClient with the server base URL and pass it to the transport:
@Bean
fun taxiTransport(): WebClientTaxiTransport {
val webClient = WebClient.builder()
.baseUrl("https://orbital.example.com")
.defaultHeader("Authorization", "Bearer ${token()}")
.build()
return WebClientTaxiTransport(webClient)
}Fetching a single result
fun findFlights(): Mono<List<FindFlightsResult>> =
transport.mono(FindFlightsQuery())
fun findFlightDetails(number: String): Mono<FindFlightDetailsResult> =
transport.mono(FindFlightDetailsQuery(number))Streaming results
fun streamFlights(): Flux<FindFlightsResult> =
transport.stream(FindFlightsQuery())Errors during the stream surface as Flux.error / Mono.error and can be handled with .onErrorMap() or .doOnError().
Java HttpClient
Zero additional dependencies — uses the java.net.http.HttpClient built into Java 11+. Best for Java applications or anywhere you want to minimise the dependency footprint.
<dependency>
<groupId>org.taxilang</groupId>
<artifactId>taxiql-transport-http-client</artifactId>
<version>VERSION</version>
</dependency>var transport = new JavaHttpClientTaxiTransport(
"https://orbital.example.com",
() -> Map.of("Authorization", "Bearer " + token())
);Fetching a single result
// Non-blocking
transport.execute(new FindFlightsQuery())
.thenAccept(flights ->
System.out.println("Found " + flights.size() + " flights")
);
// Blocking
var flights = transport.execute(new FindFlightsQuery()).get();Streaming results
stream() returns a Publisher<T> compatible with Project Reactor, RxJava, and any other Reactive Streams library:
transport.stream(new FindFlightsQuery())
.subscribe(new Subscriber<>() {
public void onSubscribe(Subscription s) { s.request(Long.MAX_VALUE); }
public void onNext(FindFlightsResult f) { System.out.println(f.getId()); }
public void onError(Throwable t) { t.printStackTrace(); }
public void onComplete() { System.out.println("Done"); }
});Parameterized queries
When a query declares parameters, the generated class has a constructor that accepts them:
query FindFlightDetails(flightNumber: FlightNumber) {
find { Flight(FlightNumber == flightNumber) } as { ... }
}// Kotlin
val flight = client.executeForResult(FindFlightDetailsQuery("QF-1")).getOrThrow()// Java
var flight = transport.execute(new FindFlightDetailsQuery("QF-1")).get();Parameter types follow the Taxi type system. For type aliases wrapping primitives (like FlightNumber inherits String), the constructor parameter is the underlying Java type (String in this case), annotated with @DataType carrying the Taxi type name. This lets frameworks that understand Taxi semantics do type-safe wiring automatically.
Error handling
Query failures
When the server rejects a query — either with a non-2xx HTTP response or by emitting an event:error SSE event mid-stream — the SDK surfaces a QueryFailureException containing the server’s error message.
With executeForResult() (Ktor), failures come back as Result.failure rather than exceptions:
val result = client.executeForResult(FindFlightsQuery())
result
.onSuccess { flights -> println("Found ${flights.size} flights") }
.onFailure { error ->
if (error is QueryFailureException)
println("Query failed: ${error.failure.message}")
}With Spring WebClient, they surface as Mono.error / Flux.error:
transport.mono(FindFlightsQuery())
.onErrorMap(QueryFailureException::class.java) { e ->
MyApplicationException("Query failed: ${e.failure.message}")
}With CompletableFuture (Ktor’s execute() or Java HttpClient), the exception is wrapped in a standard ExecutionException:
try {
val flights = transport.execute(FindFlightsQuery()).get()
} catch (e: ExecutionException) {
val cause = e.cause
if (cause is QueryFailureException)
println("Query failed: ${cause.failure.message}")
}SSE stream errors
The server can signal an error mid-stream using an SSE error event:
event:error
data:{"message":"No data sources were found that can return Flight[]", ...}
The SDK detects this and terminates the stream with a QueryFailureException, using whatever error channel is natural for the transport. Any results emitted before the error are delivered normally.
With Ktor flow(), catch it like any other exception:
try {
client.flow(FindFlightsQuery())
.onEach { println(it.id) }
.collect()
} catch (e: QueryFailureException) {
println("Stream failed: ${e.failure.message}")
}With Spring WebClient’s Flux, handle it with .doOnError():
transport.stream(FindFlightsQuery())
.doOnError(QueryFailureException::class.java) { e ->
println("Stream failed: ${e.failure.message}")
}
.subscribe()Jackson configuration
All transports use Jackson for JSON deserialization. The default ObjectMapper is Kotlin-aware (via jackson-module-kotlin). Pass a custom objectMapper to the constructor if you need specific serialization settings such as custom date formats or naming strategies.
Reference
The following classes are used by the SDK. They’re generated by code-gen tools, such as the Maven plugin, rather than intended to be written by hand.
TaxiTransport
The core interface implemented by all three transports:
interface TaxiTransport {
fun <T> stream(query: StreamableTaxiQlQuery<T>): Publisher<T>
fun <T> execute(query: UnaryTaxiQlQuery<T>): CompletableFuture<T>
}KtorTaxiTransport and WebClientTaxiTransport extend this with idiomatic extras (flow(), executeForResult(), mono()) — prefer those in Kotlin and Spring code respectively.
UnaryTaxiQlQuery
Used for queries that return a single result or a complete list in one response:
// Single object
class FindFlightQuery(private val flightId: String) : UnaryTaxiQlQuery<Flight> {
override val taxiQl = """
query FindFlight(flightId: FlightId) {
find { Flight( FlightId == flightId ) }
}
""".trimIndent()
override val arguments = mapOf("flightId" to flightId)
override val responseType = object : TypeReference<Flight>() {}
}
// Collection
class FindAllFlightsQuery : UnaryTaxiQlQuery<List<Flight>> {
override val taxiQl = "find { Flight[] }"
override val arguments = emptyMap<String, Any?>()
override val responseType = object : TypeReference<List<Flight>>() {}
}StreamableTaxiQlQuery
Used for queries that stream results one element at a time:
class LiveFlightsQuery : StreamableTaxiQlQuery<Flight> {
override val taxiQl = "stream { Flight[] }"
override val arguments = emptyMap<String, Any?>()
override val streamType = object : TypeReference<Flight>() {}
}A find { T[] } query can implement both interfaces on the same class, allowing the caller to choose between receiving the full list at once or streaming element by element:
class FindAllFlightsQuery : UnaryTaxiQlQuery<List<Flight>>, StreamableTaxiQlQuery<Flight> {
override val taxiQl = "find { Flight[] }"
override val arguments = emptyMap<String, Any?>()
override val responseType = object : TypeReference<List<Flight>>() {}
override val streamType = object : TypeReference<Flight>() {}
}The taxiql-maven-plugin generates this dual-interface pattern automatically for all find { T[] } queries.