# JVM: Kotlin, Scala, Java

Modern patterns, tooling, and best practices for the JVM ecosystem.

---

## Language Comparison

| Aspect | Kotlin | Scala 3 | Java 21+ |
|---|---|---|---|
| **Paradigm** | Multi-paradigm (OO + FP) | Functional-first | OO + FP (records, patterns) |
| **Null Safety** | Built-in (`?`, `!!`) | Option / `Null` | Annotations (`@Nullable`) |
| **Data Classes** | `data class` | `case class` | `record` |
| **Pattern Matching** | `when` + sealed classes | `match` + ADTs | `switch` + patterns (preview) |
| **Coroutines** | First-class (`suspend`) | ZIO / Cats Effect / Ox | Virtual threads (Loom) |
| **Type Inference** | Local | Global (improved in 3) | Local (`var`) |
| **Compilation** | JVM / JS / Native / WASM | JVM / JS / Native | JVM |
| **Learning Curve** | Low (Java-like) | Medium-High | Low (familiar) |

---

## Kotlin (Recommended for Most Teams)

### Idiomatic Patterns
```kotlin
// Null safety
fun findUser(id: UserId): User? = repository.findById(id)
val name = user?.name ?: "Unknown"

// Sealed classes for state machines
sealed interface OrderState {
    data class Pending(val createdAt: Instant) : OrderState
    data class Confirmed(val confirmedAt: Instant) : OrderState
    data class Shipped(val tracking: String) : OrderState
    data class Delivered(val deliveredAt: Instant) : OrderState
    data class Cancelled(val reason: String) : OrderState
}

// Extension functions
fun User.fullName() = "$firstName $lastName"

// Scope functions
val user = userRepository.findById(id)
    ?.also { log.info("Found user: ${it.id}") }
    ?: throw NotFoundException(id)

// Result type (Arrow or custom)
sealed class Result<out T, out E> {
    data class Success<out T>(val value: T) : Result<T, Nothing>()
    data class Failure<out E>(val error: E) : Result<Nothing, E>()
}

fun createUser(cmd: CreateUserCmd): Result<User, DomainError> =
    Email(cmd.email).map { User(cmd.name, it) }
        .flatMap { repo.save(it).map { it } }
```

### Coroutines (Structured Concurrency)
```kotlin
// suspend functions = async without callback hell
suspend fun getDashboard(userId: UserId): Dashboard = coroutineScope {
    val (profile, orders, recs) = coroutineScope {
        async { profileService.get(userId) }
        async { orderService.recent(userId) }
        async { recService.forUser(userId) }
    }.awaitAll()
    Dashboard(profile, orders, recs)
}

// Supervision + timeout
withTimeout(5_000) {
    supervisorScope {
        children.forEach { child ->
            launch { child.process() }
        }
    }
}

// Flow (cold async streams)
fun userUpdates(userId: UserId): Flow<User> = callbackFlow {
    val listener = UserListener { offer(it) }
    repo.addListener(userId, listener)
    awaitClose { repo.removeListener(userId, listener) }
}
```

### Spring Boot 3 + Kotlin
```kotlin
// Controller
@RestController
@RequestMapping("/api/v1/users")
class UserController(private val service: UserService) {

    @GetMapping("/{id}")
    fun getUser(@PathVariable id: UserId): ResponseEntity<User> =
        service.getUser(id).fold(
            { ResponseEntity.notFound().build() },
            { ResponseEntity.ok(it) }
        )

    @PostMapping
    fun createUser(@Valid @RequestBody cmd: CreateUserCmd): ResponseEntity<User> =
        ResponseEntity.status(HttpStatus.CREATED).body(service.createUser(cmd))
}

// Configuration
@Configuration
@ConfigurationProperties("app")
data class AppConfig(
    val database: DatabaseConfig,
    val redis: RedisConfig,
    val kafka: KafkaConfig
)

// Virtual threads (Java 21+)
@Bean
fun taskExecutor(): ExecutorService = Executors.newVirtualThreadPerTaskExecutor()
```

---

## Scala 3 (For FP-Heavy Teams)

### Effect Systems
```scala
// ZIO 2 (recommended for production)
import zio.*

trait UserRepository {
  def findById(id: UserId): IO[RepoError, Option[User]]
  def save(user: User): IO[RepoError, Unit]
}

case class UserService(repo: UserRepository) {
  def createUser(cmd: CreateUserCmd): ZIO[Any, DomainError, User] =
    for {
      email <- Email.make(cmd.email).mapError(DomainError.InvalidEmail)
      user  = User(cmd.name, email)
      _     <- repo.save(user).mapError(DomainError.RepoError)
    } yield user
}

// Structured concurrency
val dashboard = for {
  fiber1 <- profileService.get(userId).fork
  fiber2 <- orderService.recent(userId).fork
  fiber3 <- recService.forUser(userId).fork
  profile <- fiber1.join
  orders  <- fiber2.join
  recs    <- fiber3.join
} yield Dashboard(profile, orders, recs)
```

### Type-Level Programming
```scala
// Phantom types for compile-time state tracking
trait State
trait Draft extends State
trait Published extends State

case class Article[S <: State](title: String, content: String)

def publish(article: Article[Draft]): Article[Published] =
  article.asInstanceOf[Article[Published]] // safe: only called after validation

// GADTs for type-safe DSL
enum Expr[A] {
  case Lit(value: Int) extends Expr[Int]
  case Add(l: Expr[Int], r: Expr[Int]) extends Expr[Int]
  case If[B](cond: Expr[Boolean], t: Expr[B], f: Expr[B]) extends Expr[B]
}
```

### HTTP (http4s + Cats Effect)
```scala
object UserRoutes {
  def apply[F[_]: Async](service: UserService[F]): HttpRoutes[F] = HttpRoutes.of {
    case GET -> Root / "users" / UUIDVar(id) =>
      service.getUser(UserId(id)).flatMap {
        case Some(user) => Ok(user.asJson)
        case None => NotFound()
      }
    case req @ POST -> Root / "users" =>
      req.asJsonDecode[CreateUserCmd].flatMap(cmd =>
        service.createUser(cmd).flatMap(Created(_))
      )
  }
}
```

---

## Java 21+ (Modern Java)

### Records + Pattern Matching
```java
// Immutable data carriers
public record UserId(UUID value) {}
public record User(UserId id, String email, String name, Instant createdAt) {}

// Sealed interfaces for exhaustive matching
public sealed interface OrderState permits Pending, Confirmed, Shipped {
    record Pending(Instant createdAt) implements OrderState {}
    record Confirmed(Instant confirmedAt) implements OrderState {}
    record Shipped(String tracking) implements OrderState {}
}

// Pattern matching (preview)
String describe(OrderState state) = switch (state) {
    case Pending(var created) -> "Pending since " + created;
    case Confirmed(var confirmed) -> "Confirmed at " + confirmed;
    case Shipped(var tracking) -> "Shipped: " + tracking;
};

// Virtual threads (Project Loom)
try (var executor = Executors.newVirtualThreadPerTaskExecutor()) {
    List<Future<User>> futures = userIds.stream()
        .map(id -> executor.submit(() -> userService.getUser(id)))
        .toList();
    // ...
}
```

### Spring Boot 3 + Virtual Threads
```java
@SpringBootApplication
public class Application {
    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }

    @Bean
    public ExecutorService virtualThreadExecutor() {
        return Executors.newVirtualThreadPerTaskExecutor();
    }
}

// Controller uses virtual threads automatically for blocking calls
@RestController
@RequiredArgsConstructor
public class UserController {
    private final UserService service;

    @GetMapping("/users/{id}")
    public ResponseEntity<User> getUser(@PathVariable UserId id) {
        return service.getUser(id)
            .map(ResponseEntity::ok)
            .orElse(ResponseEntity.notFound().build());
    }
}
```

---

## Build Tools

### Gradle (Kotlin DSL) — Recommended
```kotlin
// build.gradle.kts
plugins {
    id("org.springframework.boot") version "3.2.0"
    id("io.spring.dependency-management") version "1.1.4"
    kotlin("jvm") version "1.9.0"
    kotlin("plugin.spring") version "1.9.0"
    kotlin("plugin.serialization") version "1.9.0"
}

dependencies {
    implementation(platform("org.springframework.boot:spring-boot-dependencies:3.2.0"))
    implementation("org.springframework.boot:spring-boot-starter-webflux")
    implementation("org.springframework.boot:spring-boot-starter-data-r2dbc")
    implementation("org.jetbrains.kotlin:kotlin-reflect")
    implementation("org.jetbrains.kotlinx:kotlinx-coroutines-reactor")
    implementation("io.arrow-kt:arrow-core:1.2.0")
    
    testImplementation("org.springframework.boot:spring-boot-starter-test")
    testImplementation("io.kotest:kotest-runner-junit5:5.8.0")
    testImplementation("io.kotest:kotest-assertions-core:5.8.0")
    testImplementation("io.mockk:mockk:1.13.13")
}

tasks.withType<Test> {
    useJUnitPlatform()
    testLogging { events("passed", "skipped", "failed") }
}
```

### Maven (if preferred)
```xml
<!-- pom.xml -->
<properties>
    <java.version>21</java.version>
    <kotlin.version>1.9.0</kotlin.version>
</properties>
<dependencyManagement>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-dependencies</artifactId>
            <version>3.2.0</version>
            <type>pom</type>
            <scope>import</scope>
        </dependency>
    </dependencies>
</dependencyManagement>
```

---

## Database Access

### Spring Data (R2DBC for Reactive)
```kotlin
// Kotlin + R2DBC
interface UserRepository : ReactiveCrudRepository<User, UserId> {
    fun findByEmail(email: String): Mono<User>
}

// Service
@Service
class UserService(private val repo: UserRepository) {
    fun getUser(id: UserId): Mono<User> = repo.findById(id)
    
    fun createUser(cmd: CreateUserCmd): Mono<User> =
        Email(cmd.email).fold(
            { Mono.error(DomainError.InvalidEmail(it)) },
            { email -> repo.save(User(UserId.random(), email, cmd.name, Instant.now())) }
        )
}
```

### jOOQ (Type-Safe SQL) — Alternative
```kotlin
// Generated code from DSLContext
dsl.insertInto(USERS)
   .set(USERS.ID, UUID.randomUUID())
   .set(USERS.EMAIL, email)
   .set(USERS.NAME, name)
   .execute()

val user = dsl.selectFrom(USERS)
    .where(USERS.ID.eq(id))
    .fetchOptionalInto(User::class.java)
```

---

## Testing

### Kotest (Kotlin)
```kotlin
class UserServiceTest : BehaviorSpec() {
    init {
        Given("a user repository") {
            val repo = mockk<UserRepository>()
            val service = UserService(repo)
            
            When("creating a user with valid email") {
                val cmd = CreateUserCmd("John", "john@example.com")
                every { repo.save(any()) } returns Result.Success(Unit)
                
                Then("user is created") {
                    val result = service.createUser(cmd)
                    result.shouldBeSuccess()
                }
            }
        }
    }
}
```

### ScalaTest + ZIO Test
```scala
object UserServiceSpec extends ZIOSpecDefault {
  def spec = suite("UserService")(
    test("create user succeeds with valid email") {
      for {
        repo <- ZIO.service[UserRepository]
        service = UserService(repo)
        result <- service.createUser(CreateUserCmd("John", "john@example.com"))
          .exit
      } yield assert(result)(succeeds(anything))
    }
  ).provide(InMemoryUserRepository.layer)
}
```

### JUnit 5 + Mockito (Java)
```java
@ExtendWith(MockitoExtension.class)
class UserServiceTest {
    @Mock UserRepository repo;
    @InjectMocks UserService service;

    @Test
    void createUser_savesUser() {
        when(repo.save(any())).thenReturn(Mono.empty());
        
        StepVerifier.create(service.createUser(new CreateUserCmd("John", "john@example.com")))
            .expectNextMatches(u -> u.email().equals("john@example.com"))
            .verifyComplete();
    }
}
```

---

## Observability

### Micrometer + OpenTelemetry (All Languages)
```kotlin
// Spring Boot 3 auto-configures Micrometer + OTel
@Configuration
class ObservabilityConfig {
    @Bean
    fun meterFilter(): MeterFilter =
        MeterFilter.deny(id -> id.getName().startsWith("jvm."))
}
```

```yaml
# application.yml
management:
  endpoints:
    web:
      exposure:
        include: health,metrics,prometheus
  metrics:
    tags:
      application: ${spring.application.name}
  otel:
    metrics:
      export:
        interval: 10s
    traces:
      exporter: otlp
```

---

## Tooling Stack (2024+)

| Category | Kotlin | Scala | Java |
|---|---|---|---|
| **Build** | Gradle (Kotlin DSL) | Mill / sbt | Gradle / Maven |
| **Linting** | **detekt** + ktlint | **scalafix** + scalafmt | **Checkstyle** / SpotBugs |
| **Formatting** | ktlint | scalafmt | google-java-format |
| **Testing** | Kotest / JUnit 5 | munit / ZIO Test / ScalaTest | JUnit 5 + Mockito |
| **Property Testing** | kotest-property | ScalaCheck | jqwik |
| **DI** | Spring / Koin / Kodein | ZIO Layer / MacWire | Spring / Dagger |
| **Serialization** | kotlinx.serialization | circe / uPickle | Jackson |
| **HTTP Client** |ktor.client / Spring WebClient | sttp / http4s client | Spring WebClient |
| **Observability** | Micrometer + OTel | OpenTelemetry Java | Micrometer + OTel |
| **Vulnerability Scan** | OWASP Dependency Check | OWASP Dependency Check | OWASP Dependency Check |

---

## When to Choose What

| Scenario | Recommendation |
|---|---|
| **Team knows Java, wants modern** | Kotlin + Spring Boot 3 |
| **FP-first, complex domain modeling** | Scala 3 + ZIO / Cats Effect |
| **Enterprise, long-term support, conservative** | Java 21 LTS + Spring Boot 3 |
| **High throughput, low latency** | Java 21 Virtual Threads / Kotlin Coroutines |
| **Data engineering, Spark** | Scala 3 |
| **Android + Backend shared code** | Kotlin Multiplatform |
| **Team split on FP vs OO** | Kotlin (bridges both) |

---

## Related Notes

- [[Languages/Language-Agnostic-Principles]] — Universal patterns
- [[Languages/TypeScript-JavaScript]] — JS/TS patterns
- [[Languages/Go]] — Go patterns
- [[Languages/Rust]] — Rust patterns
- [[Architecture/Architectural-Styles]] — Hexagonal on JVM
- [[Patterns/Design-Patterns]] — Patterns in JVM languages
- [[DevOps/CI-CD-Best-Practices]] — Pipeline for JVM
- [[Testing/Test-Pyramid]] — Testing strategy

---

*Sources: Kotlin Docs, Scala 3 Docs, Java 21 Docs, Spring Boot 3 Reference, ZIO Docs, Cats Effect Docs, Arrow Docs, Kotest, ScalaTest, JUnit 5, Micrometer, OpenTelemetry Java, Gradle, Mill, sbt, detekt, ktlint, scalafix, scalafmt, Error Prone*