Kotlin Spring Boot Server
This guide shows how to implement an xRPC server using Kotlin and Spring Boot.
Planned: The
kotlin-springboot-servertarget is not available in the CLI yet. This page is a preview and the API may change.
Prerequisites
- Define your API contract (see API Contract) and export
router - Generate Kotlin Spring Boot code:
xrpc generate --targets kotlin-springboot-server(planned) - Implement your handlers using the generated code
Note: The xRPC CLI runs on Node.js (>= 18), but the generated Kotlin code runs on the JVM. The generated code is expected to use Spring Boot framework APIs.
Framework-Specific Target: xRPC generates code for framework-specific targets. The kotlin-springboot-server target is planned to generate code tailored specifically for Spring Boot, including Spring @RestController integration.
Client SDKs are generated by separate targets (planned).
Basic Server Setup
First, define your API contract (see API Contract for details):
// contract.ts
import { z } from 'zod';
import { createRouter, createEndpoint, query, mutation } from 'xrpckit';
export const router = createRouter({
greeting: createEndpoint({
greet: query({
input: z.object({ name: z.string() }),
output: z.object({ message: z.string() }),
}),
setGreeting: mutation({
input: z.object({ name: z.string(), greeting: z.string() }),
output: z.object({ message: z.string() }),
}),
}),
});
export type Api = typeof router;
Then, generate code and implement your server with handlers:
# Generate Kotlin Spring Boot code (planned target)
xrpc generate --targets kotlin-springboot-server
This is planned to generate code in <output>/xrpc/:
types.kt: Data classes for input/output typesXrpcController.kt: Spring Boot@RestControllerimplementationXrpcHandlers.kt: Interface that you implement with your business logic
// Handlers.kt - Implement the generated interface
package com.yourorg.handlers
import com.yourorg.generated.*
import org.springframework.stereotype.Service
@Service
class GreetingHandlers : XrpcHandlers {
override suspend fun greetingGreet(input: GreetInput): GreetOutput {
return GreetOutput(message = "Hello, ${input.name}!")
}
override suspend fun greetingSetGreeting(input: SetGreetingInput): SetGreetingOutput {
return SetGreetingOutput(message = "${input.greeting}, ${input.name}!")
}
}
// Application.kt - Spring Boot application
package com.yourorg
import com.yourorg.generated.XrpcController
import com.yourorg.handlers.GreetingHandlers
import org.springframework.boot.autoconfigure.SpringBootApplication
import org.springframework.boot.runApplication
import org.springframework.context.annotation.Bean
@SpringBootApplication
class Application {
@Bean
fun xrpcController(handlers: GreetingHandlers): XrpcController {
return XrpcController(handlers)
}
}
fun main(args: Array<String>) {
runApplication<Application>(*args)
}
Router, Endpoints, and Queries/Mutations
xRPC uses a hierarchical structure:
- Router: Primary grouping mechanism that exports all endpoints
- Endpoints: Named API namespaces (like
greeting,user,product) - Queries & Mutations: Individual RPC methods within an endpoint
Multiple Endpoints
When working with multiple endpoints in your router:
// contract.ts
export const router = createRouter({
user: createEndpoint({
getUser: query({ ... }),
updateUser: mutation({ ... }),
}),
product: createEndpoint({
listProducts: query({ ... }),
createProduct: mutation({ ... }),
}),
});
export type Api = typeof router;
Implement handlers for all endpoints:
// Handlers.kt
package com.yourorg.handlers
import com.yourorg.generated.*
import org.springframework.stereotype.Service
@Service
class AppHandlers : XrpcHandlers {
// User endpoint handlers
override suspend fun userGetUser(input: GetUserInput): GetUserOutput {
return GetUserOutput(
id = input.id,
name = "John Doe",
email = "john@example.com"
)
}
override suspend fun userUpdateUser(input: UpdateUserInput): UpdateUserOutput {
return UpdateUserOutput(
id = input.id,
name = input.name ?: "John Doe",
email = input.email ?: "john@example.com"
)
}
// Product endpoint handlers
override suspend fun productListProducts(input: ListProductsInput): ListProductsOutput {
return ListProductsOutput(
products = listOf(
Product(id = "1", name = "Product 1", price = 100.0),
Product(id = "2", name = "Product 2", price = 200.0)
)
)
}
override suspend fun productCreateProduct(input: CreateProductInput): CreateProductOutput {
return CreateProductOutput(
id = "new-id",
name = input.name,
price = input.price
)
}
}
Type Safety
xRPC ensures full type safety through generated types. The generated code provides an XrpcHandlers interface that matches your API contract exactly.
Generated Types
After running xrpc generate --targets kotlin-springboot-server, the generated code is planned to include:
// xrpc/types.kt
data class GreetInput(
val name: String
)
data class GreetOutput(
val message: String
)
data class SetGreetingInput(
val name: String,
val greeting: String
)
data class SetGreetingOutput(
val message: String
)
Generated Interface
The generated XrpcHandlers interface enforces type-safe handler implementations:
// xrpc/XrpcHandlers.kt
interface XrpcHandlers {
suspend fun greetingGreet(input: GreetInput): GreetOutput
suspend fun greetingSetGreeting(input: SetGreetingInput): SetGreetingOutput
}
Type-Safe Handlers
By implementing XrpcHandlers, Kotlin ensures:
- Structure matches contract: All endpoints and their queries/mutations must be implemented
- Input types are correct: Handler input types match Zod schemas
- Output types are correct: Handler return types match Zod schemas
- Autocomplete: Full IntelliSense support for handler methods
- Compile-time errors: Kotlin catches mismatches before runtime
Example: Type Errors
Kotlin will catch errors if your handlers don’t match the contract:
@Service
class MyHandlers : XrpcHandlers {
override suspend fun greetingGreet(input: GreetInput): GreetOutput {
// ❌ Error: Missing required property 'message'
// Kotlin knows the output must match GreetOutput
return GreetOutput() // Compile error
}
// ❌ Error: 'greetingSetGreeting' must be overridden
// Kotlin enforces all queries/mutations must be implemented
}
Benefits
- Compile-time safety: Catch errors before running code
- Refactoring support: Rename endpoints and methods with confidence
- Autocomplete: IDE suggests available endpoints and their methods
- Documentation: Types serve as inline documentation
Handler Function
The handler function is fully type-safe. Kotlin knows the exact input and output types from your API contract:
@Service
class GreetingHandlers : XrpcHandlers {
override suspend fun greetingGreet(input: GreetInput): GreetOutput {
// Kotlin knows input.name is String ✅
val name = input.name
// Return type is checked - must match GreetOutput: { message: String }
return GreetOutput(
message = "Hello, $name!" // ✅ Type-checked
)
}
}
Handler Signature
Each handler:
- Is a
suspendfunction (supports coroutines) - Receives typed input matching your Zod schema (e.g.,
GreetInput) - Returns typed output matching your Zod schema (e.g.,
GreetOutput)
Coroutines Support
All handlers are suspend functions, allowing you to use Kotlin coroutines:
@Service
class GreetingHandlers : XrpcHandlers {
override suspend fun greetingGreet(input: GreetInput): GreetOutput {
// Use coroutines for async operations
val result = withContext(Dispatchers.IO) {
// Database call, API call, etc.
fetchUserData(input.name)
}
return GreetOutput(message = "Hello, ${result.name}!")
}
}
Generated Controller
The generated XrpcController is a Spring Boot @RestController that:
- Handles HTTP POST requests to
/api(configurable) - Parses JSON request bodies
- Validates inputs using generated validators
- Routes to appropriate handler methods
- Planned: output validation before serialization
- Returns JSON responses
// xrpc/XrpcController.kt
@RestController
@RequestMapping("/api")
class XrpcController(
private val handlers: XrpcHandlers
) {
@PostMapping
suspend fun handle(@RequestBody request: JsonRpcRequest): ResponseEntity<JsonRpcResponse> {
// Request parsing, validation, routing, and response handling
// All implemented in generated code
}
}
Spring Boot Integration
The generated controller integrates seamlessly with Spring Boot:
- Dependency Injection: Handlers are injected via Spring’s DI container
- Spring Annotations: Uses standard Spring Boot annotations
- Error Handling: Integrates with Spring’s exception handling
- Middleware: Can use Spring interceptors and filters
- Configuration: Follows Spring Boot configuration patterns
Custom Request Mapping
You can customize the request path by configuring the controller:
@SpringBootApplication
class Application {
@Bean
fun xrpcController(handlers: GreetingHandlers): XrpcController {
return XrpcController(handlers, basePath = "/rpc") // Custom path
}
}
Adding Middleware
Use Spring interceptors for cross-cutting concerns:
@Component
class AuthInterceptor : HandlerInterceptor {
override fun preHandle(
request: HttpServletRequest,
response: HttpServletResponse,
handler: Any
): Boolean {
// Authentication logic
return true
}
}
@Configuration
class WebConfig : WebMvcConfigurer {
override fun addInterceptors(registry: InterceptorRegistry) {
registry.addInterceptor(AuthInterceptor())
.addPathPatterns("/api/**")
}
}
Generated Code Structure
The generated code in <output>/xrpc/ is planned to include:
Types (types.kt): Type-safe data classes matching your Zod schemas:
// Generated input types
data class GreetInput(
val name: String
)
data class SetGreetingInput(
val name: String,
val greeting: String
)
// Generated output types
data class GreetOutput(
val message: String
)
data class SetGreetingOutput(
val message: String
)
Handlers Interface (XrpcHandlers.kt): Interface that you implement:
interface XrpcHandlers {
suspend fun greetingGreet(input: GreetInput): GreetOutput
suspend fun greetingSetGreeting(input: SetGreetingInput): SetGreetingOutput
}
Controller (XrpcController.kt): Spring Boot @RestController:
XrpcController(handlers: XrpcHandlers): Constructor takes your handler implementation- Handles HTTP routing, validation, and response serialization
Validation: Runtime validators generated from Zod schemas validate inputs before handler execution. Output validation is planned.
Handler Naming Convention
Handler method names follow the pattern: {endpoint}{Method}
- Endpoint name is camelCase (e.g.,
greeting→greeting) - Method name is PascalCase (e.g.,
greet→Greet) - Combined:
greetingGreet,greetingSetGreeting
For nested endpoints or multiple words, the pattern remains consistent:
userendpoint,getUsermethod →userGetUserproductendpoint,listProductsmethod →productListProducts
Error Handling
The generated controller handles errors and returns appropriate HTTP responses:
- Validation Errors: Returns 400 Bad Request with error details
- Handler Errors: Returns 500 Internal Server Error
- Not Found: Returns 404 if endpoint doesn’t exist
You can customize error handling by extending the generated controller or using Spring’s exception handlers.