kmp-kotlin-rpc
>
Works with
---
name: kmp-kotlin-rpc
description: >
license: Apache-2.0
---
## Pre-implementation check (run before writing any `:data` layer code)
```bash
grep -r "RemoteService\|@Rpc\|withRpc\|KtorRPCClient\|rpcClient\|\.rpc(" \
<project_root>/*/src --include="*.kt" -l
```
**If files match** → kRPC is already wired in this project. Before adding any `safeRequest`
or `HttpClient` call to the Kotlin backend:
1. Identify which service interface owns the operation (check `shared/rpc/` or equivalent)
2. If the operation is already exposed on an RPC service → call it through the existing RPC
client; **do not add a parallel HTTP call**
3. If the operation is not yet on any service interface → extend the existing service
interface with a new method; do not create a second transport
**If nothing matches** → kRPC is not in use. Decide: is this a Kotlin-to-Kotlin boundary?
If yes and both sides are controlled, consider kRPC before defaulting to REST. If the backend
is third-party or non-Kotlin, use `kmp-network-layer`.
---
## When to Use This Skill
Use this skill when you need to:
- Design a Kotlin-first RPC boundary for a KMP app
- Decide whether RPC fits better than REST or gRPC
- Split shared service contracts from server implementation
- Build an authenticated RPC flow in a Ktor-backed app
- Scaffold the initial client/server RPC module layout
**Recommended default:** use Kotlin RPC only when both sides are Kotlin-first and the
procedure style is a better fit than resource-oriented REST.
**Trigger keywords:** kotlin rpc, kRPC, kotlinx rpc, Ktor RPC, RPC service, typed
contract, service stub, client/server contract, shared RPC models, Kotlin-first API.
**Freshness rule:** the `kotlinx-rpc` library is pre-stable — recheck the
[kotlinx.rpc changelog](https://github.com/Kotlin/kotlinx-rpc) before starting any
implementation. Pay particular attention to: the `@Rpc` annotation API, the Ktor plugin
version alignment, and whether serialization format has changed.
## Recommendation First
Default to this approach:
1. **Use Kotlin RPC for Kotlin-to-Kotlin boundaries.**
2. **Keep contracts small and explicit.**
3. **Keep auth outside the RPC transport** and guard the server route first.
4. **Keep public REST APIs separate** when non-Kotlin clients need stable HTTP semantics.
Why:
- Kotlin RPC is a good fit when the codebase already shares Kotlin models and logic
- REST is still the clearer choice for public, mixed-client APIs
- auth, persistence, and transport should remain separate concerns
## Project Structure
Keep the shared contract and the server implementation split cleanly:
```text
shared/
rpc/
GreetingService.kt
model/GreetingRequest.kt
model/GreetingResponse.kt
server/
rpc/
GreetingRpcModule.kt
auth/
...
client/
rpc/
GreetingRpcClient.kt
```
Rules:
- service interfaces and request/response types live in shared code
- server modules install and expose the RPC implementation
- client modules own the transport setup and generated stubs
- auth stays at the Ktor route boundary, not inside domain logic
## Core Pattern
Model the RPC boundary as a service interface plus shared DTOs:
```kotlin
interface GreetingService {
suspend fun greet(request: GreetingRequest): GreetingResponse
}
data class GreetingRequest(val name: String)
data class GreetingResponse(val message: String)
```
Then wire the implementation behind a Ktor server boundary:
```kotlin
// Pseudocode sketch - adapt to the current official Ktor RPC API
fun Application.rpcModule() {
routing {
// authenticate("auth-bearer") { rpc(...) }
// expose GreetingServiceImpl behind the RPC transport
}
}
```
Keep the client side thin:
```kotlin
class GreetingRpcClient(
// transport + generated stub setup lives here
) {
suspend fun greet(name: String): String
}
```
## Streaming with Flow
kRPC supports server-push streaming natively — a service method can return a `Flow`
instead of a single value, and kotlinx-rpc handles the framing over its Ktor transport:
```kotlin
interface CounterService {
fun countUpdates(): Flow<Int> // streaming method
}
```
Real constraints, verified against kotlinx-rpc's actual support surface (not every
Flow-shaped signature works):
- **`Flow` only** — `StateFlow` and `SharedFlow` are explicitly not supported and there
are no plans to add them. Convert with `.stateIn`/`.shareIn` on the client after
collecting, not on the service interface itself.
- **The streaming method must be non-suspending**, and the `Flow` must be the
**top-level return type** — `suspend fun countUpdates(): Flow<Int>` and
`suspend fun getPage(): List<Flow<Int>>` are both invalid shapes.
- Runs over the same Ktor transport as request/response RPC calls — no separate
connection or protocol to manage.
- As of 2026, kotlinx-rpc also supports native gRPC/Protobuf as an alternative
protocol (schema-first `.proto` files, Gradle plugin generates suspend functions +
`Flow`-based streaming + type-safe builders) — use this only when the contract must
interop with non-Kotlin gRPC clients; for Kotlin-only streaming, plain kRPC `Flow`
methods above are simpler.
If the streaming target is one-way push to a client that may not be Kotlin, use SSE
instead — see `kmp-network-layer`'s SSE section and its decision table.
## When Not to Use It
Do not default to Kotlin RPC when:
- the API is public and must support many non-Kotlin consumers
- the domain is already resource-oriented and REST is simpler
- the transport needs to be easy to inspect with standard HTTP tooling
- you need a stable cross-language contract with minimal Kotlin coupling
## Docs to Recheck First
Before changing this skill, re-read the current official docs:
- [First steps with Kotlin RPC](https://ktor.io/docs/tutorial-first-steps-with-kotlin-rpc.html)
- [Build a full-stack application with Kotlin Multiplatform](https://ktor.io/docs/full-stack-development-with-kotlin-multiplatform.html)
- [Authentication and authorization in Ktor Server](https://ktor.io/docs/server-auth.html)
- [Type-safe routing](https://ktor.io/docs/type-safe-routing.html)
## Scaffold Script
- `scripts/scaffold_kotlin_rpc.py` - creates a starter shared/server/client RPC layout.
---
## Related Skills
- `kmp-ktor-auth-service` — auth guards for the RPC route live here
- `kmp-mongodb-database` — RPC service implementations often delegate to a MongoDB repository
- `kmp-feature-scaffold` — the shared contract module is a peer of the server and client modules
- `kmp-network-layer` — use this instead of RPC when the client is non-Kotlin or the API is public; also owns SSE for one-way server push
---
## Testing
```kotlin
// Always test against a Fake service — never mock the generated RPC stubs
class FakeCounterService : CounterService {
var count = 0
override suspend fun getCount(): Int = count
override suspend fun increment(): Int = ++count
override fun countUpdates(): Flow<Int> = flowOf(count)
}
@Test fun `increment increments counter`() = runTest {
val service = FakeCounterService()
assertEquals(0, service.getCount())
service.increment()
assertEquals(1, service.getCount())
}
@Test fun `countUpdates emits current count`() = runTest {
val service = FakeCounterService().apply { count = 5 }
service.countUpdates().test {
assertEquals(5, awaitItem())
cancelAndIgnoreRemainingEvents()
}
}
// Integration test — real in-process server (jvmTest only, not commonTest)
@Test fun `rpc round-trip with in-process server`() = runTest {
val server = embeddedServer(Netty, port = 0) {
install(RPC)
routing { rpc("/counter") { registerService<CounterService> { CounterServiceImpl() } } }
}.start(wait = false)
val port = (server.engine as NettyApplicationEngine).resolvedConnectors().first().port
val client = HttpClient(CIO) { install(RPC) }
val service = client.rpc("ws://localhost:$port/counter").withService<CounterService>()
assertEquals(1, service.increment())
client.close(); server.stop(0, 0)
}
```
---
## Common Anti-Patterns
- **Adding `safeRequest` for an endpoint already on an RPC service** — if `UserService.getUser(id)`
exists as an RPC method, calling `client.safeRequest { get("/users/$id") }` in parallel creates
two code paths that can diverge; extend the service method if behaviour needs to change
- **Creating a second `HttpClient` for the Kotlin backend when kRPC already handles it** — two
transports to the same server doubles token refresh logic, error handling, and serialisation
config; route through the existing RPC client
- **Not checking for kRPC before writing `:data` layer code** — run the pre-implementation grep
above before every new repository implementation; never assume the project is HTTP-only
- using Kotlin RPC for a public API consumed by non-Kotlin clients — REST is clearer
- putting auth logic inside the RPC service interface — auth belongs at the Ktor route boundary
- keeping the RPC contract in the server module — it must live in shared code so the client can use it
- using RPC for simple CRUD resources that REST already handles well — adds complexity without benefit
- skipping the `authenticated {}` Ktor block before the RPC route — exposes the service unauthenticated
If the contract needs to be consumed by a browser frontend or a non-Kotlin mobile client, use REST.
---
## Output Style
When asked about Kotlin RPC, respond in this order:
1. recommendation (use RPC only when both sides are Kotlin-first)
2. project structure (shared contract, server module, client module)
3. code snippet (service interface + server wiring sketch)
4. why RPC fits (or doesn't fit) the stated use case
5. main alternative (REST, gRPC)
Lead with the fit/no-fit decision. Keep the code snippet to the interface and one route binding.
---
## Changelog
| Date | Change |
|---|---|
| 2026-07-31 | Added a "Streaming with Flow" section documenting kRPC's real streaming constraints (Flow only — not StateFlow/SharedFlow; non-suspending top-level `Flow` return type required) and the 2026 native gRPC/Protobuf integration. Cross-referenced `kmp-network-layer`'s new SSE decision table. Real gap — the only prior streaming example was incidental, with no deliberate guidance. |
| 2026-06-21 | Initial release. |More Mobile skills
animation-vocabulary
emilkowalski/skills
Reverse-lookup glossary that turns a vague description of a web animation or motion effect into its exact term ("the bouncy thing when a popover opens" → Pop in; "the iOS rubber-band scroll" → Rubber-banding). Use when the user asks "what's it called when…", or describes a motion effect without knowing its name and wants the right word to prompt an AI or designer with. For naming an effect, not designing or building one.
xcode-project-setup
firebase/agent-skills
Safely modifies Xcode projects (.pbxproj) to add Swift Packages and link files. Use this skill whenever an iOS project needs dependencies installed (e.g. Firebase, Alamofire).
cross-border-ecommerce
nexscope-ai/ecommerce-skills
Cross-border e-commerce expansion advisor. Scores target markets on 8 weighted dimensions (market size, ecommerce penetration, competition, regulatory complexity, logistics infrastructure, payment ecosystem, cultural distance, IP protection), compares 5 fulfillment models with cost and transit data, provides country-by-country tax/duty compliance guides (EU VAT/IOSS, UK VAT, US sales tax, CA GST, AU GST, JP consumption tax), maps local payment preferences by market, and builds a phased expansion roadmap. No API key required.

