diff --git a/skills/kotlin-tooling-java-to-kotlin/SKILL.md b/skills/kotlin-tooling-java-to-kotlin/SKILL.md
new file mode 100644
index 0000000..e856280
--- /dev/null
+++ b/skills/kotlin-tooling-java-to-kotlin/SKILL.md
@@ -0,0 +1,138 @@
+---
+name: kotlin-tooling-java-to-kotlin
+description: >
+ Use when converting Java source files to idiomatic Kotlin, when user mentions
+ "java to kotlin", "j2k", "convert java", "migrate java to kotlin", or when
+ working with .java files that need to become .kt files. Handles framework-aware
+ conversion for Spring, Lombok, Hibernate, Jackson, Micronaut, Quarkus, Dagger/Hilt,
+ RxJava, JUnit, Guice, Retrofit, and Mockito.
+license: Apache-2.0
+metadata:
+ author: JetBrains
+ version: "1.0.0"
+---
+
+# Java to Kotlin Conversion
+
+Convert Java source files to idiomatic Kotlin using a disciplined 4-step conversion
+methodology with 5 invariants checked at each step. Supports framework-aware conversion
+that handles annotation site targets, library idioms, and API preservation.
+
+## Workflow
+
+```dot
+digraph j2k_workflow {
+ rankdir=TB;
+ "User specifies files" -> "Step 0: Scan & Detect";
+ "Step 0: Scan & Detect" -> "Load framework guides";
+ "Load framework guides" -> "Step 1: Convert";
+ "Step 1: Convert" -> "Step 2: Write .kt";
+ "Step 2: Write .kt" -> "Step 3: Git rename";
+ "Step 3: Git rename" -> "Step 4: Verify";
+ "Step 4: Verify" -> "Next file?" [label="pass"];
+ "Step 4: Verify" -> "Fix issues" [label="fail"];
+ "Fix issues" -> "Step 1: Convert";
+ "Next file?" -> "Step 0: Scan & Detect" [label="batch: yes"];
+ "Next file?" -> "Done" [label="no more files"];
+}
+```
+
+## Step 0: Scan & Detect Frameworks
+
+Before converting, scan the Java file's import statements to detect which frameworks
+are in use. Load ONLY the matching framework reference files to keep context focused.
+
+### Framework Detection Table
+
+| Import prefix | Framework guide |
+|---|---|
+| `org.springframework.*` | [SPRING.md](references/frameworks/SPRING.md) |
+| `lombok.*` | [LOMBOK.md](references/frameworks/LOMBOK.md) |
+| `javax.persistence.*`, `jakarta.persistence.*`, `org.hibernate.*` | [HIBERNATE.md](references/frameworks/HIBERNATE.md) |
+| `com.fasterxml.jackson.*` | [JACKSON.md](references/frameworks/JACKSON.md) |
+| `io.micronaut.*` | [MICRONAUT.md](references/frameworks/MICRONAUT.md) |
+| `io.quarkus.*`, `javax.enterprise.*`, `jakarta.enterprise.*` | [QUARKUS.md](references/frameworks/QUARKUS.md) |
+| `dagger.*`, `dagger.hilt.*` | [DAGGER-HILT.md](references/frameworks/DAGGER-HILT.md) |
+| `io.reactivex.*`, `rx.*` | [RXJAVA.md](references/frameworks/RXJAVA.md) |
+| `org.junit.*`, `org.testng.*` | [JUNIT.md](references/frameworks/JUNIT.md) |
+| `com.google.inject.*` | [GUICE.md](references/frameworks/GUICE.md) |
+| `retrofit2.*`, `okhttp3.*` | [RETROFIT.md](references/frameworks/RETROFIT.md) |
+| `org.mockito.*` | [MOCKITO.md](references/frameworks/MOCKITO.md) |
+
+If `javax.inject.*` is detected, check for Dagger/Hilt vs Guice by looking for other
+imports from those frameworks. If ambiguous, load both guides.
+
+## Step 1: Convert
+
+Apply the conversion methodology from [CONVERSION-METHODOLOGY.md](references/CONVERSION-METHODOLOGY.md).
+
+This is a 4-step chain-of-thought process:
+1. **Faithful 1:1 translation** — exact semantics preserved
+2. **Nullability & mutability audit** — val/var, nullable types
+3. **Collection type conversion** — Java mutable → Kotlin types
+4. **Idiomatic transformations** — properties, string templates, lambdas
+
+Five invariants are checked after each step. If any invariant is violated, revert
+to the previous step and redo.
+
+Apply any loaded framework-specific guidance during step 4 (idiomatic transformations).
+
+## Step 2: Write Output
+
+Write the converted Kotlin code to a `.kt` file with the same name as the original
+Java file, in the same directory.
+
+## Step 3: Preserve Git History
+
+To preserve `git blame` history, use a two-phase approach:
+
+```bash
+# Phase 1: Rename (creates rename tracking)
+git mv src/main/java/com/example/Foo.java src/main/kotlin/com/example/Foo.kt
+git commit -m "Rename Foo.java to Foo.kt"
+
+# Phase 2: Replace content (tracked as modification, not new file)
+# Write the converted Kotlin content to Foo.kt
+git commit -m "Convert Foo from Java to Kotlin"
+```
+
+If the project keeps Java and Kotlin in the same source root (e.g., `src/main/java/`),
+rename in place:
+
+```bash
+git mv src/main/java/com/example/Foo.java src/main/java/com/example/Foo.kt
+```
+
+If the project does not use Git, simply write the `.kt` file and delete the `.java` file.
+
+## Step 4: Verify
+
+After conversion, verify using [checklist.md](assets/checklist.md):
+- Attempt to compile the converted file
+- Run existing tests
+- Check annotation site targets
+- Confirm no behavioral changes
+
+## Batch Conversion
+
+When converting multiple files (a directory or package):
+
+1. **List all `.java` files** in the target scope
+2. **Sort by dependency order** — convert leaf dependencies first (files that don't
+ import other files in the conversion set), then work up to files that depend on them
+3. **Convert one file at a time** — apply the full workflow (steps 0-4) for each
+4. **Track progress** — report which files are done, which remain
+5. **Handle cross-references** — after converting a file, update imports in other Java
+ files if needed (e.g., if a class moved packages)
+
+For large batches, consider converting in packages (bottom-up from leaf packages).
+
+## Common Pitfalls
+
+See [KNOWN-ISSUES.md](references/KNOWN-ISSUES.md) for:
+- Kotlin keyword conflicts (`when`, `in`, `is`, `object`)
+- SAM conversion ambiguity
+- Platform types from Java interop
+- `@JvmStatic` / `@JvmField` / `@JvmOverloads` usage
+- Checked exceptions and `@Throws`
+- Wildcard generics → Kotlin variance
diff --git a/skills/kotlin-tooling-java-to-kotlin/assets/checklist.md b/skills/kotlin-tooling-java-to-kotlin/assets/checklist.md
new file mode 100644
index 0000000..6b629b3
--- /dev/null
+++ b/skills/kotlin-tooling-java-to-kotlin/assets/checklist.md
@@ -0,0 +1,60 @@
+# Post-Conversion Verification Checklist
+
+Use this checklist after converting each Java file to Kotlin.
+
+## Compilation & Tests
+- [ ] The `.kt` file compiles without errors
+- [ ] All existing tests still pass
+- [ ] No new compiler warnings introduced
+
+## Semantic Correctness
+- [ ] No new side-effects or behavioural changes
+- [ ] All public API signatures preserved (method names, parameter types, return types)
+- [ ] Exception behaviour unchanged (same exceptions thrown in same conditions)
+
+## Annotations
+- [ ] All annotations preserved from the original Java code
+- [ ] Annotation site targets correct (`@field:`, `@get:`, `@set:`, `@param:`)
+- [ ] No annotations accidentally dropped during conversion
+
+## Imports & Package
+- [ ] Package declaration matches original
+- [ ] All imports carried forward (except Java types that shadow Kotlin builtins)
+- [ ] No new imports added unnecessarily
+
+## Documentation
+- [ ] All Javadoc converted to KDoc format
+- [ ] `{@code ...}` → backtick code in KDoc
+- [ ] `{@link ...}` → `[...]` KDoc links
+- [ ] `
` paragraph tags → blank lines
+- [ ] `@param`, `@return`, `@throws` tags preserved
+- [ ] Class-level and method-level documentation preserved
+
+## Nullability & Mutability
+- [ ] Non-null types used only where provably non-null
+- [ ] Nullable types (`?`) used for all Java types that could be null
+- [ ] `val` used for all immutable variables/properties
+- [ ] `var` used only for mutable variables/properties
+
+## Collections
+- [ ] `MutableList`/`MutableSet`/`MutableMap` for Java's mutable collections
+- [ ] `List`/`Set`/`Map` only where Java used immutable wrappers
+
+## Kotlin Idioms
+- [ ] Getters/setters replaced with Kotlin properties where appropriate
+- [ ] String concatenation replaced with string templates where clearer
+- [ ] Elvis operator used where appropriate
+- [ ] `when` expression used instead of `switch`
+- [ ] Smart casts used after `is` checks (no explicit casts)
+
+## Framework-Specific (check applicable items)
+- [ ] **Spring**: Classes that need proxying are `open`; `@Bean` methods are `open`
+- [ ] **Lombok**: All Lombok annotations removed; replaced with Kotlin equivalents
+- [ ] **Hibernate/JPA**: Entities are `open` (not data classes); no-arg constructor provided
+- [ ] **Jackson**: `@field:` and `@get:` annotation site targets correct
+- [ ] **RxJava**: Reactive types correctly mapped to Coroutines/Flow
+- [ ] **Mockito**: `when` keyword escaped or replaced with MockK equivalent
+
+## Git History
+- [ ] File renamed via `git mv` (not delete + create)
+- [ ] Rename commit separate from content change commit
diff --git a/skills/kotlin-tooling-java-to-kotlin/references/CONVERSION-METHODOLOGY.md b/skills/kotlin-tooling-java-to-kotlin/references/CONVERSION-METHODOLOGY.md
new file mode 100644
index 0000000..908fb23
--- /dev/null
+++ b/skills/kotlin-tooling-java-to-kotlin/references/CONVERSION-METHODOLOGY.md
@@ -0,0 +1,352 @@
+# Conversion Methodology
+
+You are a senior Kotlin engineer and Java-Kotlin JVM interop specialist. Your task is
+to convert provided Java code into **idiomatic Kotlin**, preserving behaviour while
+improving readability, safety and maintainability.
+
+## The 4-Step Precognition Process
+
+Before emitting any code, run through the provided Java input and perform these 4 steps
+of thinking. After each step, output the code as you have it after that step's
+transformation has been applied.
+
+### Step 1: Faithful 1:1 Translation
+
+Convert the Java code 1 to 1 into Kotlin, prioritising faithfulness to the original
+Java semantics, to replicate the Java code's functionality and logic exactly.
+
+**Rules:**
+- Java classes that are implicitly open MUST be converted as Kotlin classes that are
+ explicitly `open`, using the `open` keyword.
+- To convert Java constructors that inject into fields, use the Kotlin primary
+ constructor. Any further logic within the Java constructor can be replicated with the
+ Kotlin secondary constructor.
+
+### Step 2: Nullability & Mutability
+
+Check that mutability and nullability are correctly expressed in your Kotlin conversion.
+Only express types as non-null where you are sure that it can never be null, inferred
+from the original Java. Use `val` instead of `var` where you see variables that are
+never modified.
+
+**Rules:**
+- If you see a logical assertion that a value is not null (e.g., `Objects.requireNonNull`),
+ this shows that the author has considered that the value can never be null. Use a
+ non-null type in this case, and remove the logical assertion.
+- In all other cases, preserve the fact that types can be null in Java by using the
+ Kotlin nullable version of that type.
+
+### Step 3: Collection Type Conversion
+
+Convert datatypes like collections from their Java variants to the Kotlin variants.
+
+**Rules:**
+- For Java collections like `List` that are mutable by default, always use the Kotlin
+ `MutableList`, unless you see explicitly that the Java code uses an immutable wrapper
+ (e.g., `Collections.unmodifiableList()`) — in this case, use the Kotlin `List` (and
+ so on for other collections like `Set`, `Map` etc.)
+
+### Step 4: Idiomatic Transformations
+
+Introduce syntactic transformations to make the output truly idiomatic.
+
+**Rules:**
+- Where getters and setters are defined as methods in Java, use the Kotlin syntax to
+ replace these methods with a more idiomatic version.
+- Lambdas should be used where they can simplify code complexity while replicating the
+ exact behaviour of the previous code.
+
+## The 5 Invariants
+
+In each stage of your chain of thought, the following invariants must hold.
+
+**Invariant 1:** No new side-effects or behaviour.
+
+**Invariant 2:** Preserve all annotations and targets exactly.
+- Annotations must target the backing field in Kotlin where they targeted the field in
+ Java. Use annotation site targets: `@field:`, `@get:`, `@set:`, `@param:`.
+
+**Invariant 3:** Preserve the package declaration and all imports.
+- Carry forwards every single import, adding no new imports. Only remove imports where
+ they would shadow Kotlin names (e.g., `java.util.List` shadows Kotlin's `List`).
+
+**Invariant 4:** Preserve all Javadoc comments.
+- In step 1, convert any Javadoc comments to KDoc comments.
+- In all other steps, carry forwards the KDoc comments, preserving information where
+ structure changes.
+
+**Invariant 5:** Ensure the output result is in Kotlin.
+- The emitted code must be syntactically valid Kotlin.
+
+**After each step**, go through each of these invariants, listing the ones that no
+longer hold. If any exist, revert to the previous step and recalculate from there.
+
+---
+
+## Example 1: Utility Class with Nullability
+
+### Java Input
+
+```java
+package com.acme.util;
+
+import java.time.LocalDate;
+import java.util.Objects;
+
+/**
+* Utility for printing a greeting that includes the current date.
+*/
+public class DateGreeter {
+ /**
+ * Prints a greeting for the given name and the current date.
+ *
+ * @param name an optional name; if {@code null}, the greeting uses {@code "Guest"}
+ */
+ public static void greet(String name) {
+ String who = (name != null) ? name : "Guest";
+ System.out.println("Hello, " + who + " - today is " + LocalDate.now());
+ }
+}
+```
+
+### Conversion Walkthrough
+
+**Step 1** — Faithful translation. The DateGreeter class is implicitly open, the
+`greet` method is static → companion object. Javadoc converted to KDoc.
+
+```kotlin
+package com.acme.util
+
+import java.time.LocalDate
+import java.util.Objects
+
+/**
+* Utility for printing a greeting that includes the current date.
+*/
+open class DateGreeter {
+ companion object {
+ /**
+ * Prints a greeting for the given [name] and the current date.
+ *
+ * @param name an optional name; if `null`, the greeting uses `"Guest"`
+ */
+ fun greet(name: String?) {
+ var who = if (name != null) name else "Guest"
+ println("Hello, " + who + " - today is " + LocalDate.now())
+ }
+ }
+}
+```
+
+Invariants check: All OK.
+
+**Step 2** — The `String?` is correct since the author checks for null. The `who`
+variable is only read, so change `var` → `val`.
+
+```kotlin
+// Same as above but with:
+val who = if (name != null) name else "Guest"
+```
+
+Invariants check: All OK.
+
+**Step 3** — No collections in this code. No changes.
+
+Invariants check: All OK.
+
+**Step 4** — The `greet` function is not tied to any state of DateGreeter, so move it
+to a top-level function. Use string templates and Elvis operator.
+
+```kotlin
+package com.acme.util
+
+import java.time.LocalDate
+import java.util.Objects
+
+/**
+* Prints a greeting for the given [name] and the current date.
+*
+* @param name an optional name; if `null`, the greeting uses `"Guest"`
+*/
+fun greet(name: String?) {
+ println("Hello, ${name ?: "Guest"} - today is ${LocalDate.now()}")
+}
+```
+
+Invariants check: All OK.
+
+---
+
+## Example 2: Domain Model with Annotations
+
+### Java Input
+
+```java
+package com.acme.model;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import javax.annotation.Nullable;
+import java.util.Objects;
+
+/**
+* Domain model for a user with a required identifier and an optional nickname.
+*
+* The {@code id} is serialized as {@code "id"} and is required.
+* The {@code nickname} may be absent.
+*/
+public class User {
+ /**
+ * Stable, non-null identifier serialized as {@code "id"}.
+ */
+ @JsonProperty("id")
+ private final String id;
+
+ /**
+ * Optional nickname for display purposes.
+ */
+ @Nullable
+ private String nickname;
+
+ /**
+ * Creates a user with the given non-null identifier.
+ *
+ * @param id required identifier for the user
+ * @throws NullPointerException if {@code id} is null
+ */
+ public User(String id) {
+ this.id = Objects.requireNonNull(id, "id");
+ }
+
+ /**
+ * Returns the identifier serialized as {@code "id"}.
+ *
+ * @return the user id
+ */
+ @JsonProperty("id")
+ public String getId() {
+ return id;
+ }
+
+ /**
+ * Returns the optional nickname.
+ *
+ * @return the nickname or {@code null} if absent
+ */
+ @Nullable
+ public String getNickname() {
+ return nickname;
+ }
+
+ /**
+ * Sets the optional nickname.
+ *
+ * @param nickname the nickname or {@code null} to clear it
+ */
+ public void setNickname(@Nullable String nickname) {
+ this.nickname = nickname;
+ }
+}
+```
+
+### Conversion Walkthrough
+
+**Step 1** — Faithful translation. Class is implicitly open → `open class`.
+`@JsonProperty("id")` on the field → `@field:JsonProperty("id")`.
+`@JsonProperty("id")` on the getter → `@get:JsonProperty("id")` when converted to
+property later. Keep explicit getters/setters at this step for faithfulness.
+
+```kotlin
+package com.acme.model
+
+import com.fasterxml.jackson.annotation.JsonProperty
+import javax.annotation.Nullable
+import java.util.Objects
+
+/**
+* Domain model for a user with a required identifier and an optional nickname.
+*
+* The `id` is serialized as `"id"` and is required.
+* The `nickname` may be absent.
+*/
+open class User {
+
+ /**
+ * Stable, non-null identifier serialized as `"id"`.
+ */
+ @field:JsonProperty("id")
+ private val id: String
+
+ /**
+ * Optional nickname for display purposes.
+ */
+ @field:Nullable
+ private var nickname: String? = null
+
+ /**
+ * Creates a user with the given non-null identifier.
+ *
+ * @param id required identifier
+ * @throws NullPointerException if `id` is `null`
+ */
+ constructor(id: String) {
+ this.id = Objects.requireNonNull(id, "id")
+ }
+
+ @get:JsonProperty("id")
+ fun getId(): String { return id }
+
+ @Nullable
+ fun getNickname(): String? { return nickname }
+
+ fun setNickname(@Nullable nickname: String?) { this.nickname = nickname }
+}
+```
+
+Invariants check: All OK.
+
+**Step 2** — `id` is non-null by design (`Objects.requireNonNull` enforces it).
+`nickname` is nullable (`@Nullable`). No val/var changes needed beyond what's already
+done. Code unchanged.
+
+Invariants check: All OK.
+
+**Step 3** — No collections. No changes.
+
+Invariants check: All OK.
+
+**Step 4** — Idiomatic Kotlin:
+1. Primary constructor with `id` as a `val` property. Apply both `@field:JsonProperty`
+ and `@get:JsonProperty` to match both Java annotation targets.
+2. Convert `nickname` getter/setter → Kotlin property with `@field:Nullable` and
+ `@get:Nullable`.
+3. Drop `Objects.requireNonNull` — Kotlin's type system enforces non-null.
+4. Preserve all imports even if now unused (invariant 3).
+
+```kotlin
+package com.acme.model
+
+import com.fasterxml.jackson.annotation.JsonProperty
+import javax.annotation.Nullable
+import java.util.Objects
+
+/**
+* Domain model for a user with a required identifier and an optional nickname.
+*
+* The `id` is serialized as `"id"` and is required.
+* The `nickname` may be absent.
+*
+* @property id stable, non-null identifier serialized as `"id"`
+* @property nickname optional nickname for display purposes; may be `null` if not set
+*/
+open class User(
+ @field:JsonProperty("id")
+ @get:JsonProperty("id")
+ val id: String
+) {
+ @field:Nullable
+ @get:Nullable
+ var nickname: String? = null
+}
+```
+
+Invariants check: All OK.
diff --git a/skills/kotlin-tooling-java-to-kotlin/references/KNOWN-ISSUES.md b/skills/kotlin-tooling-java-to-kotlin/references/KNOWN-ISSUES.md
new file mode 100644
index 0000000..9de639d
--- /dev/null
+++ b/skills/kotlin-tooling-java-to-kotlin/references/KNOWN-ISSUES.md
@@ -0,0 +1,358 @@
+# Known Issues and Common Pitfalls
+
+A reference of common issues encountered during Java-to-Kotlin conversion, with solutions.
+
+### Kotlin Keyword Conflicts
+
+Java identifiers that are reserved keywords in Kotlin will cause compilation errors after conversion.
+
+**Affected keywords:** `when`, `in`, `is`, `object`, `fun`, `val`, `var`, `typealias`, `as`
+
+**Solution:** Backtick-escape them in Kotlin:
+
+```java
+// Java
+public void when(String event) { ... }
+public boolean in(List items) { ... }
+```
+
+```kotlin
+// Kotlin — backtick-escaped
+fun `when`(event: String) { ... }
+fun `in`(items: List): Boolean { ... }
+```
+
+When the API is internal (not exposed to other modules), prefer renaming the identifier to a non-keyword alternative instead of using backticks. For example, rename `when` to `onEvent` or `in` to `contains`.
+
+### SAM Conversion Ambiguity
+
+When a Java method has overloads that each accept a different SAM (Single Abstract Method) interface, Kotlin's trailing lambda syntax becomes ambiguous. The compiler cannot determine which SAM interface the lambda should implement.
+
+```java
+// Java — overloaded method accepting different SAM types
+public class TaskExecutor {
+ void submit(Runnable task) { ... }
+ void submit(Callable task) { ... }
+}
+```
+
+```kotlin
+// Kotlin — WRONG: ambiguous, won't compile
+executor.submit { doWork() }
+
+// Kotlin — CORRECT: explicit SAM constructor
+executor.submit(Runnable { doWork() })
+executor.submit(Callable { computeResult() })
+```
+
+Use explicit SAM constructor calls whenever there are overloaded methods accepting different functional interfaces.
+
+### Platform Types
+
+Java types without nullability annotations (`@Nullable`, `@NotNull`, `@NonNull`) become "platform types" (`T!`) in Kotlin. Platform types bypass Kotlin's null-safety system — they are neither nullable nor non-null, and null checks are deferred to runtime.
+
+```java
+// Java — no nullability annotations
+public String getName() { return name; }
+public List getItems() { return items; }
+```
+
+```kotlin
+// Kotlin — BAD: platform types left in converted code
+val name = obj.name // inferred as String! — unsafe
+val items = obj.items // inferred as List! — unsafe
+
+// Kotlin — GOOD: explicit nullability based on code analysis
+val name: String = obj.name // if provably non-null
+val name: String? = obj.name // if could be null
+val items: List = obj.items // if neither list nor elements are null
+```
+
+Always add explicit type declarations to eliminate platform types. Analyze the Java source code, documentation, and call sites to determine the correct nullability.
+
+### @JvmStatic / @JvmField / @JvmOverloads
+
+When converted Kotlin code is still called from Java, use JVM interop annotations to maintain a clean Java API:
+
+**`@JvmStatic`** — Makes companion object functions accessible as static methods from Java:
+
+```kotlin
+class Config {
+ companion object {
+ @JvmStatic
+ fun getInstance(): Config = ...
+ }
+}
+```
+
+```java
+// Java callers can use: Config.getInstance()
+// Without @JvmStatic they would need: Config.Companion.getInstance()
+```
+
+**`@JvmField`** — Exposes a property as a direct field rather than through getter/setter:
+
+```kotlin
+class Constants {
+ companion object {
+ @JvmField
+ val DEFAULT_TIMEOUT = 30_000L
+ }
+}
+```
+
+```java
+// Java callers can use: Constants.DEFAULT_TIMEOUT
+// Without @JvmField they would need: Constants.Companion.getDEFAULT_TIMEOUT()
+```
+
+**`@JvmOverloads`** — Generates Java overloads for functions with default parameters:
+
+```kotlin
+@JvmOverloads
+fun connect(host: String, port: Int = 443, secure: Boolean = true) { ... }
+```
+
+```java
+// Java sees three overloads:
+// connect(String host)
+// connect(String host, int port)
+// connect(String host, int port, boolean secure)
+```
+
+### Checked Exceptions
+
+Kotlin does not have checked exceptions. When Kotlin code is called from Java, the Java compiler will not know about thrown exceptions unless annotated with `@Throws`:
+
+```kotlin
+// Without @Throws, Java callers cannot catch IOException in a catch block
+// (the Java compiler will say "exception is never thrown in the corresponding try block")
+
+@Throws(IOException::class)
+fun readFile(path: String): String {
+ return File(path).readText()
+}
+```
+
+Add `@Throws` to every Kotlin function that throws checked exceptions and is called from Java code.
+
+### Wildcard Generics
+
+Java wildcard types map to Kotlin's variance annotations:
+
+| Java | Kotlin | Description |
+|------|--------|-------------|
+| `? extends T` | `out T` | Covariance (producer) |
+| `? super T` | `in T` | Contravariance (consumer) |
+| Raw type `List` | `List` | Add explicit type parameter |
+
+```java
+// Java
+public void process(List extends Number> numbers) { ... }
+public void addAll(List super Integer> target) { ... }
+public void legacy(List items) { ... } // raw type
+```
+
+```kotlin
+// Kotlin
+fun process(numbers: List) { ... }
+fun addAll(target: MutableList) { ... }
+fun legacy(items: List) { ... } // explicit type parameter
+```
+
+For raw types, analyze the code to determine the most specific type parameter rather than defaulting to `Any?`.
+
+### Static Members
+
+Java's `static` keyword has no direct equivalent in Kotlin. Use the following mappings:
+
+**Static methods** — Use companion object functions, or top-level functions if they don't need class state:
+
+```java
+// Java
+public class StringUtils {
+ public static String capitalize(String s) { ... }
+}
+```
+
+```kotlin
+// Kotlin — top-level function (preferred when no class state needed)
+fun capitalize(s: String): String { ... }
+
+// Kotlin — companion object (when logically tied to the class)
+class StringUtils {
+ companion object {
+ fun capitalize(s: String): String { ... }
+ }
+}
+```
+
+**Static constants** — Use `const val` for compile-time constants (primitives and String), `val` for object constants:
+
+```kotlin
+class HttpStatus {
+ companion object {
+ const val OK = 200 // primitive — const val
+ const val NOT_FOUND_MESSAGE = "Not Found" // String — const val
+ val DEFAULT_HEADERS = mapOf("Accept" to "application/json") // object — val
+ }
+}
+```
+
+**Static initializers** — Use companion object `init {}` block or top-level code:
+
+```kotlin
+class Registry {
+ companion object {
+ private val handlers = mutableMapOf()
+ init {
+ handlers["default"] = DefaultHandler()
+ }
+ }
+}
+```
+
+### Synchronized Blocks
+
+Java's `synchronized` constructs map to Kotlin as follows:
+
+**Synchronized blocks** — Use Kotlin's `synchronized()` function:
+
+```java
+// Java
+synchronized (lock) {
+ sharedState.update();
+}
+```
+
+```kotlin
+// Kotlin
+synchronized(lock) {
+ sharedState.update()
+}
+```
+
+**Synchronized methods** — Use the `@Synchronized` annotation:
+
+```java
+// Java
+public synchronized void update() { ... }
+```
+
+```kotlin
+// Kotlin
+@Synchronized
+fun update() { ... }
+```
+
+### Anonymous Inner Classes
+
+**Single Abstract Method (SAM) interfaces** — Convert to lambda syntax:
+
+```java
+// Java
+executor.submit(new Runnable() {
+ @Override
+ public void run() {
+ doWork();
+ }
+});
+```
+
+```kotlin
+// Kotlin
+executor.submit(Runnable { doWork() })
+```
+
+**Multiple methods or abstract classes** — Use `object` expression:
+
+```java
+// Java
+view.addListener(new ViewListener() {
+ @Override
+ public void onOpen() { ... }
+ @Override
+ public void onClose() { ... }
+});
+```
+
+```kotlin
+// Kotlin
+view.addListener(object : ViewListener {
+ override fun onOpen() { ... }
+ override fun onClose() { ... }
+})
+```
+
+### Array Handling
+
+Java arrays map to Kotlin types as follows:
+
+| Java | Kotlin | Notes |
+|------|--------|-------|
+| `String[]` | `Array` | Reference type arrays |
+| `int[]` | `IntArray` | Primitive array (not `Array`) |
+| `long[]` | `LongArray` | Primitive array |
+| `double[]` | `DoubleArray` | Primitive array |
+| `boolean[]` | `BooleanArray` | Primitive array |
+| `Object[]` | `Array` | |
+| `new int[10]` | `IntArray(10)` | Array creation |
+| `new String[10]` | `arrayOfNulls(10)` | Nullable element array |
+| `String... args` | `vararg args: String` | Varargs parameter |
+
+Using `Array` instead of `IntArray` causes boxing overhead — always use the specialized primitive array types.
+
+### Ternary Operator
+
+Kotlin has no ternary operator. Use `if`/`else` as an expression:
+
+```java
+// Java
+String label = (count > 0) ? "Items: " + count : "Empty";
+```
+
+```kotlin
+// Kotlin
+val label = if (count > 0) "Items: $count" else "Empty"
+```
+
+### instanceof
+
+Java's `instanceof` maps to Kotlin's `is` keyword. Kotlin supports smart casting, so an explicit cast after an `is` check is unnecessary:
+
+```java
+// Java
+if (shape instanceof Circle) {
+ Circle circle = (Circle) shape;
+ double area = circle.getArea();
+}
+```
+
+```kotlin
+// Kotlin — smart cast, no explicit cast needed
+if (shape is Circle) {
+ val area = shape.area // shape is automatically cast to Circle
+}
+```
+
+### try-with-resources
+
+Java's try-with-resources maps to Kotlin's `.use {}` extension function:
+
+```java
+// Java
+try (BufferedReader reader = new BufferedReader(new FileReader(path))) {
+ String line = reader.readLine();
+ process(line);
+}
+```
+
+```kotlin
+// Kotlin
+BufferedReader(FileReader(path)).use { reader ->
+ val line = reader.readLine()
+ process(line)
+}
+```
+
+The `.use {}` function works on any `Closeable` or `AutoCloseable` instance and guarantees the resource is closed even if an exception is thrown.
diff --git a/skills/kotlin-tooling-java-to-kotlin/references/frameworks/DAGGER-HILT.md b/skills/kotlin-tooling-java-to-kotlin/references/frameworks/DAGGER-HILT.md
new file mode 100644
index 0000000..fe3636b
--- /dev/null
+++ b/skills/kotlin-tooling-java-to-kotlin/references/frameworks/DAGGER-HILT.md
@@ -0,0 +1,160 @@
+# Dagger / Hilt Conversion Guide
+
+## When This Applies
+
+This guide applies when the Java source contains imports matching `dagger.*` or
+`dagger.hilt.*`. This covers Dagger 2, Hilt for Android, and Hilt Jetpack integrations.
+
+## Key Rules
+
+### 1. @Inject constructor syntax
+
+Kotlin places `@Inject` before the `constructor` keyword in the primary constructor:
+
+```kotlin
+class Foo @Inject constructor(private val bar: Bar)
+```
+
+### 2. @Module classes with @Provides methods
+
+Keep `@Provides` methods `open`, or use `object` for modules that contain only
+`@JvmStatic` provides methods (companion object pattern):
+
+```kotlin
+@Module
+@InstallIn(SingletonComponent::class)
+object NetworkModule {
+ @Provides
+ @Singleton
+ fun provideOkHttpClient(): OkHttpClient = OkHttpClient.Builder().build()
+}
+```
+
+### 3. @Binds abstract methods
+
+`@Binds` methods work in abstract classes exactly as in Java. Convert the abstract
+class directly — no special Kotlin considerations.
+
+### 4. Hilt Android annotations
+
+`@HiltAndroidApp`, `@AndroidEntryPoint`, `@HiltViewModel` — preserve these exactly
+on Application, Activity, Fragment, and ViewModel classes.
+
+### 5. Scoping annotations
+
+`@Singleton`, `@ActivityScoped`, `@ViewModelScoped`, `@FragmentScoped` — preserve
+exactly. No annotation site target is needed.
+
+### 6. @AssistedInject / @AssistedFactory
+
+`@AssistedInject` replaces `@Inject` on the constructor. `@Assisted` parameters
+appear alongside regular injected parameters in the primary constructor:
+
+```kotlin
+class PlayerViewModel @AssistedInject constructor(
+ @Assisted private val playerId: String,
+ private val repository: PlayerRepository
+) : ViewModel()
+```
+
+### 7. @Component / @Subcomponent interfaces
+
+Convert directly to Kotlin interfaces. Dagger's annotation processing works
+identically with Kotlin interfaces via kapt or KSP.
+
+---
+
+## Examples
+
+### Example 1: Hilt ViewModel with @Inject Constructor and a @Module
+
+**Java:**
+
+```java
+package com.acme.feature;
+
+import androidx.lifecycle.ViewModel;
+import dagger.Module;
+import dagger.Provides;
+import dagger.hilt.InstallIn;
+import dagger.hilt.android.lifecycle.HiltViewModel;
+import dagger.hilt.components.SingletonComponent;
+import javax.inject.Inject;
+import javax.inject.Singleton;
+
+@HiltViewModel
+public class UserProfileViewModel extends ViewModel {
+
+ private final UserRepository userRepository;
+ private final AnalyticsTracker analyticsTracker;
+
+ @Inject
+ public UserProfileViewModel(UserRepository userRepository, AnalyticsTracker analyticsTracker) {
+ this.userRepository = userRepository;
+ this.analyticsTracker = analyticsTracker;
+ }
+
+ public LiveData getUser(String userId) {
+ analyticsTracker.trackProfileView(userId);
+ return userRepository.getUser(userId);
+ }
+}
+
+@Module
+@InstallIn(SingletonComponent.class)
+public class AnalyticsModule {
+
+ @Provides
+ @Singleton
+ public AnalyticsTracker provideAnalyticsTracker(Application app) {
+ return new AnalyticsTracker(app);
+ }
+}
+```
+
+**Kotlin:**
+
+```kotlin
+package com.acme.feature
+
+import androidx.lifecycle.LiveData
+import androidx.lifecycle.ViewModel
+import dagger.Module
+import dagger.Provides
+import dagger.hilt.InstallIn
+import dagger.hilt.android.lifecycle.HiltViewModel
+import dagger.hilt.components.SingletonComponent
+import javax.inject.Inject
+import javax.inject.Singleton
+
+@HiltViewModel
+class UserProfileViewModel @Inject constructor(
+ private val userRepository: UserRepository,
+ private val analyticsTracker: AnalyticsTracker
+) : ViewModel() {
+
+ fun getUser(userId: String): LiveData {
+ analyticsTracker.trackProfileView(userId)
+ return userRepository.getUser(userId)
+ }
+}
+
+@Module
+@InstallIn(SingletonComponent::class)
+object AnalyticsModule {
+
+ @Provides
+ @Singleton
+ fun provideAnalyticsTracker(app: Application): AnalyticsTracker {
+ return AnalyticsTracker(app)
+ }
+}
+```
+
+Key changes:
+- `@Inject` moves before the `constructor` keyword in the primary constructor.
+- Constructor parameters become `private val` in the primary constructor.
+- The module class becomes an `object` since it contains only static-like provides methods.
+- `SingletonComponent.class` becomes `SingletonComponent::class` (Kotlin class reference).
+- Java getter method `getUser` becomes a regular function `getUser` (no `get` prefix
+ convention change needed here since it takes a parameter).
diff --git a/skills/kotlin-tooling-java-to-kotlin/references/frameworks/GUICE.md b/skills/kotlin-tooling-java-to-kotlin/references/frameworks/GUICE.md
new file mode 100644
index 0000000..11a795d
--- /dev/null
+++ b/skills/kotlin-tooling-java-to-kotlin/references/frameworks/GUICE.md
@@ -0,0 +1,165 @@
+# Guice Conversion Guide
+
+## When This Applies
+
+This guide applies when the Java source contains imports matching `com.google.inject.*`.
+This covers Google Guice core, Guice multibindings, and Guice servlet.
+
+## Key Rules
+
+### 1. @Inject constructor syntax
+
+Kotlin places `@Inject` before the `constructor` keyword in the primary constructor:
+
+```kotlin
+class Foo @Inject constructor(private val bar: Bar)
+```
+
+### 2. @Provides methods in Modules
+
+Keep `@Provides` methods as regular functions. Guice modules extend `AbstractModule`,
+so override `configure()` as usual.
+
+### 3. Module.configure() override
+
+Override `configure()` in Kotlin. Use Guice's binding DSL with Kotlin class references:
+
+```kotlin
+bind(Foo::class.java).to(FooImpl::class.java)
+```
+
+### 4. @Named qualifier — annotation site targets
+
+In Kotlin, `@Named` on constructor parameters needs a site target to reach the
+parameter (not the field or property). Use `@param:Named` for constructor injection:
+
+```kotlin
+class Foo @Inject constructor(
+ @param:Named("primary") private val dataSource: DataSource
+)
+```
+
+When used on function parameters (e.g., in `@Provides` methods), no site target
+is needed.
+
+### 5. @Singleton scope
+
+Preserve `@Singleton` exactly. It can be placed on the class declaration or in
+module bindings via `.in(Singleton::class.java)`.
+
+### 6. Provider
+
+`Provider` can stay as-is for lazy or scoped injection. Where the only purpose
+is deferred initialization, Kotlin's `lazy` delegation can be used as an alternative
+outside of Guice-managed contexts.
+
+---
+
+## Examples
+
+### Example 1: Guice Module with Bindings and an Injected Class
+
+**Java:**
+
+```java
+package com.acme.config;
+
+import com.google.inject.AbstractModule;
+import com.google.inject.Provides;
+import com.google.inject.Singleton;
+import com.google.inject.name.Named;
+
+public class AppModule extends AbstractModule {
+
+ @Override
+ protected void configure() {
+ bind(CacheService.class).to(RedisCacheService.class);
+ bind(NotificationService.class).to(EmailNotificationService.class).in(Singleton.class);
+ }
+
+ @Provides
+ @Singleton
+ public HttpClient provideHttpClient(@Named("baseUrl") String baseUrl) {
+ return new HttpClient(baseUrl);
+ }
+}
+```
+
+```java
+package com.acme.service;
+
+import com.google.inject.Inject;
+import com.google.inject.name.Named;
+
+public class OrderService {
+
+ private final CacheService cacheService;
+ private final HttpClient httpClient;
+ private final String region;
+
+ @Inject
+ public OrderService(CacheService cacheService, HttpClient httpClient, @Named("region") String region) {
+ this.cacheService = cacheService;
+ this.httpClient = httpClient;
+ this.region = region;
+ }
+
+ public Order findById(Long id) {
+ return cacheService.getOrFetch(id, () -> httpClient.get("/orders/" + id, Order.class));
+ }
+}
+```
+
+**Kotlin:**
+
+```kotlin
+package com.acme.config
+
+import com.google.inject.AbstractModule
+import com.google.inject.Provides
+import com.google.inject.Singleton
+import com.google.inject.name.Named
+
+class AppModule : AbstractModule() {
+
+ override fun configure() {
+ bind(CacheService::class.java).to(RedisCacheService::class.java)
+ bind(NotificationService::class.java).to(EmailNotificationService::class.java).`in`(Singleton::class.java)
+ }
+
+ @Provides
+ @Singleton
+ fun provideHttpClient(@Named("baseUrl") baseUrl: String): HttpClient {
+ return HttpClient(baseUrl)
+ }
+}
+```
+
+```kotlin
+package com.acme.service
+
+import com.google.inject.Inject
+import com.google.inject.name.Named
+
+class OrderService @Inject constructor(
+ private val cacheService: CacheService,
+ private val httpClient: HttpClient,
+ @param:Named("region") private val region: String
+) {
+
+ fun findById(id: Long): Order? {
+ return cacheService.getOrFetch(id) { httpClient.get("/orders/$id", Order::class.java) }
+ }
+}
+```
+
+Key changes:
+- `@Inject` moves before the `constructor` keyword in the primary constructor.
+- Constructor parameters become `private val` in the primary constructor.
+- `@Named("region")` uses `@param:Named` site target so the annotation reaches the
+ constructor parameter rather than the Kotlin property.
+- `.in(Singleton.class)` becomes `` .`in`(Singleton::class.java) `` — `in` is a
+ reserved keyword in Kotlin and must be escaped with backticks.
+- The lambda in `getOrFetch` uses Kotlin's trailing lambda syntax instead of an
+ anonymous inner class.
+- String concatenation `"/orders/" + id` becomes a string template `"/orders/$id"`.
diff --git a/skills/kotlin-tooling-java-to-kotlin/references/frameworks/HIBERNATE.md b/skills/kotlin-tooling-java-to-kotlin/references/frameworks/HIBERNATE.md
new file mode 100644
index 0000000..014228d
--- /dev/null
+++ b/skills/kotlin-tooling-java-to-kotlin/references/frameworks/HIBERNATE.md
@@ -0,0 +1,227 @@
+# Hibernate / JPA Conversion Guide
+
+## When This Applies
+
+Detected when imports match any of:
+- `javax.persistence.*`
+- `jakarta.persistence.*`
+- `org.hibernate.*`
+
+## Critical Rules
+
+1. **Do NOT use data classes for JPA entities.** Data classes generate `equals`/`hashCode`
+ based on all properties, which breaks Hibernate's identity semantics and proxy creation.
+
+2. **Keep entity classes `open`.** Hibernate creates proxies via subclassing. Kotlin classes
+ are `final` by default, so you must use `open` explicitly (or use the `allopen` compiler
+ plugin with JPA annotation support).
+
+3. **Provide a no-argument constructor** if Hibernate requires one for proxy creation. Use a
+ secondary constructor or default values for all primary constructor parameters.
+
+4. **Annotation site targets matter:**
+ - `@Id`, `@Column`, `@GeneratedValue` on fields → use `@field:Id`, `@field:Column`, etc.
+ in Kotlin, OR place annotations on constructor parameters with `@field:` site target.
+ - `@ManyToOne`, `@OneToMany`, `@JoinColumn` → same `@field:` targeting.
+
+5. **Lazy loading considerations:** `@ManyToOne(fetch = FetchType.LAZY)` requires the entity
+ class to be open for proxy creation. `@OneToMany` with lazy collections work with Kotlin's
+ `MutableList`.
+
+6. **`@Embeddable` classes**: Can be data classes (they don't need proxies).
+
+7. **`@MappedSuperclass`**: Must be `open abstract class` in Kotlin.
+
+## Examples
+
+### Example 1: JPA Entity with @Id, @Column, and Relationships
+
+**Java:**
+```java
+@Entity
+@Table(name = "users")
+public class User {
+
+ @Id
+ @GeneratedValue(strategy = GenerationType.IDENTITY)
+ private Long id;
+
+ @Column(name = "username", nullable = false, unique = true)
+ private String username;
+
+ @Column(name = "email")
+ private String email;
+
+ @ManyToOne(fetch = FetchType.LAZY)
+ @JoinColumn(name = "department_id")
+ private Department department;
+
+ protected User() {}
+
+ public User(String username, String email, Department department) {
+ this.username = username;
+ this.email = email;
+ this.department = department;
+ }
+
+ public Long getId() { return id; }
+ public String getUsername() { return username; }
+ public String getEmail() { return email; }
+ public void setEmail(String email) { this.email = email; }
+ public Department getDepartment() { return department; }
+ public void setDepartment(Department department) { this.department = department; }
+}
+```
+
+**Kotlin:**
+```kotlin
+@Entity
+@Table(name = "users")
+open class User(
+
+ @field:Column(name = "username", nullable = false, unique = true)
+ open val username: String,
+
+ @field:Column(name = "email")
+ open var email: String? = null,
+
+ @field:ManyToOne(fetch = FetchType.LAZY)
+ @field:JoinColumn(name = "department_id")
+ open var department: Department? = null
+
+) {
+ @field:Id
+ @field:GeneratedValue(strategy = GenerationType.IDENTITY)
+ open var id: Long? = null
+ protected set
+
+ protected constructor() : this(username = "")
+}
+```
+
+### Example 2: @Embeddable Value Object
+
+**Java:**
+```java
+@Embeddable
+public class Address {
+
+ @Column(name = "street")
+ private String street;
+
+ @Column(name = "city")
+ private String city;
+
+ @Column(name = "zip_code")
+ private String zipCode;
+
+ protected Address() {}
+
+ public Address(String street, String city, String zipCode) {
+ this.street = street;
+ this.city = city;
+ this.zipCode = zipCode;
+ }
+
+ public String getStreet() { return street; }
+ public String getCity() { return city; }
+ public String getZipCode() { return zipCode; }
+}
+```
+
+**Kotlin:**
+```kotlin
+@Embeddable
+data class Address(
+
+ @field:Column(name = "street")
+ val street: String = "",
+
+ @field:Column(name = "city")
+ val city: String = "",
+
+ @field:Column(name = "zip_code")
+ val zipCode: String = ""
+)
+```
+
+`@Embeddable` classes can safely be data classes because Hibernate does not proxy them.
+Default values satisfy the no-arg constructor requirement.
+
+### Example 3: Entity with @ManyToOne and @OneToMany
+
+**Java:**
+```java
+@Entity
+@Table(name = "departments")
+public class Department {
+
+ @Id
+ @GeneratedValue(strategy = GenerationType.IDENTITY)
+ private Long id;
+
+ @Column(name = "name", nullable = false)
+ private String name;
+
+ @OneToMany(mappedBy = "department", cascade = CascadeType.ALL, orphanRemoval = true)
+ private List users = new ArrayList<>();
+
+ protected Department() {}
+
+ public Department(String name) {
+ this.name = name;
+ }
+
+ public Long getId() { return id; }
+ public String getName() { return name; }
+ public List getUsers() { return users; }
+
+ public void addUser(User user) {
+ users.add(user);
+ user.setDepartment(this);
+ }
+
+ public void removeUser(User user) {
+ users.remove(user);
+ user.setDepartment(null);
+ }
+}
+```
+
+**Kotlin:**
+```kotlin
+@Entity
+@Table(name = "departments")
+open class Department(
+
+ @field:Column(name = "name", nullable = false)
+ open val name: String = ""
+
+) {
+ @field:Id
+ @field:GeneratedValue(strategy = GenerationType.IDENTITY)
+ open var id: Long? = null
+ protected set
+
+ @field:OneToMany(mappedBy = "department", cascade = [CascadeType.ALL], orphanRemoval = true)
+ open val users: MutableList = mutableListOf()
+
+ protected constructor() : this(name = "")
+
+ fun addUser(user: User) {
+ users.add(user)
+ user.department = this
+ }
+
+ fun removeUser(user: User) {
+ users.remove(user)
+ user.department = null
+ }
+}
+```
+
+Key points in this example:
+- `cascade` array syntax uses Kotlin's `[CascadeType.ALL]` instead of Java's `{CascadeType.ALL}`.
+- The collection is typed as `MutableList` to allow Hibernate to manage the relationship.
+- The class and its properties are `open` so Hibernate can create proxies.
+- The no-arg constructor delegates to the primary constructor with default values.
diff --git a/skills/kotlin-tooling-java-to-kotlin/references/frameworks/JACKSON.md b/skills/kotlin-tooling-java-to-kotlin/references/frameworks/JACKSON.md
new file mode 100644
index 0000000..aa7b8f1
--- /dev/null
+++ b/skills/kotlin-tooling-java-to-kotlin/references/frameworks/JACKSON.md
@@ -0,0 +1,248 @@
+# Jackson Conversion Guide
+
+## When This Applies
+
+Detected when imports match `com.fasterxml.jackson.*`.
+
+## Key Rules
+
+1. **Annotation site targets**:
+ - `@JsonProperty` on a Java field → `@field:JsonProperty` in Kotlin.
+ - `@JsonProperty` on a Java getter → `@get:JsonProperty` in Kotlin.
+ - When converting to Kotlin properties, apply BOTH `@field:` and `@get:` targets to
+ match Java's dual annotation on field + getter.
+
+2. **@JsonCreator**: Java's `@JsonCreator` static factory or constructor → Kotlin primary
+ constructor. The `@JsonCreator` annotation is often unnecessary on Kotlin's primary
+ constructor if using the Jackson Kotlin module, but preserve it for safety.
+
+3. **@JsonIgnore**: Preserve exactly. Use `@get:JsonIgnore` or `@field:JsonIgnore`
+ depending on original target.
+
+4. **@JsonDeserialize / @JsonSerialize**: Preserve exactly with correct site targets.
+
+5. **@JsonInclude**: Preserve on class or property level.
+
+6. **@JsonFormat**: Preserve with `@field:JsonFormat` site target.
+
+7. **Jackson Kotlin Module**: Note that projects using Jackson with Kotlin should add
+ `jackson-module-kotlin` for proper Kotlin support (data classes, default values,
+ nullable types). This is NOT something to add during conversion — just note it if
+ missing.
+
+8. **Builder pattern with @JsonPOJOBuilder**: Replace with primary constructor +
+ `@JsonCreator` if converting to data class. Otherwise preserve.
+
+---
+
+## Example 1: DTO with Various Jackson Annotations
+
+### Java Input
+
+```java
+package com.acme.dto;
+
+import com.fasterxml.jackson.annotation.JsonIgnore;
+import com.fasterxml.jackson.annotation.JsonInclude;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import com.fasterxml.jackson.annotation.JsonFormat;
+
+/**
+ * Data transfer object for an order summary.
+ */
+@JsonInclude(JsonInclude.Include.NON_NULL)
+public class OrderSummaryDto {
+
+ @JsonProperty("order_id")
+ private final String orderId;
+
+ @JsonProperty("total_amount")
+ private final double totalAmount;
+
+ @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd")
+ private final String createdDate;
+
+ @JsonIgnore
+ private String internalNote;
+
+ public OrderSummaryDto(String orderId, double totalAmount, String createdDate) {
+ this.orderId = orderId;
+ this.totalAmount = totalAmount;
+ this.createdDate = createdDate;
+ }
+
+ @JsonProperty("order_id")
+ public String getOrderId() {
+ return orderId;
+ }
+
+ @JsonProperty("total_amount")
+ public double getTotalAmount() {
+ return totalAmount;
+ }
+
+ @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd")
+ public String getCreatedDate() {
+ return createdDate;
+ }
+
+ @JsonIgnore
+ public String getInternalNote() {
+ return internalNote;
+ }
+
+ public void setInternalNote(String internalNote) {
+ this.internalNote = internalNote;
+ }
+}
+```
+
+### Kotlin Output
+
+```kotlin
+package com.acme.dto
+
+import com.fasterxml.jackson.annotation.JsonIgnore
+import com.fasterxml.jackson.annotation.JsonInclude
+import com.fasterxml.jackson.annotation.JsonProperty
+import com.fasterxml.jackson.annotation.JsonFormat
+
+/**
+ * Data transfer object for an order summary.
+ *
+ * @property orderId unique identifier for the order, serialized as `"order_id"`
+ * @property totalAmount total monetary amount, serialized as `"total_amount"`
+ * @property createdDate date the order was created, formatted as `yyyy-MM-dd`
+ */
+@JsonInclude(JsonInclude.Include.NON_NULL)
+open class OrderSummaryDto(
+ @field:JsonProperty("order_id")
+ @get:JsonProperty("order_id")
+ val orderId: String?,
+
+ @field:JsonProperty("total_amount")
+ @get:JsonProperty("total_amount")
+ val totalAmount: Double,
+
+ @field:JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd")
+ @get:JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd")
+ val createdDate: String?
+) {
+ @field:JsonIgnore
+ @get:JsonIgnore
+ var internalNote: String? = null
+}
+```
+
+**Key points:**
+- `@JsonInclude` stays at class level — no site target needed.
+- `@JsonProperty` gets both `@field:` and `@get:` to match the Java field + getter
+ annotations.
+- `@JsonFormat` also gets both `@field:` and `@get:` since Java had it on both.
+- `@JsonIgnore` gets both `@field:` and `@get:` to suppress serialization fully.
+
+---
+
+## Example 2: Class with @JsonCreator Factory Method
+
+### Java Input
+
+```java
+package com.acme.model;
+
+import com.fasterxml.jackson.annotation.JsonCreator;
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+/**
+ * Immutable configuration entry deserialized from JSON.
+ */
+public class ConfigEntry {
+
+ private final String key;
+ private final String value;
+ private final boolean enabled;
+
+ @JsonCreator
+ public static ConfigEntry create(
+ @JsonProperty("key") String key,
+ @JsonProperty("value") String value,
+ @JsonProperty("enabled") boolean enabled) {
+ return new ConfigEntry(key, value, enabled);
+ }
+
+ private ConfigEntry(String key, String value, boolean enabled) {
+ this.key = key;
+ this.value = value;
+ this.enabled = enabled;
+ }
+
+ @JsonProperty("key")
+ public String getKey() {
+ return key;
+ }
+
+ @JsonProperty("value")
+ public String getValue() {
+ return value;
+ }
+
+ @JsonProperty("enabled")
+ public boolean isEnabled() {
+ return enabled;
+ }
+}
+```
+
+### Kotlin Output
+
+```kotlin
+package com.acme.model
+
+import com.fasterxml.jackson.annotation.JsonCreator
+import com.fasterxml.jackson.annotation.JsonProperty
+
+/**
+ * Immutable configuration entry deserialized from JSON.
+ *
+ * @property key the configuration key
+ * @property value the configuration value
+ * @property enabled whether this entry is active
+ */
+data class ConfigEntry @JsonCreator constructor(
+ @field:JsonProperty("key")
+ @get:JsonProperty("key")
+ val key: String?,
+
+ @field:JsonProperty("value")
+ @get:JsonProperty("value")
+ val value: String?,
+
+ @field:JsonProperty("enabled")
+ @get:JsonProperty("enabled")
+ val enabled: Boolean
+) {
+ companion object {
+ /**
+ * Factory method preserved for documentation; the primary constructor
+ * with [JsonCreator] handles deserialization directly.
+ */
+ @JsonCreator
+ @JvmStatic
+ fun create(
+ @JsonProperty("key") key: String?,
+ @JsonProperty("value") value: String?,
+ @JsonProperty("enabled") enabled: Boolean
+ ): ConfigEntry = ConfigEntry(key, value, enabled)
+ }
+}
+```
+
+**Key points:**
+- The Java `@JsonCreator` static factory is converted to a Kotlin primary constructor
+ with `@JsonCreator`. The companion object factory is preserved for backward
+ compatibility but the primary constructor handles deserialization.
+- The class becomes a `data class` since it is immutable and value-oriented.
+- `@JsonCreator` is kept on the primary constructor for safety, ensuring Jackson can
+ deserialize even without the Jackson Kotlin module.
+- String parameters remain nullable (`String?`) since Java strings are nullable by
+ default and there is no `@NonNull` or `Objects.requireNonNull` evidence.
diff --git a/skills/kotlin-tooling-java-to-kotlin/references/frameworks/JUNIT.md b/skills/kotlin-tooling-java-to-kotlin/references/frameworks/JUNIT.md
new file mode 100644
index 0000000..fb4a286
--- /dev/null
+++ b/skills/kotlin-tooling-java-to-kotlin/references/frameworks/JUNIT.md
@@ -0,0 +1,193 @@
+# JUnit / TestNG Conversion Guide
+
+## When This Applies
+
+Detected when imports match `org.junit.*` or `org.testng.*`.
+
+## Key Rules
+
+### 1. JUnit 4 to Kotlin (with JUnit 5)
+
+| JUnit 4 | Kotlin (JUnit 5 / kotlin.test) |
+|---|---|
+| `@Test` | `@Test` (from `kotlin.test` or `org.junit.jupiter.api`) |
+| `@Before` | `@BeforeEach` (JUnit 5) or `@BeforeTest` (kotlin.test) |
+| `@After` | `@AfterEach` (JUnit 5) or `@AfterTest` (kotlin.test) |
+| `@BeforeClass` | `@BeforeAll` in companion object with `@JvmStatic` |
+| `@AfterClass` | `@AfterAll` in companion object with `@JvmStatic` |
+| `@RunWith` | `@ExtendWith` (JUnit 5) |
+| `@Ignore` | `@Disabled` (JUnit 5) |
+| `@Rule` / `@ClassRule` | `@ExtendWith` or `@RegisterExtension` |
+| `Assert.assertEquals(expected, actual)` | `assertEquals(expected, actual)` (kotlin.test) |
+| `Assert.assertTrue(condition)` | `assertTrue(condition)` (kotlin.test) |
+| `@Test(expected = X.class)` | `assertFailsWith { }` (kotlin.test) or `assertThrows { }` (JUnit 5) |
+
+### 2. JUnit 5 stays mostly the same
+
+JUnit 5 annotations (`@Test`, `@BeforeEach`, `@AfterEach`, etc.) remain unchanged.
+Focus on Kotlin idioms in the test body:
+
+- `assertThrows { code }` — uses reified generics, no `.class` needed.
+- Test classes and methods do not need to be `public` — Kotlin's default visibility
+ is public, which satisfies JUnit's requirements.
+- Test methods do not need `open` unless using a framework that subclasses the test
+ (e.g., certain Spring test configurations).
+
+### 3. TestNG to Kotlin
+
+| TestNG | Kotlin (JUnit 5) |
+|---|---|
+| `@Test` | `@Test` |
+| `@BeforeMethod` | `@BeforeEach` |
+| `@AfterMethod` | `@AfterEach` |
+| `@BeforeClass` | `@BeforeAll` with `@JvmStatic` in companion object |
+| `@AfterClass` | `@AfterAll` with `@JvmStatic` in companion object |
+| `@DataProvider` | `@ParameterizedTest` + `@MethodSource` |
+
+### 4. Assertion style
+
+Prefer `kotlin.test` assertions (`assertEquals`, `assertTrue`, `assertFailsWith`)
+for portability across test frameworks. They delegate to the underlying framework
+at runtime.
+
+### 5. Backtick method names
+
+Kotlin allows backtick-quoted method names for readable test names:
+```kotlin
+@Test
+fun `should return empty list when no users exist`() { ... }
+```
+
+---
+
+## Example: JUnit 4 Test Class to Kotlin with JUnit 5
+
+### Java Input
+
+```java
+package com.acme.service;
+
+import org.junit.Before;
+import org.junit.After;
+import org.junit.Test;
+import org.junit.BeforeClass;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+
+/**
+ * Tests for the UserService class.
+ */
+public class UserServiceTest {
+
+ private static DatabaseConnection db;
+ private UserService userService;
+
+ @BeforeClass
+ public static void setupDatabase() {
+ db = DatabaseConnection.create("test");
+ }
+
+ @Before
+ public void setUp() {
+ userService = new UserService(db);
+ }
+
+ @After
+ public void tearDown() {
+ db.clearTestData();
+ }
+
+ @Test
+ public void testFindById() {
+ User user = userService.findById(1L);
+ assertNotNull(user);
+ assertEquals("Alice", user.getName());
+ }
+
+ @Test
+ public void testFindAllReturnsNonEmptyList() {
+ List users = userService.findAll();
+ assertNotNull(users);
+ assertTrue(users.size() > 0);
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testFindByIdWithNegativeIdThrows() {
+ userService.findById(-1L);
+ }
+}
+```
+
+### Kotlin Output
+
+```kotlin
+package com.acme.service
+
+import org.junit.jupiter.api.AfterEach
+import org.junit.jupiter.api.BeforeAll
+import org.junit.jupiter.api.BeforeEach
+import org.junit.jupiter.api.Test
+import kotlin.test.assertEquals
+import kotlin.test.assertFailsWith
+import kotlin.test.assertNotNull
+import kotlin.test.assertTrue
+
+/**
+ * Tests for the UserService class.
+ */
+class UserServiceTest {
+
+ companion object {
+ private lateinit var db: DatabaseConnection
+
+ @BeforeAll
+ @JvmStatic
+ fun setupDatabase() {
+ db = DatabaseConnection.create("test")
+ }
+ }
+
+ private lateinit var userService: UserService
+
+ @BeforeEach
+ fun setUp() {
+ userService = UserService(db)
+ }
+
+ @AfterEach
+ fun tearDown() {
+ db.clearTestData()
+ }
+
+ @Test
+ fun `should find user by id`() {
+ val user = userService.findById(1L)
+ assertNotNull(user)
+ assertEquals("Alice", user.name)
+ }
+
+ @Test
+ fun `should return non-empty list from findAll`() {
+ val users = userService.findAll()
+ assertNotNull(users)
+ assertTrue(users.isNotEmpty())
+ }
+
+ @Test
+ fun `should throw IllegalArgumentException for negative id`() {
+ assertFailsWith {
+ userService.findById(-1L)
+ }
+ }
+}
+```
+
+**Key points:**
+- JUnit 4 `@Before` / `@After` → JUnit 5 `@BeforeEach` / `@AfterEach`.
+- `@BeforeClass` static method → `@BeforeAll` + `@JvmStatic` inside `companion object`.
+- `@Test(expected = ...)` → `assertFailsWith { }` with reified generics.
+- Static assertions become kotlin.test top-level function imports.
+- Test method names use backtick syntax for readability.
+- `users.size() > 0` becomes idiomatic `users.isNotEmpty()`.
+- The `db` field uses `lateinit var` since it is initialized in `@BeforeAll`.
diff --git a/skills/kotlin-tooling-java-to-kotlin/references/frameworks/LOMBOK.md b/skills/kotlin-tooling-java-to-kotlin/references/frameworks/LOMBOK.md
new file mode 100644
index 0000000..c3546a0
--- /dev/null
+++ b/skills/kotlin-tooling-java-to-kotlin/references/frameworks/LOMBOK.md
@@ -0,0 +1,237 @@
+# Lombok Conversion Guide
+
+## When This Applies
+
+Detected when imports match `lombok.*`.
+
+## Core Rule
+
+**Remove ALL Lombok annotations entirely.** Do not convert Lombok to Lombok — convert
+to idiomatic Kotlin equivalents. Lombok has no place in Kotlin code.
+
+## Annotation Conversion Table
+
+| Lombok Annotation | Kotlin Equivalent |
+|---|---|
+| `@Getter` / `@Setter` | Kotlin properties (val/var) — automatic |
+| `@Data` | `data class` with primary constructor properties |
+| `@Value` (Lombok) | `data class` with `val` properties (immutable) |
+| `@Builder` | Default parameter values, or named arguments. For complex builders, use Kotlin builder DSL |
+| `@NoArgsConstructor` | Secondary no-arg constructor, or default values for all params |
+| `@AllArgsConstructor` | Primary constructor (Kotlin default) |
+| `@RequiredArgsConstructor` | Primary constructor with only required (non-default) params |
+| `@ToString` | `data class` auto-generates toString, or manual `override fun toString()` |
+| `@EqualsAndHashCode` | `data class` auto-generates, or manual `override fun equals/hashCode` |
+| `@Slf4j` / `@Log` / `@Log4j2` | Companion object with logger (see example below) |
+| `@Cleanup` | Kotlin's `.use {}` extension function |
+| `@SneakyThrows` | Kotlin has no checked exceptions — just remove it |
+| `@Synchronized` | Kotlin's `@Synchronized` annotation |
+| `@With` | `data class` `.copy()` method |
+| `@Accessors(chain = true)` | Kotlin's `apply {}` block |
+
+## Key Rules
+
+1. **@Slf4j** — Convert to a companion object with an explicit logger:
+```kotlin
+companion object {
+ private val log = LoggerFactory.getLogger(MyClass::class.java)
+}
+```
+
+2. **@Data with JPA entities** — Do NOT use `data class` for JPA entities. Use regular
+ `open class` with properties instead. Data classes break Hibernate proxies.
+
+3. **@Builder** — Prefer default parameter values. Only create an explicit builder
+ pattern if the Java code has complex builder logic beyond simple setters.
+
+4. **Lombok `val`** — Replace with Kotlin's `val` (they serve the same purpose).
+
+---
+
+## Example 1: @Data Class with @Builder
+
+### Java Input
+
+```java
+package com.acme.model;
+
+import lombok.Builder;
+import lombok.Data;
+
+/**
+ * Represents a customer order with shipping details.
+ */
+@Data
+@Builder
+public class Order {
+ private String orderId;
+ private String customerName;
+ private int quantity;
+ private boolean expedited;
+}
+```
+
+### Kotlin Output
+
+```kotlin
+package com.acme.model
+
+/**
+ * Represents a customer order with shipping details.
+ */
+data class Order(
+ val orderId: String?,
+ val customerName: String?,
+ val quantity: Int = 0,
+ val expedited: Boolean = false
+)
+```
+
+**What changed:**
+- `@Data` → `data class` with primary constructor properties.
+- `@Builder` → default parameter values. Callers use named arguments:
+ `Order(orderId = "123", customerName = "Alice", quantity = 2)`.
+- All Lombok imports removed.
+- Fields become `val` properties (immutable by default; use `var` only if mutation is
+ required by the original code).
+- Reference types are nullable (`String?`) because Java fields default to `null` unless
+ proven otherwise.
+
+---
+
+## Example 2: @Slf4j Annotated Service Class
+
+### Java Input
+
+```java
+package com.acme.service;
+
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+
+/**
+ * Service that processes incoming payment requests.
+ */
+@Slf4j
+@RequiredArgsConstructor
+public class PaymentService {
+
+ private final PaymentGateway gateway;
+ private final NotificationSender notifier;
+
+ /**
+ * Processes a payment for the given amount.
+ *
+ * @param amount the payment amount in cents
+ * @return true if the payment succeeded
+ */
+ public boolean processPayment(long amount) {
+ log.info("Processing payment of {} cents", amount);
+ try {
+ gateway.charge(amount);
+ notifier.sendConfirmation(amount);
+ log.info("Payment of {} cents succeeded", amount);
+ return true;
+ } catch (Exception e) {
+ log.error("Payment failed for amount {}", amount, e);
+ return false;
+ }
+ }
+}
+```
+
+### Kotlin Output
+
+```kotlin
+package com.acme.service
+
+import org.slf4j.LoggerFactory
+
+/**
+ * Service that processes incoming payment requests.
+ */
+open class PaymentService(
+ private val gateway: PaymentGateway,
+ private val notifier: NotificationSender
+) {
+
+ companion object {
+ private val log = LoggerFactory.getLogger(PaymentService::class.java)
+ }
+
+ /**
+ * Processes a payment for the given amount.
+ *
+ * @param amount the payment amount in cents
+ * @return true if the payment succeeded
+ */
+ fun processPayment(amount: Long): Boolean {
+ log.info("Processing payment of {} cents", amount)
+ return try {
+ gateway.charge(amount)
+ notifier.sendConfirmation(amount)
+ log.info("Payment of {} cents succeeded", amount)
+ true
+ } catch (e: Exception) {
+ log.error("Payment failed for amount {}", amount, e)
+ false
+ }
+ }
+}
+```
+
+**What changed:**
+- `@Slf4j` → companion object with `LoggerFactory.getLogger(...)`.
+- `@RequiredArgsConstructor` → primary constructor with `val` parameters.
+- Lombok imports replaced with `org.slf4j.LoggerFactory`.
+- `try/catch` used as an expression (idiomatic Kotlin).
+- Class is `open` because Java classes are implicitly open.
+
+---
+
+## Example 3: @Value (Lombok) Immutable Class
+
+### Java Input
+
+```java
+package com.acme.config;
+
+import lombok.Value;
+
+/**
+ * Immutable configuration for connecting to a database.
+ */
+@Value
+public class DatabaseConfig {
+ String host;
+ int port;
+ String databaseName;
+ boolean useSsl;
+}
+```
+
+### Kotlin Output
+
+```kotlin
+package com.acme.config
+
+/**
+ * Immutable configuration for connecting to a database.
+ */
+data class DatabaseConfig(
+ val host: String?,
+ val port: Int,
+ val databaseName: String?,
+ val useSsl: Boolean
+)
+```
+
+**What changed:**
+- `@Value` → `data class` with `val` properties (all immutable).
+- Lombok's `@Value` makes the class final, and Kotlin `data class` is also final by
+ default — so the semantics match.
+- All Lombok imports removed.
+- Auto-generated `equals()`, `hashCode()`, `toString()`, and `copy()` come from
+ `data class` for free.
+- Reference types are nullable (`String?`) since the original Java fields have no
+ nullability annotations.
diff --git a/skills/kotlin-tooling-java-to-kotlin/references/frameworks/MICRONAUT.md b/skills/kotlin-tooling-java-to-kotlin/references/frameworks/MICRONAUT.md
new file mode 100644
index 0000000..d46dbcd
--- /dev/null
+++ b/skills/kotlin-tooling-java-to-kotlin/references/frameworks/MICRONAUT.md
@@ -0,0 +1,120 @@
+# Micronaut Conversion Guide
+
+## When This Applies
+
+This guide applies when the Java source contains imports matching `io.micronaut.*`.
+This covers Micronaut HTTP, Micronaut Data, and Micronaut Security.
+
+## Key Rules
+
+### 1. Constructor injection is the default
+
+Micronaut uses compile-time dependency injection via constructor injection by default.
+This maps naturally to Kotlin's primary constructor. Remove `@Inject` when there is
+only one constructor — Micronaut discovers it automatically.
+
+### 2. Stereotype annotations
+
+`@Singleton`, `@Controller`, `@Client`, `@Repository` — preserve these exactly.
+No annotation site target is needed.
+
+### 3. @Value annotation
+
+Escape `$` in Kotlin to prevent string template interpretation:
+
+```kotlin
+@Value("\${config.key}") val configKey: String
+```
+
+### 4. @Inject field injection → constructor injection
+
+Replace `@Inject` on fields with constructor parameters in Kotlin's primary constructor.
+This eliminates `lateinit var` and makes dependencies immutable.
+
+### 5. AOP interceptors require open classes
+
+Classes using AOP annotations (`@Around`, `@Introduction`, `@Cacheable`) must be `open`
+in Kotlin because Micronaut generates subclass proxies for them at compile time.
+
+### 6. Bean factories
+
+`@Factory` classes and their `@Bean`-annotated methods should be `open` so Micronaut
+can manage their lifecycle through subclassing.
+
+### 7. @ConfigurationProperties
+
+Convert to a class with mutable properties. Use `lateinit var` for required `String`
+properties and `var` with defaults for primitives. The class must be `open`.
+
+---
+
+## Examples
+
+### Example 1: Micronaut Controller with Constructor Injection
+
+**Java:**
+
+```java
+package com.acme.web;
+
+import io.micronaut.http.annotation.Controller;
+import io.micronaut.http.annotation.Get;
+import io.micronaut.http.annotation.PathVariable;
+import jakarta.inject.Inject;
+
+@Controller("/api/orders")
+public class OrderController {
+
+ private final OrderService orderService;
+ private final InventoryClient inventoryClient;
+
+ @Inject
+ public OrderController(OrderService orderService, InventoryClient inventoryClient) {
+ this.orderService = orderService;
+ this.inventoryClient = inventoryClient;
+ }
+
+ @Get("/{id}")
+ public OrderDto getOrder(@PathVariable Long id) {
+ return orderService.findById(id);
+ }
+
+ @Get
+ public List listOrders() {
+ return orderService.findAll();
+ }
+}
+```
+
+**Kotlin:**
+
+```kotlin
+package com.acme.web
+
+import io.micronaut.http.annotation.Controller
+import io.micronaut.http.annotation.Get
+import io.micronaut.http.annotation.PathVariable
+
+@Controller("/api/orders")
+class OrderController(
+ private val orderService: OrderService,
+ private val inventoryClient: InventoryClient
+) {
+
+ @Get("/{id}")
+ fun getOrder(@PathVariable id: Long): OrderDto? {
+ return orderService.findById(id)
+ }
+
+ @Get
+ fun listOrders(): List {
+ return orderService.findAll()
+ }
+}
+```
+
+Key changes:
+- `@Inject` is removed — Micronaut auto-discovers the single constructor.
+- The `jakarta.inject.Inject` import is removed because it is no longer referenced.
+- Constructor parameters become `private val` in the primary constructor.
+- Return type `OrderDto` becomes `OrderDto?` where the service may return null.
diff --git a/skills/kotlin-tooling-java-to-kotlin/references/frameworks/MOCKITO.md b/skills/kotlin-tooling-java-to-kotlin/references/frameworks/MOCKITO.md
new file mode 100644
index 0000000..337f7e3
--- /dev/null
+++ b/skills/kotlin-tooling-java-to-kotlin/references/frameworks/MOCKITO.md
@@ -0,0 +1,253 @@
+# Mockito to MockK Conversion Guide
+
+## When This Applies
+
+Detected when imports match `org.mockito.*`.
+
+While Mockito works in Kotlin, MockK is the idiomatic Kotlin mocking library. Convert
+to MockK when the project already uses MockK or is doing a full Kotlin migration. If
+the project wants to keep Mockito, convert only the Java syntax to Kotlin syntax using
+the `mockito-kotlin` helper library.
+
+## Key Rules
+
+### 1. MockK conversion table
+
+| Mockito | MockK |
+|---|---|
+| `Mockito.mock(Foo.class)` | `mockk()` |
+| `@Mock Foo foo` | `@MockK lateinit var foo: Foo` (with `@ExtendWith(MockKExtension::class)`) |
+| `when(foo.bar()).thenReturn(x)` | `every { foo.bar() } returns x` |
+| `when(foo.bar()).thenThrow(e)` | `every { foo.bar() } throws e` |
+| `when(foo.bar()).thenAnswer { }` | `every { foo.bar() } answers { }` |
+| `doNothing().when(foo).bar()` | `justRun { foo.bar() }` |
+| `verify(foo).bar()` | `verify { foo.bar() }` |
+| `verify(foo, times(2)).bar()` | `verify(exactly = 2) { foo.bar() }` |
+| `verify(foo, never()).bar()` | `verify(exactly = 0) { foo.bar() }` |
+| `ArgumentCaptor` | `slot()` and `capture(slot)` |
+| `any()` | `any()` |
+| `eq(x)` | `eq(x)` (often not needed — MockK matches exact values by default) |
+| `Mockito.spy(obj)` | `spyk(obj)` |
+| `@InjectMocks` | No direct equivalent — use constructor injection |
+| `verifyNoMoreInteractions(foo)` | `confirmVerified(foo)` |
+
+### 2. Coroutine support in MockK
+
+For suspending functions, use `coEvery` and `coVerify` instead of `every` and `verify`:
+```kotlin
+coEvery { foo.suspendBar() } returns x
+coVerify { foo.suspendBar() }
+```
+
+### 3. Keeping Mockito (syntax-only conversion)
+
+If keeping Mockito, use the `mockito-kotlin` library (`org.mockito.kotlin`) for
+Kotlin-friendly wrappers:
+- `mock()` instead of `Mockito.mock(Foo::class.java)` — uses reified generics.
+- `whenever(foo.bar())` instead of `` Mockito.`when`(foo.bar()) `` — avoids backtick-
+ escaping `when` (it is a Kotlin keyword).
+- `argumentCaptor()` — type-safe captor via reified generics.
+- `any()` — properly handles Kotlin's non-null types.
+
+### 4. Relaxed mocks
+
+MockK supports relaxed mocks that return default values without explicit stubbing:
+`mockk(relaxed = true)`. This has no direct Mockito equivalent (Mockito's
+`RETURNS_DEFAULTS` is the closest).
+
+---
+
+## Example 1: Converting to MockK
+
+### Java Input
+
+```java
+package com.acme.service;
+
+import static org.junit.Assert.assertEquals;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+import static org.mockito.ArgumentMatchers.anyLong;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.ArgumentCaptor;
+
+/**
+ * Tests for OrderService using Mockito mocks.
+ */
+public class OrderServiceTest {
+
+ private UserRepository userRepository;
+ private OrderRepository orderRepository;
+ private OrderService orderService;
+
+ @Before
+ public void setUp() {
+ userRepository = mock(UserRepository.class);
+ orderRepository = mock(OrderRepository.class);
+ orderService = new OrderService(userRepository, orderRepository);
+ }
+
+ @Test
+ public void testCreateOrderForUser() {
+ User user = new User(1L, "Alice");
+ when(userRepository.findById(1L)).thenReturn(user);
+
+ orderService.createOrder(1L, "ITEM-100");
+
+ ArgumentCaptor captor = ArgumentCaptor.forClass(Order.class);
+ verify(orderRepository).save(captor.capture());
+ assertEquals("ITEM-100", captor.getValue().getItemCode());
+ assertEquals(1L, captor.getValue().getUserId());
+ }
+
+ @Test
+ public void testGetOrderCount() {
+ when(orderRepository.countByUserId(anyLong())).thenReturn(5);
+
+ int count = orderService.getOrderCount(1L);
+
+ assertEquals(5, count);
+ verify(orderRepository).countByUserId(1L);
+ }
+}
+```
+
+### Kotlin Output (MockK)
+
+```kotlin
+package com.acme.service
+
+import io.mockk.every
+import io.mockk.mockk
+import io.mockk.slot
+import io.mockk.verify
+import org.junit.jupiter.api.BeforeEach
+import org.junit.jupiter.api.Test
+import kotlin.test.assertEquals
+
+/**
+ * Tests for OrderService using MockK mocks.
+ */
+class OrderServiceTest {
+
+ private val userRepository = mockk()
+ private val orderRepository = mockk()
+ private val orderService = OrderService(userRepository, orderRepository)
+
+ @Test
+ fun `should create order for user`() {
+ val user = User(1L, "Alice")
+ every { userRepository.findById(1L) } returns user
+ every { orderRepository.save(any()) } returns Unit
+
+ orderService.createOrder(1L, "ITEM-100")
+
+ val orderSlot = slot()
+ verify { orderRepository.save(capture(orderSlot)) }
+ assertEquals("ITEM-100", orderSlot.captured.itemCode)
+ assertEquals(1L, orderSlot.captured.userId)
+ }
+
+ @Test
+ fun `should return order count`() {
+ every { orderRepository.countByUserId(any()) } returns 5
+
+ val count = orderService.getOrderCount(1L)
+
+ assertEquals(5, count)
+ verify { orderRepository.countByUserId(1L) }
+ }
+}
+```
+
+**Key points:**
+- `mock(Foo.class)` → `mockk()` using reified generics.
+- `@Before` setUp is eliminated — mocks are initialized inline with property
+ declarations. This works because MockK mocks do not require a runner.
+- `when(...).thenReturn(...)` → `every { ... } returns ...`.
+- `ArgumentCaptor` → `slot()` with `capture(slot)`, accessed via `slot.captured`.
+- `anyLong()` → `any()` (MockK's `any()` handles all types).
+- `verify(foo).bar()` → `verify { foo.bar() }`.
+
+---
+
+## Example 2: Keeping Mockito (mockito-kotlin syntax)
+
+### Java Input
+
+```java
+package com.acme.service;
+
+import static org.junit.Assert.assertEquals;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import org.junit.Before;
+import org.junit.Test;
+
+/**
+ * Tests for PricingService using Mockito.
+ */
+public class PricingServiceTest {
+
+ private PriceRepository priceRepository;
+ private PricingService pricingService;
+
+ @Before
+ public void setUp() {
+ priceRepository = mock(PriceRepository.class);
+ pricingService = new PricingService(priceRepository);
+ }
+
+ @Test
+ public void testGetPrice() {
+ when(priceRepository.findPriceByItemCode("ITEM-1")).thenReturn(9.99);
+ double price = pricingService.getPrice("ITEM-1");
+ assertEquals(9.99, price, 0.001);
+ verify(priceRepository).findPriceByItemCode("ITEM-1");
+ }
+}
+```
+
+### Kotlin Output (mockito-kotlin)
+
+```kotlin
+package com.acme.service
+
+import org.junit.jupiter.api.BeforeEach
+import org.junit.jupiter.api.Test
+import org.mockito.kotlin.mock
+import org.mockito.kotlin.verify
+import org.mockito.kotlin.whenever
+import kotlin.test.assertEquals
+
+/**
+ * Tests for PricingService using Mockito.
+ */
+class PricingServiceTest {
+
+ private val priceRepository = mock()
+ private val pricingService = PricingService(priceRepository)
+
+ @Test
+ fun `should return price for item`() {
+ whenever(priceRepository.findPriceByItemCode("ITEM-1")).thenReturn(9.99)
+
+ val price = pricingService.getPrice("ITEM-1")
+
+ assertEquals(9.99, price, 0.001)
+ verify(priceRepository).findPriceByItemCode("ITEM-1")
+ }
+}
+```
+
+**Key points:**
+- `mock(Foo.class)` → `mock()` from `org.mockito.kotlin` (reified generics).
+- `when(...)` → `whenever(...)` to avoid backtick-escaping the `when` keyword.
+- `verify` stays the same — `org.mockito.kotlin.verify` wraps Mockito's verify.
+- The `setUp` method is eliminated — mocks are initialized inline.
+- `assertEquals` with a delta parameter works the same way from kotlin.test.
diff --git a/skills/kotlin-tooling-java-to-kotlin/references/frameworks/QUARKUS.md b/skills/kotlin-tooling-java-to-kotlin/references/frameworks/QUARKUS.md
new file mode 100644
index 0000000..453f157
--- /dev/null
+++ b/skills/kotlin-tooling-java-to-kotlin/references/frameworks/QUARKUS.md
@@ -0,0 +1,138 @@
+# Quarkus Conversion Guide
+
+## When This Applies
+
+This guide applies when the Java source contains imports matching `io.quarkus.*`,
+`javax.enterprise.*`, or `jakarta.enterprise.*`. This covers Quarkus REST, Quarkus CDI,
+and Panache ORM.
+
+## Key Rules
+
+### 1. CDI beans need a no-arg constructor
+
+The CDI specification requires beans to have a no-arg constructor (package-private or
+public). In Kotlin, satisfy this by giving all constructor parameters default values,
+or by adding a secondary no-arg constructor.
+
+### 2. Scope annotations
+
+`@ApplicationScoped`, `@RequestScoped`, `@Dependent` — preserve these exactly.
+Beans with these scopes must have a no-arg constructor accessible to CDI.
+
+### 3. @Inject field injection → constructor injection
+
+Replace `@Inject` on fields with an `@Inject`-annotated primary constructor in Kotlin.
+CDI requires the `@Inject` annotation on the constructor when multiple constructors
+exist. With a single constructor, Quarkus discovers it automatically.
+
+### 4. REST endpoint annotations
+
+`@Path`, `@GET`, `@POST`, `@PUT`, `@DELETE`, `@Produces`, `@Consumes` — preserve
+these exactly. No annotation site target is needed.
+
+### 5. Panache entities
+
+Panache entities must remain `open` — do NOT use `data class`. Extend `PanacheEntity`
+(auto-generated Long ID) or `PanacheEntityBase` (custom ID type). Keep fields as
+`open` mutable properties because Panache enhances field access at build time.
+
+### 6. @ConfigProperty
+
+Use on constructor parameters with a default value to satisfy CDI's no-arg
+constructor requirement:
+
+```kotlin
+@ConfigProperty(name = "app.greeting") val greeting: String = ""
+```
+
+---
+
+## Examples
+
+### Example 1: REST Resource with CDI Injection
+
+**Java:**
+
+```java
+package com.acme.web;
+
+import jakarta.enterprise.context.ApplicationScoped;
+import jakarta.inject.Inject;
+import jakarta.ws.rs.GET;
+import jakarta.ws.rs.Path;
+import jakarta.ws.rs.PathParam;
+import jakarta.ws.rs.Produces;
+import jakarta.ws.rs.core.MediaType;
+import java.util.List;
+
+@Path("/api/products")
+@ApplicationScoped
+@Produces(MediaType.APPLICATION_JSON)
+public class ProductResource {
+
+ @Inject
+ ProductService productService;
+
+ @Inject
+ PricingService pricingService;
+
+ @GET
+ public List listProducts() {
+ return productService.findAll();
+ }
+
+ @GET
+ @Path("/{id}")
+ public ProductDto getProduct(@PathParam("id") Long id) {
+ return productService.findById(id);
+ }
+}
+```
+
+**Kotlin:**
+
+```kotlin
+package com.acme.web
+
+import jakarta.enterprise.context.ApplicationScoped
+import jakarta.inject.Inject
+import jakarta.ws.rs.GET
+import jakarta.ws.rs.Path
+import jakarta.ws.rs.PathParam
+import jakarta.ws.rs.Produces
+import jakarta.ws.rs.core.MediaType
+
+@Path("/api/products")
+@ApplicationScoped
+@Produces(MediaType.APPLICATION_JSON)
+class ProductResource @Inject constructor(
+ private val productService: ProductService,
+ private val pricingService: PricingService
+) {
+
+ // No-arg constructor required by CDI — default values satisfy this
+ constructor() : this(
+ productService = ProductService(),
+ pricingService = PricingService()
+ )
+
+ @GET
+ fun listProducts(): List {
+ return productService.findAll()
+ }
+
+ @GET
+ @Path("/{id}")
+ fun getProduct(@PathParam("id") id: Long): ProductDto? {
+ return productService.findById(id)
+ }
+}
+```
+
+Key changes:
+- `@Inject` field injection is replaced by an `@Inject`-annotated primary constructor.
+- A secondary no-arg constructor is added to satisfy the CDI specification. In practice,
+ CDI will use the `@Inject` constructor — the no-arg constructor exists only to pass
+ validation.
+- Constructor parameters become `private val` in the primary constructor.
+- Return type `ProductDto` becomes `ProductDto?` where the service may return null.
diff --git a/skills/kotlin-tooling-java-to-kotlin/references/frameworks/RETROFIT.md b/skills/kotlin-tooling-java-to-kotlin/references/frameworks/RETROFIT.md
new file mode 100644
index 0000000..9039126
--- /dev/null
+++ b/skills/kotlin-tooling-java-to-kotlin/references/frameworks/RETROFIT.md
@@ -0,0 +1,151 @@
+# Retrofit / OkHttp Conversion Guide
+
+## When This Applies
+
+Detected when imports match `retrofit2.*` or `okhttp3.*`.
+
+## Key Rules
+
+### 1. Interface declarations
+
+Retrofit service interfaces convert directly — Kotlin interfaces are structurally
+identical to Java interfaces for this purpose.
+
+### 2. Call\ to suspend functions
+
+Replace `Call` return types with `suspend fun` returning `T` directly. This requires
+the Retrofit coroutine adapter (built-in since Retrofit 2.6.0). The `Callback`
+async pattern is eliminated entirely.
+
+### 3. Annotation preservation
+
+All Retrofit annotations transfer directly with no changes:
+- HTTP method annotations: `@GET`, `@POST`, `@PUT`, `@DELETE`, `@PATCH`, `@HTTP`
+- Header annotations: `@Headers`, `@Header`, `@HeaderMap`
+- Parameter annotations: `@Path`, `@Query`, `@QueryMap`, `@Body`, `@Field`,
+ `@FieldMap`, `@Part`, `@PartMap`
+- `@FormUrlEncoded`, `@Multipart`, `@Streaming`
+
+### 4. Response\ handling
+
+For endpoints where HTTP status codes matter, keep `Response` as the return type
+with `suspend fun`. For simple cases where only the body is needed, return `T` directly
+and let Retrofit throw on non-2xx responses.
+
+### 5. OkHttpClient.Builder
+
+Java builder chains convert directly. Use `.apply {}` or `.also {}` for grouping
+related configuration:
+
+```kotlin
+val client = OkHttpClient.Builder().apply {
+ connectTimeout(30, TimeUnit.SECONDS)
+ readTimeout(30, TimeUnit.SECONDS)
+ addInterceptor(loggingInterceptor)
+}.build()
+```
+
+### 6. Interceptor SAM conversion
+
+Java `Interceptor` anonymous classes become Kotlin SAM lambdas:
+`Interceptor { chain -> chain.proceed(chain.request()) }`
+
+### 7. Request/Response body handling
+
+`RequestBody.create(mediaType, content)` → `content.toRequestBody(mediaType)` when
+using the `okhttp3-kotlin-extensions` artifact (or `okhttp-bom` with Kotlin extensions).
+
+---
+
+## Example: Retrofit Interface with Coroutine Support
+
+### Java Input
+
+```java
+package com.acme.api;
+
+import java.util.List;
+import retrofit2.Call;
+import retrofit2.Callback;
+import retrofit2.Response;
+import retrofit2.http.Body;
+import retrofit2.http.DELETE;
+import retrofit2.http.GET;
+import retrofit2.http.Headers;
+import retrofit2.http.PATCH;
+import retrofit2.http.POST;
+import retrofit2.http.Path;
+import retrofit2.http.Query;
+
+/**
+ * Retrofit service interface for the Users API.
+ */
+public interface UserApi {
+
+ @GET("users")
+ Call> getUsers(@Query("page") int page, @Query("limit") int limit);
+
+ @GET("users/{id}")
+ Call getUserById(@Path("id") long id);
+
+ @POST("users")
+ @Headers("Content-Type: application/json")
+ Call createUser(@Body CreateUserRequest request);
+
+ @PATCH("users/{id}")
+ Call updateUser(@Path("id") long id, @Body UpdateUserRequest request);
+
+ @DELETE("users/{id}")
+ Call deleteUser(@Path("id") long id);
+}
+```
+
+### Kotlin Output
+
+```kotlin
+package com.acme.api
+
+import retrofit2.Response
+import retrofit2.http.Body
+import retrofit2.http.DELETE
+import retrofit2.http.GET
+import retrofit2.http.Headers
+import retrofit2.http.PATCH
+import retrofit2.http.POST
+import retrofit2.http.Path
+import retrofit2.http.Query
+
+/**
+ * Retrofit service interface for the Users API.
+ */
+interface UserApi {
+
+ @GET("users")
+ suspend fun getUsers(@Query("page") page: Int, @Query("limit") limit: Int): List
+
+ @GET("users/{id}")
+ suspend fun getUserById(@Path("id") id: Long): UserDto
+
+ @POST("users")
+ @Headers("Content-Type: application/json")
+ suspend fun createUser(@Body request: CreateUserRequest): UserDto
+
+ @PATCH("users/{id}")
+ suspend fun updateUser(@Path("id") id: Long, @Body request: UpdateUserRequest): UserDto
+
+ @DELETE("users/{id}")
+ suspend fun deleteUser(@Path("id") id: Long): Response
+}
+```
+
+**Key points:**
+- `Call` is removed — each method becomes a `suspend fun` returning `T` directly.
+ Retrofit 2.6.0+ supports this natively without an additional adapter.
+- `Call` becomes `Response`. `Unit` is Kotlin's equivalent of `Void`.
+ `Response` is used here to allow checking the HTTP status code on delete.
+- `Call` and `Callback` imports are removed since they are no longer referenced.
+- All HTTP method and parameter annotations (`@GET`, `@POST`, `@Path`, `@Query`,
+ `@Body`, `@Headers`, etc.) are preserved exactly as-is.
+- Java `int` → Kotlin `Int`, Java `long` → Kotlin `Long`.
+- The `public` modifier on the interface is removed — Kotlin's default visibility
+ is public.
diff --git a/skills/kotlin-tooling-java-to-kotlin/references/frameworks/RXJAVA.md b/skills/kotlin-tooling-java-to-kotlin/references/frameworks/RXJAVA.md
new file mode 100644
index 0000000..bb9e5da
--- /dev/null
+++ b/skills/kotlin-tooling-java-to-kotlin/references/frameworks/RXJAVA.md
@@ -0,0 +1,181 @@
+# RxJava to Coroutines/Flow Conversion Guide
+
+## When This Applies
+
+Detected when imports match `io.reactivex.*` or `rx.*`. This is a significant paradigm
+shift — RxJava reactive types map to Kotlin coroutines and Flow.
+
+## Key Rules
+
+### 1. Dependency setup
+
+Add `kotlinx-coroutines-core` and `kotlinx-coroutines-rx3` (or `kotlinx-coroutines-rx2`)
+as dependencies if performing a gradual migration. The bridge library provides extension
+functions like `asFlow()` and `asObservable()` for interop at module boundaries.
+
+### 2. Type mapping
+
+| RxJava | Kotlin |
+|---|---|
+| `Observable` | `Flow` |
+| `Flowable` | `Flow` (backpressure is built-in) |
+| `Single` | `suspend fun`: T |
+| `Maybe` | `suspend fun`: T? |
+| `Completable` | `suspend fun` returning `Unit` |
+| `Disposable` | `Job` (from coroutines) |
+| `CompositeDisposable` | `CoroutineScope` (structured concurrency) |
+
+### 3. Operator mapping
+
+| RxJava | Kotlin Flow |
+|---|---|
+| `subscribeOn(Schedulers.io())` | `flowOn(Dispatchers.IO)` |
+| `observeOn(AndroidSchedulers.mainThread())` | `flowOn(Dispatchers.Main)` or collect on Main |
+| `flatMap` | `flatMapMerge` or `flatMapConcat` |
+| `map` | `map` (same) |
+| `filter` | `filter` (same) |
+| `zip` | `combine` or `zip` |
+| `merge` | `merge` |
+| `concat` | `flatMapConcat` |
+| `onErrorReturn` | `catch { emit(default) }` |
+| `doOnNext` | `onEach` |
+| `subscribe()` | `collect {}` in a coroutine scope |
+
+### 4. Error handling
+
+RxJava's `onError` callback maps to Flow's `catch` operator or a try-catch block
+wrapping the `collect` call. In suspend functions (replacing `Single`/`Completable`),
+use standard try-catch.
+
+### 5. Backpressure
+
+Flow has built-in backpressure via suspension. There is no need for a separate
+`Flowable` type — all `Flow` instances support backpressure by default.
+
+### 6. Threading
+
+`flowOn` changes the upstream dispatcher (analogous to `subscribeOn`). Collection
+always happens on the caller's dispatcher. To collect on a specific dispatcher,
+launch the collecting coroutine in the desired scope.
+
+### 7. Lifecycle and cancellation
+
+RxJava's `Disposable` / `CompositeDisposable` pattern is replaced by structured
+concurrency. Cancelling a `CoroutineScope` cancels all child coroutines and flow
+collections automatically.
+
+---
+
+## Example: Converting an Observable Chain to Flow
+
+### Java Input
+
+```java
+package com.acme.data;
+
+import io.reactivex.rxjava3.core.Observable;
+import io.reactivex.rxjava3.schedulers.Schedulers;
+import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers;
+import io.reactivex.rxjava3.disposables.CompositeDisposable;
+
+/**
+ * Repository that streams user data from a remote source.
+ */
+public class UserRepository {
+
+ private final UserApi api;
+ private final CompositeDisposable disposables = new CompositeDisposable();
+
+ public UserRepository(UserApi api) {
+ this.api = api;
+ }
+
+ public Observable> getActiveUsers() {
+ return api.getAllUsers()
+ .subscribeOn(Schedulers.io())
+ .map(users -> filterActive(users))
+ .doOnNext(users -> logCount(users))
+ .onErrorReturn(throwable -> Collections.emptyList());
+ }
+
+ public void observeUsers(UserCallback callback) {
+ disposables.add(
+ getActiveUsers()
+ .observeOn(AndroidSchedulers.mainThread())
+ .subscribe(
+ users -> callback.onUsers(users),
+ error -> callback.onError(error)
+ )
+ );
+ }
+
+ public void clear() {
+ disposables.clear();
+ }
+
+ private List filterActive(List users) {
+ return users.stream().filter(User::isActive).collect(Collectors.toList());
+ }
+
+ private void logCount(List users) {
+ System.out.println("Active users: " + users.size());
+ }
+}
+```
+
+### Kotlin Output
+
+```kotlin
+package com.acme.data
+
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.SupervisorJob
+import kotlinx.coroutines.cancel
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.catch
+import kotlinx.coroutines.flow.flowOn
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.onEach
+import kotlinx.coroutines.launch
+
+/**
+ * Repository that streams user data from a remote source.
+ */
+class UserRepository(
+ private val api: UserApi
+) {
+ private val scope = CoroutineScope(SupervisorJob() + Dispatchers.Main)
+
+ fun getActiveUsers(): Flow> =
+ api.getAllUsers()
+ .map { users -> users.filter { it.isActive } }
+ .onEach { users -> println("Active users: ${users.size}") }
+ .catch { emit(emptyList()) }
+ .flowOn(Dispatchers.IO)
+
+ fun observeUsers(callback: UserCallback) {
+ scope.launch {
+ getActiveUsers().collect { users ->
+ callback.onUsers(users)
+ }
+ }
+ }
+
+ fun clear() {
+ scope.cancel()
+ }
+}
+```
+
+**Key points:**
+- `Observable>` becomes `Flow>`.
+- `subscribeOn(Schedulers.io())` becomes `flowOn(Dispatchers.IO)` at the end of the
+ chain (it affects all upstream operators).
+- `CompositeDisposable` is replaced by a `CoroutineScope` with `SupervisorJob`.
+ Calling `scope.cancel()` cancels all active collections.
+- `doOnNext` becomes `onEach`.
+- `onErrorReturn` becomes `catch { emit(emptyList()) }`.
+- `observeOn(AndroidSchedulers.mainThread())` is unnecessary because `scope` already
+ uses `Dispatchers.Main`, and `collect` runs on the collector's dispatcher.
+- Java streams (`filter` + `collect`) become Kotlin's `filter` directly on the list.
diff --git a/skills/kotlin-tooling-java-to-kotlin/references/frameworks/SPRING.md b/skills/kotlin-tooling-java-to-kotlin/references/frameworks/SPRING.md
new file mode 100644
index 0000000..ae07434
--- /dev/null
+++ b/skills/kotlin-tooling-java-to-kotlin/references/frameworks/SPRING.md
@@ -0,0 +1,238 @@
+# Spring Framework Conversion Guide
+
+## When This Applies
+
+This guide applies when the Java source contains imports matching `org.springframework.*`.
+This covers Spring Boot, Spring MVC, Spring Data, and Spring Security.
+
+## Key Rules
+
+### 1. SpringApplication.run — spread CLI args
+
+In Kotlin, `String[]` varargs must be spread with the `*` operator.
+
+- Java: `SpringApplication.run(App.class, args);`
+- Kotlin: `SpringApplication.run(App::class.java, *args)`
+
+### 2. Constructor injection over @Autowired
+
+Kotlin's primary constructor makes constructor injection natural. When a class has a
+single constructor, Spring auto-discovers it — remove `@Autowired`.
+
+### 3. Stereotype annotations
+
+`@Component`, `@Service`, `@RestController`, and `@Repository` target the class.
+Preserve these annotations exactly. No annotation site target is needed.
+
+### 4. @Value annotation
+
+Use `@Value` on constructor parameters. Escape `$` in SpEL expressions to prevent
+Kotlin string template interpretation:
+
+```kotlin
+@Value("\${app.name}") val appName: String
+```
+
+### 5. @ConfigurationProperties
+
+Convert to a `data class` only if the properties are immutable. For mutable
+configuration, use a regular class with `lateinit var`.
+
+### 6. Spring Data repositories
+
+Interface declarations convert directly. Replace `Optional` return types with
+nullable `T?` in Kotlin for idiomatic usage.
+
+### 7. @RequestMapping / @GetMapping / @PostMapping etc.
+
+Preserve exactly. Where Java uses array initializer syntax for annotation parameters,
+use `arrayOf()` in Kotlin.
+
+### 8. @Transactional
+
+Preserve exactly. The class must remain `open` because Spring creates proxies via
+subclassing. Do not make `@Transactional` classes `final`.
+
+### 9. @Bean methods in @Configuration classes
+
+`@Bean` methods must be `open` so that Spring can override them in CGLIB proxies.
+Alternatively, apply the `allopen` compiler plugin with a Spring preset, which makes
+annotated classes and their members open automatically.
+
+---
+
+## Examples
+
+### Example 1: Spring Boot Application Main Class
+
+**Java:**
+
+```java
+package com.acme;
+
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+
+@SpringBootApplication
+public class Application {
+ public static void main(String[] args) {
+ SpringApplication.run(Application.class, args);
+ }
+}
+```
+
+**Kotlin:**
+
+```kotlin
+package com.acme
+
+import org.springframework.boot.SpringApplication
+import org.springframework.boot.autoconfigure.SpringBootApplication
+import org.springframework.boot.runApplication
+
+@SpringBootApplication
+open class Application
+
+fun main(args: Array) {
+ runApplication(*args)
+}
+```
+
+Key changes:
+- `main` becomes a top-level function (no companion object needed).
+- `runApplication` is a Spring Boot Kotlin extension that replaces
+ `SpringApplication.run(T::class.java, *args)`.
+- The `*args` spread operator is required for the varargs parameter.
+
+---
+
+### Example 2: REST Controller with Constructor Injection
+
+**Java:**
+
+```java
+package com.acme.web;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+@RestController
+@RequestMapping("/api/users")
+public class UserController {
+
+ private final UserService userService;
+
+ @Autowired
+ public UserController(UserService userService) {
+ this.userService = userService;
+ }
+
+ @GetMapping("/{id}")
+ public UserDto getUser(@PathVariable Long id) {
+ return userService.findById(id);
+ }
+
+ @GetMapping
+ public List getAllUsers() {
+ return userService.findAll();
+ }
+}
+```
+
+**Kotlin:**
+
+```kotlin
+package com.acme.web
+
+import org.springframework.web.bind.annotation.GetMapping
+import org.springframework.web.bind.annotation.PathVariable
+import org.springframework.web.bind.annotation.RequestMapping
+import org.springframework.web.bind.annotation.RestController
+
+@RestController
+@RequestMapping("/api/users")
+class UserController(
+ private val userService: UserService
+) {
+
+ @GetMapping("/{id}")
+ fun getUser(@PathVariable id: Long): UserDto? {
+ return userService.findById(id)
+ }
+
+ @GetMapping
+ fun getAllUsers(): List {
+ return userService.findAll()
+ }
+}
+```
+
+Key changes:
+- `@Autowired` is removed — Spring auto-discovers the single constructor.
+- The `Autowired` import is removed because it is no longer referenced.
+- Constructor parameter becomes a `private val` in the primary constructor.
+- Return type `UserDto` becomes `UserDto?` where the service may return null.
+
+---
+
+### Example 3: @ConfigurationProperties Class
+
+**Java:**
+
+```java
+package com.acme.config;
+
+import org.springframework.boot.context.properties.ConfigurationProperties;
+import org.springframework.stereotype.Component;
+
+@Component
+@ConfigurationProperties(prefix = "app.mail")
+public class MailProperties {
+
+ private String host;
+ private int port = 587;
+ private String username;
+ private String password;
+
+ public String getHost() { return host; }
+ public void setHost(String host) { this.host = host; }
+
+ public int getPort() { return port; }
+ public void setPort(int port) { this.port = port; }
+
+ public String getUsername() { return username; }
+ public void setUsername(String username) { this.username = username; }
+
+ public String getPassword() { return password; }
+ public void setPassword(String password) { this.password = password; }
+}
+```
+
+**Kotlin (mutable config with lateinit var):**
+
+```kotlin
+package com.acme.config
+
+import org.springframework.boot.context.properties.ConfigurationProperties
+import org.springframework.stereotype.Component
+
+@Component
+@ConfigurationProperties(prefix = "app.mail")
+open class MailProperties {
+ lateinit var host: String
+ var port: Int = 587
+ lateinit var username: String
+ lateinit var password: String
+}
+```
+
+Key changes:
+- Getters and setters are replaced by Kotlin properties.
+- `lateinit var` is used for required `String` properties that Spring populates
+ after construction.
+- `port` keeps its default value and uses a regular `var` (`lateinit` does not
+ support primitive types).
+- The class is `open` so that Spring can create a CGLIB proxy for it.