# Java / Kotlin SDK

> Execute TaxiQL queries from Java and Kotlin applications using the taxiql-jvm client libraries

Source: https://taxilang.org/docs/sdks/java-kotlin

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.

_Available since 1.70.0_

## Installation

All modules are published to Maven Central under the `org.taxilang` group. Add the core module plus whichever transport suits your stack.

### Maven

```xml
<!-- 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

```kotlin
implementation("org.taxilang:taxiql-jvm-core:VERSION")
implementation("org.taxilang:taxiql-transport-ktor:VERSION")
```

Replace `VERSION` with the latest release:

![](https://img.shields.io/badge/dynamic/xml.svg?label=Taxi&url=http%3A%2F%2Frepo.orbitalhq.com%2Frelease%2Forg%2Ftaxilang%2Fparent%2Fmaven-metadata.xml&query=%2F%2Frelease&colorB=green&prefix=v&style=for-the-badge&logo=kotlin&logoColor=white)

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`:

```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:

```taxi
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:

```kotlin
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.

```xml
<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`:

```hocon
# 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:

```xml
<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>>` **and** `StreamableTaxiQlQuery<T>` for `find { 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>` for `find { T }` queries
- implements `StreamableTaxiQlQuery<T>` for `stream { T }` queries
- has a typed constructor for each query parameter

See [reference docs](#reference) below for examples.

### Gradle plugin

A Gradle plugin is planned. If you'd like to use one, please reach out on [Slack](https://join.slack.com/t/orbitalapi/shared_invite/zt-697laanr-DHGXXak5slqsY9DqwrkzHg).

---

## Transports

### Ktor

Best choice for Kotlin applications. `KtorTaxiTransport` uses coroutines throughout and exposes `executeForResult()` and `flow()` as the primary API.

```xml
<dependency>
  <groupId>org.taxilang</groupId>
  <artifactId>taxiql-transport-ktor</artifactId>
  <version>VERSION</version>
</dependency>
```

```kotlin
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

```kotlin
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

```kotlin
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.

```xml
<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:

```kotlin
@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

```kotlin
fun findFlights(): Mono<List<FindFlightsResult>> =
    transport.mono(FindFlightsQuery())

fun findFlightDetails(number: String): Mono<FindFlightDetailsResult> =
    transport.mono(FindFlightDetailsQuery(number))
```

#### Streaming results

```kotlin
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.

```xml
<dependency>
  <groupId>org.taxilang</groupId>
  <artifactId>taxiql-transport-http-client</artifactId>
  <version>VERSION</version>
</dependency>
```

```java
var transport = new JavaHttpClientTaxiTransport(
    "https://orbital.example.com",
    () -> Map.of("Authorization", "Bearer " + token())
);
```

#### Fetching a single result

```java
// 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:

```java
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:

```taxi
query FindFlightDetails(flightNumber: FlightNumber) {
    find { Flight(FlightNumber == flightNumber) } as { ... }
}
```

```kotlin
// Kotlin
val flight = client.executeForResult(FindFlightDetailsQuery("QF-1")).getOrThrow()
```

```java
// 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:

```kotlin
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`:

```kotlin
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`:

```kotlin
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:

```kotlin
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()`:

```kotlin
transport.stream(FindFlightsQuery())
    .doOnError(QueryFailureException::class.java) { e ->
        println("Stream failed: ${e.failure.message}")
    }
    .subscribe()
```

---

> **Note: 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](#maven-plugin), rather than intended to be written by hand.

### TaxiTransport

The core interface implemented by all three transports:

```kotlin
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:

```kotlin
// 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:

```kotlin
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:

```kotlin
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.
