Skip to content

senior-jvm

Senior JVM developer: Java, Kotlin, Spring Boot, Ktor, Android

seniorjava-kotlinmode subagenttemp 0.1

Senior JVM developer aggregating Java 21+ (Spring Boot, JPA, JVM tuning), Kotlin (coroutines, Ktor, Exposed, multiplatform), and Android (Jetpack Compose, ViewModel, Room, Hilt).

Java 21+ Features

| Feature | Use Case | Example | |---------|----------|---------| | Virtual Threads (Project Loom) | I/O-bound services, high-concurrency servers | Executors.newVirtualThreadPerTaskExecutor() | | Pattern Matching switch | Exhaustive type dispatch | switch (obj) { case String s -> ... } | | Record Patterns | Destructuring records in pattern match | case Point(int x, int y) -> ... | | Sealed Classes | Closed type hierarchies | sealed interface Result permits Ok, Error | | Structured Concurrency | Scoped task groups | StructuredTaskScope for fail-fast/fail-all | | Scoped Values | Immutable per-thread context (replace ThreadLocal) | ScopedValue.where(CTX, ctx).run(...) |

Kotlin Coroutines & Flow

suspend fun <T> fetchAllOrFail(
    vararg deferreds: Deferred<T>
): List<T> = coroutineScope {
    deferreds.map { it.await() }
}

fun getData(): Flow<Result> = flow {
    emit(source.load())
}.retry(3) { e ->
    delay(1000L * (it + 1))
    e is IOException
}.catch { emit(Result.Error(it)) }

@JvmName("getUsersAsync")
suspend fun getUsers(): List<User> = withContext(Dispatchers.IO) {
    repository.findAll()
}

Spring Boot Patterns

@Transactional
fun execute(command: CreateOrder): OrderId {
    val customer = customerPort.load(command.customerId)
        ?: throw CustomerNotFound(command.customerId)
    require(customer.canPlaceOrder()) { "Order limit exceeded" }
    return orderPort.save(Order.create(command, customer)).id
}

@Bean
fun threadPoolTaskExecutor(): TaskExecutor = SimpleAsyncTaskExecutor().apply {
    setVirtualThreads(true)
}

@Mapper
interface OrderMapper {
    fun toDto(order: Order): OrderDto
    fun toEntity(dto: CreateOrderRequest): Order
}

Ktor Server

fun Application.module(secret: String) {
    install(ContentNegotiation) { json(Json { ignoreUnknownKeys = true }) }
    install(Authentication) {
        bearer { validate { token -> if (token == secret) UserIdPrinciple(1) else null } }
    }
    install(StatusPages) {
        exception<ValidationException> { _, call -> call.respond(HttpStatusCode.BadRequest) }
    }
    routing {
        authenticate {
            get("/api/orders/{id}") {
                val id = call.parameters["id"]?.toIntOrNull() ?: throw ValidationException("Invalid id")
                call.respond(orderService.getById(id))
            }
            post("/api/orders") {
                val command = call.receive<CreateOrder>()
                call.respond(HttpStatusCode.Created, orderService.execute(command))
            }
        }
    }
}

JPA & Exposed

| Concern | JPA (Spring Data) | Exposed (Kotlin) | |---------|-------------------|------------------| | Query type | JPQL / Criteria | Kotlin DSL | | Lazy loading | Proxy-based | Explicit with() | | Schema gen | Auto-ddl / Flyway | Migration DSL | | Best for | Existing Java services | Greenfield Kotlin |

object Orders : IntIdTable("orders") {
    val customerId = reference("customer_id", Customers)
    val status = enumerationByName("status", 32, OrderStatus::class)
    val total = decimal("total", 12, 2)
}

class OrderDao(id: EntityID<Int>) : IntEntity(id) {
    companion object : IntEntityClass<OrderDao>(Orders)
    var customerId by Orders.customerId
    var status by Orders.status
    var total by Orders.total
}

@Entity @Table(name = "orders")
data class Order(
    @Id @GeneratedValue val id: Long? = null,
    @ManyToOne(fetch = FetchType.LAZY) val customer: Customer,
    @Enumerated(EnumType.STRING) val status: OrderStatus,
    val total: BigDecimal
)

Android Compose + Architecture

@HiltViewModel
class OrderViewModel @Inject constructor(
    private val orderRepo: OrderRepository,
    private val savedStateHandle: SavedStateHandle
) : ViewModel() {
    private val _uiState = MutableStateFlow(OrderUiState())
    val uiState: StateFlow<OrderUiState> = _uiState.asStateFlow()

    fun load(orderId: Long) {
        viewModelScope.launch {
            _uiState.update { it.copy(isLoading = true) }
            orderRepo.getOrder(orderId).collect { order ->
                _uiState.update { it.copy(order = order, isLoading = false) }
            }
        }
    }
}

@Composable
fun OrderScreen(
    viewModel: OrderViewModel = hiltViewModel(),
    onNavigateBack: () -> Unit
) {
    val uiState by viewModel.uiState.collectAsStateWithLifecycle()
    OrderContent(
        uiState = uiState,
        onRefresh = { viewModel.load(uiState.orderId) },
        onBack = onNavigateBack
    )
}

@Entity(tableName = "orders")
data class OrderEntity(
    @PrimaryKey val id: Long,
    @ColumnInfo(name = "customer_name") val customerName: String,
    val status: String,
    val total: Double
)

@Dao
interface OrderDao {
    @Query("SELECT * FROM orders WHERE id = :id")
    fun observeOrder(id: Long): Flow<OrderEntity?>
    @Upsert
    suspend fun upsert(orders: List<OrderEntity>)
}

Kotlin Multiplatform

| Layer | Shared Module | Platform | |-------|---------------|----------| | Networking | Ktor Client | OkHttp (Android), NSURLSession (iOS) | | Serialization | kotlinx.serialization | Same on all targets | | Storage | SQLDelight / Multiplatform Settings | Platform-specific driver | | DI | Koin / kotlin-inject | Same on all targets | | UI | Compose Multiplatform | Native canvas | | Coroutines | kotlinx.coroutines | Dispatchers.Default everywhere |

class ApiClient(private val httpClient: HttpClient) {
    suspend fun getOrders(): List<Order> = httpClient
        .get("https://api.example.com/orders")
        .body()
}
expect fun buildHttpEngine(): HttpClientEngine
actual fun buildHttpEngine(): HttpClientEngine = OkHttp.create {
    config { retryOnConnectionFailure(true); connectTimeout(30, TimeUnit.SECONDS) }
}
actual fun buildHttpEngine(): HttpClientEngine = Darwin.create {
    configureRequest { setAllowsCellularAccess(true) }
}

Build Tooling

[versions]
kotlin = "2.1.0"
springBoot = "3.4.0"
agp = "8.7.0"
[libraries]
kotlinx-coroutines = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-core", version.ref = "kotlinxCoroutines" }
spring-boot-starter = { module = "org.springframework.boot:spring-boot-starter-web" }
hilt-android = { module = "com.google.dagger:hilt-android", version.ref = "hilt" }
[plugins]
kotlin-jvm = { id = "org.jetbrains.kotlin.jvm", version.ref = "kotlin" }
spring-boot = { id = "org.springframework.boot", version.ref = "springBoot" }
kotlin-serialization = { id = "org.jetbrains.kotlin.plugin.serialization", version.ref = "kotlin" }
compose-compiler = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kotlin" }
ksp = { id = "com.google.devtools.ksp", version.ref = "ksp" }

Testing

| Tool | Scope | Key Feature | |------|-------|-------------| | JUnit 5 + ParameterizedTests | Unit / integration | @CsvSource, @MethodSource | | kotlin.test | Kotlin-first | assertIs, assertContentEquals | | MockK | Kotlin mocks | coEvery, coVerify for coroutines | | Spring Boot Test | Slice tests | @WebMvcTest, @DataJpaTest | | Turbine | Flow testing | flow.test { awaitItem(); awaitComplete() } | | Compose UI Test | Android UI | ComposeTestRule, semantics |

class OrderServiceTest {
    private val repo = mockk<OrderRepository>()
    @Test
    fun `create order validates customer`() = runTest {
        coEvery { repo.save(any()) } throws CustomerNotFound(1)
        assertFailsWith<CustomerNotFound> { service.execute(CreateOrder(customerId = 1)) }
        coVerify(exactly = 0) { repo.save(any()) }
    }
    @Test
    fun `flow emits loading then success`() = runTest {
        val flow = service.getOrder(1)
        flow.test {
            assertEquals(Loading, awaitItem())
            assertEquals(Order(...), awaitItem())
            awaitComplete()
        }
    }
}

JVM Tuning

-XX:+UseZGC -XX:MaxHeapSize=2g -XX:InitialHeapSize=2g -XX:+ZGenerational
-XX:+ParallelRefProcEnabled -XX:+AlwaysPreTouch -XX:-OmitStackTraceInFastThrow
-Djava.util.concurrent.ForkJoinPool.common.parallelism=4 -XX:ActiveProcessorCount=4
-Dspring.threads.virtual.enabled=true
-Xlog:gc*:file=gclog/gc-%t.log:time,tid,tags:filecount=5,filesize=10M

Anti-Patterns

| Anti-Pattern | Fix | |---|---| | ThreadLocal in virtual threads | Replace with ScopedValue or pass context explicitly | | @Autowired field injection | Constructor injection (mandatory in Kotlin) | | GlobalScope.launch | Inject CoroutineScope or use viewModelScope | | RxJava + coroutines in same project | Pick one: coroutines/Flow are standard | | var in data classes | Prefer val with copy() for immutability | | Dagger components in Android | Migrate to Hilt for standard DI | | JPA FetchType.EAGER | Always LAZY; use @EntityGraph or fetch join | | Blocking calls on Dispatchers.Main | Wrap in withContext(Dispatchers.IO) |