Add Connect-Ktor beside an existing Ktor server and call a generated handler.
The goal is a unary Connect RPC on a Ktor server that can still host REST routes. Prerequisites match a typical Connect-Kotlin workspace: JDK 21-friendly tooling, Gradle, and the Buf CLI.
This guide is server-side only. If you need a Connect client first, follow the official Connect-Kotlin getting started guide, then come back here to expose the matching Ktor handlers. For protocol details (codecs, errors, Connect GET), see the Connect protocol specification.
dependencies {
implementation("io.github.ichizero:connect-ktor:0.4.1")
}Pin to the current release on Maven Central.
On macOS or Linux:
brew install ichizero/tap/protoc-gen-connect-ktorOr download a binary from GitHub Releases and put it on PATH.
Point Buf at the local plugin alongside the usual Java/Kotlin and Connect-Kotlin plugins:
version: v2
clean: true
managed:
enabled: true
plugins:
- remote: buf.build/protocolbuffers/java
out: path/to/code
- remote: buf.build/protocolbuffers/kotlin
out: path/to/code
- remote: buf.build/connectrpc/kotlin
out: path/to/code
- local: protoc-gen-connect-ktor
out: path/to/codebuf generateFor a service like Eliza's Say RPC, the plugin emits a handler interface and a Route extension that wires handle / handleClientStream / handleServerStream.
object ElizaServiceHandler : ElizaServiceHandlerInterface {
override suspend fun say(
request: SayRequest,
call: ApplicationCall,
): ResponseMessage<SayResponse> = ResponseMessage.Success(
sayResponse { sentence = request.sentence },
emptyMap(),
emptyMap(),
)
}Unary methods take a single request.
Client-streaming methods take a cold Flow<Req> and still return one ResponseMessage.
Server-streaming methods take a single request and return a cold Flow<Res>.
fun main() {
embeddedServer(CIO, port = 8080) {
install(Resources)
routing {
get("/health") {
call.respondText("ok")
}
install(ContentNegotiation) {
connectJson()
}
elizaService(ElizaServiceHandler)
}
}.start(wait = true)
}/health stays ordinary Ktor.
ElizaService/Say becomes a Connect procedure on the same application.
That coexistence is the migration model: move RPCs when ready, leave the rest alone.
Any Connect client that targets your host works. On the JVM, use the official Connect-Kotlin client; from React or other TypeScript apps, use Connect-ES. Buf Studio is fine for ad-hoc checks.
Point the client at http://localhost:8080 (or your deployed base URL) and invoke Say as usual.
Client configuration details live in those official guides, not here.
For a per-call deadline, send Connect-Timeout-Ms with a positive number of milliseconds.
For example:
curl -i http://localhost:8080/connectrpc.eliza.v1.ElizaService/Say \
-H 'Content-Type: application/json' \
-H 'Connect-Timeout-Ms: 1000' \
-d '{"sentence":"Hello"}'Generated unary POST/GET and streaming handlers return deadline_exceeded when their own budget expires.
The budget starts when the handler begins; Ktor performs request decoding before it invokes unary handlers.
Cancellation by the caller still cancels server work rather than becoming a deadline response.
Unary handlers can return ResponseMessage.Failure or throw ConnectException to send a Connect error.
Other handler exceptions reach the application's StatusPages handlers, if installed.
The application decides how to respond to those exceptions; without a handler, Ktor's default error handling applies.
protoc-gen-connect-ktor details