[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"skill-kotlin-kotlin-backend-jpa-entity-mapping":3,"mdc--vefc6k-key":38,"related-org-kotlin-kotlin-backend-jpa-entity-mapping":1350,"related-repo-kotlin-kotlin-backend-jpa-entity-mapping":1437},{"slug":4,"name":4,"fn":5,"description":6,"org":7,"tags":11,"stars":26,"repoUrl":27,"updatedAt":28,"license":29,"forks":30,"topics":31,"repo":33,"sourceUrl":36,"mdContent":37},"kotlin-backend-jpa-entity-mapping","map JPA entities in Kotlin backend apps","Model Kotlin persistence code correctly for Spring Data JPA and Hibernate. Covers entity design, identity and equality, uniqueness constraints, relationships, fetch plans, and common ORM (Object-Relational Mapping) traps  specific to Kotlin. Use when creating or reviewing JPA (Java Persistence API)  entities, diagnosing N+1 or LazyInitializationException, placing indexes and  uniqueness rules, or preventing Kotlin-specific bugs such as data class  entities and broken equals\u002FhashCode.\n",{"slug":8,"name":9,"logoUrl":10,"githubOrg":8},"kotlin","Kotlin","https:\u002F\u002Fpexgzepcugksgbtrxkhf.supabase.co\u002Fstorage\u002Fv1\u002Fobject\u002Fpublic\u002Forg-logos\u002Fkotlin.png",[12,14,17,20,23],{"name":9,"slug":8,"type":13},"tag",{"name":15,"slug":16,"type":13},"Backend","backend",{"name":18,"slug":19,"type":13},"Database","database",{"name":21,"slug":22,"type":13},"ORM","orm",{"name":24,"slug":25,"type":13},"Data Modeling","data-modeling",963,"https:\u002F\u002Fgithub.com\u002FKotlin\u002Fkotlin-agent-skills","2026-04-06T18:26:01.44065","Apache-2.0",35,[8,32],"skills",{"repoUrl":27,"stars":26,"forks":30,"topics":34,"description":35},[8,32],"A collection of AI agent skills useful for projects using Kotlin language","https:\u002F\u002Fgithub.com\u002FKotlin\u002Fkotlin-agent-skills\u002Ftree\u002FHEAD\u002Fskills\u002Fkotlin-backend-jpa-entity-mapping","---\nname: kotlin-backend-jpa-entity-mapping\ndescription: >\n  Model Kotlin persistence code correctly for Spring Data JPA and Hibernate.\n  Covers entity design, identity and equality, uniqueness constraints,\n  relationships, fetch plans, and common ORM (Object-Relational Mapping) traps \n  specific to Kotlin. Use when creating or reviewing JPA (Java Persistence API) \n  entities, diagnosing N+1 or LazyInitializationException, placing indexes and \n  uniqueness rules, or preventing Kotlin-specific bugs such as data class \n  entities and broken equals\u002FhashCode.\nlicense: Apache-2.0\nmetadata:\n  author: JetBrains\n  version: \"1.0.0\"\n---\n\n# JPA Entity Mapping for Kotlin\n\nKotlin's `data class` is natural for DTOs but dangerous for JPA entities. Hibernate relies on\nidentity semantics that `data class` breaks: `equals`\u002F`hashCode` over all fields corrupts\n`Set`\u002F`Map` membership after state changes, and auto-generated `copy()` creates detached\nduplicates of managed entities.\n\nThis skill teaches correct entity design, identity strategies, and uniqueness constraints\nfor Kotlin + Spring Data JPA projects.\n\n## Entity Design Rules\n\n- **Never use `data class` for JPA entities.** Use a regular `class`. Keep `data class` for DTOs.\n- Keep transport DTOs and persistence entities separate unless the project clearly uses a shared model.\n- Model required columns as non-null only when object construction and persistence lifecycle make it safe.\n- Use `lateinit` only when the project already accepts that tradeoff and the lifecycle is safe.\n- Verify `kotlin(\"plugin.jpa\")` or equivalent no-arg support when JPA entities exist.\n- Verify classes and members are compatible with proxying where needed.\n\n## Identity and Equality\n\n- Never accept all-field `equals`\u002F`hashCode` generated by `data class` on an entity.\n- Follow project conventions when they already define an identity strategy.\n- If no convention exists, use ID-based equality with a stable `hashCode`.\n- For DB-generated IDs, model the unsaved state with nullable `var id: Long? = null`\n  and a `protected set`; do not use `0L` as a sentinel value.\n- Be explicit about mutable fields and lazy associations when discussing equality.\n\n### Broken: `data class` Entity\n\n```kotlin\n\u002F\u002F WRONG: data class generates equals\u002FhashCode from ALL fields,\n\u002F\u002F and the generated ID uses a 0 sentinel instead of null\ndata class Order(\n    @Id @GeneratedValue val id: Long = 0,\n    var status: String,\n    var total: BigDecimal\n)\n\u002F\u002F BUG: order.status = \"SHIPPED\"; set.contains(order) → false (hash changed)\n\u002F\u002F BUG: Hibernate proxy.equals(entity) → false (proxy has lazy fields uninitialized)\n```\n\n### Correct: Regular Class with ID-Based Identity\n\n```kotlin\n@Entity\n@Table(name = \"orders\")\nclass Order(\n    @Column(nullable = false)\n    var status: String,\n\n    @Column(nullable = false)\n    var total: BigDecimal\n) {\n    @Id\n    @GeneratedValue(strategy = GenerationType.IDENTITY)\n    var id: Long? = null\n        protected set\n\n    override fun equals(other: Any?): Boolean {\n        if (this === other) return true\n        if (other !is Order) return false\n        return id != null && id == other.id\n    }\n\n    override fun hashCode(): Int = javaClass.hashCode()\n\n    \u002F\u002F toString must NOT reference lazy collections\n    override fun toString(): String = \"Order(id=$id, status=$status)\"\n}\n```\n\n**Key rules:**\n- `equals` compares by ID only — stable under dirty tracking and proxy unwrapping\n- `hashCode` returns class-based constant — avoids `Set`\u002F`Map` corruption after persist\n- `toString` excludes lazy-loaded relations — prevents `LazyInitializationException`\n- Constructor params are mutable entity fields; DB-generated `id` is nullable with a protected setter\n\n## Uniqueness Constraints\n\nWhen an API must be idempotent (e.g., \"reserve stock for order X\"), enforce uniqueness\nat both layers: database constraint for correctness, application check for clean errors.\n\n### Broken: No Duplicate Guard\n\n```kotlin\n@Service\nclass ReservationService(private val repo: ReservationRepository) {\n    @Transactional\n    fun createReservation(variantId: Long, orderId: String, qty: Int): Reservation {\n        \u002F\u002F BUG: no check — duplicates silently accumulate\n        return repo.save(Reservation(variantId = variantId, orderId = orderId, quantity = qty))\n    }\n}\n```\n\n### Correct: Database Constraint + Application Guard\n\n```kotlin\n@Entity\n@Table(\n    name = \"reservations\",\n    uniqueConstraints = [\n        UniqueConstraint(columnNames = [\"variant_id\", \"order_id\"])\n    ]\n)\nclass Reservation(\n    @Column(name = \"variant_id\", nullable = false)\n    val variantId: Long,\n\n    @Column(name = \"order_id\", nullable = false)\n    val orderId: String,\n\n    @Column(nullable = false)\n    var quantity: Int\n) {\n    @Id @GeneratedValue(strategy = GenerationType.IDENTITY)\n    var id: Long? = null\n        protected set\n}\n\ninterface ReservationRepository : JpaRepository\u003CReservation, Long> {\n    fun findByVariantIdAndOrderId(variantId: Long, orderId: String): Reservation?\n}\n\n@Service\nclass ReservationService(private val repo: ReservationRepository) {\n    @Transactional\n    fun createReservation(variantId: Long, orderId: String, qty: Int): Reservation {\n        repo.findByVariantIdAndOrderId(variantId, orderId)?.let {\n            throw IllegalStateException(\n                \"Reservation already exists for variant=$variantId, order=$orderId\"\n            )\n        }\n        return repo.save(Reservation(variantId = variantId, orderId = orderId, quantity = qty))\n    }\n}\n```\n\n**Key rules:**\n- Database constraint is mandatory — application checks alone have race conditions\n- Application check provides clean error messages — without it, users get raw `DataIntegrityViolationException`\n- Both layers together: application catches the common case, database catches the race\n- Spring Data derives `findByXAndY` queries automatically\n\n## Query and Fetch Rules\n\n- Diagnose N+1 by looking at actual query count or SQL logs, not by guessing from annotations.\n- Prefer targeted fetch solutions: `@EntityGraph`, `JOIN FETCH`, batch fetching, or DTO projection.\n- Be careful with collection fetch joins plus pagination — call out the tradeoff.\n- Use indexes and uniqueness constraints to support real query patterns.\n\n## Common ORM Traps\n\n- **Bidirectional associations:** maintain both sides in domain methods. Half-updated graphs cause subtle bugs.\n- **`orphanRemoval` vs cascade remove:** not interchangeable. Explain lifecycle semantics before choosing.\n- **Lazy load triggers:** `toString`, debug logging, JSON serialization, and IDE inspection can all trigger lazy loads.\n- **Bulk updates\u002Fdeletes:** bypass persistence context and lifecycle callbacks. Subsequent reads may be stale.\n- **Multiple bag fetches:** can cause Cartesian explosion. Verify the ORM can execute collection-heavy fetch plans safely.\n- **`Set` + mutable equality:** collection membership can break after entity state changes.\n- **`@Version`:** the clearest optimistic concurrency mechanism when concurrent updates matter.\n- **`open-in-view` disabled:** DTO mapping touching lazy fields must happen inside a transaction boundary.\n\n## Guardrails\n\n- Do not use `data class` for JPA entities.\n- Do not recommend `FetchType.EAGER` everywhere to silence lazy loading symptoms.\n- Do not expose entities directly through API responses by default.\n- Do not claim an N+1 fix without explaining how the fetch plan changes query behavior.\n",{"data":39,"body":43},{"name":4,"description":6,"license":29,"metadata":40},{"author":41,"version":42},"JetBrains","1.0.0",{"type":44,"children":45},"root",[46,55,116,121,128,207,213,292,306,399,405,623,631,697,703,708,714,783,789,1093,1100,1137,1143,1182,1188,1301,1307,1344],{"type":47,"tag":48,"props":49,"children":51},"element","h1",{"id":50},"jpa-entity-mapping-for-kotlin",[52],{"type":53,"value":54},"text","JPA Entity Mapping for Kotlin",{"type":47,"tag":56,"props":57,"children":58},"p",{},[59,61,68,70,75,77,83,85,91,93,99,100,106,108,114],{"type":53,"value":60},"Kotlin's ",{"type":47,"tag":62,"props":63,"children":65},"code",{"className":64},[],[66],{"type":53,"value":67},"data class",{"type":53,"value":69}," is natural for DTOs but dangerous for JPA entities. Hibernate relies on\nidentity semantics that ",{"type":47,"tag":62,"props":71,"children":73},{"className":72},[],[74],{"type":53,"value":67},{"type":53,"value":76}," breaks: ",{"type":47,"tag":62,"props":78,"children":80},{"className":79},[],[81],{"type":53,"value":82},"equals",{"type":53,"value":84},"\u002F",{"type":47,"tag":62,"props":86,"children":88},{"className":87},[],[89],{"type":53,"value":90},"hashCode",{"type":53,"value":92}," over all fields corrupts\n",{"type":47,"tag":62,"props":94,"children":96},{"className":95},[],[97],{"type":53,"value":98},"Set",{"type":53,"value":84},{"type":47,"tag":62,"props":101,"children":103},{"className":102},[],[104],{"type":53,"value":105},"Map",{"type":53,"value":107}," membership after state changes, and auto-generated ",{"type":47,"tag":62,"props":109,"children":111},{"className":110},[],[112],{"type":53,"value":113},"copy()",{"type":53,"value":115}," creates detached\nduplicates of managed entities.",{"type":47,"tag":56,"props":117,"children":118},{},[119],{"type":53,"value":120},"This skill teaches correct entity design, identity strategies, and uniqueness constraints\nfor Kotlin + Spring Data JPA projects.",{"type":47,"tag":122,"props":123,"children":125},"h2",{"id":124},"entity-design-rules",[126],{"type":53,"value":127},"Entity Design Rules",{"type":47,"tag":129,"props":130,"children":131},"ul",{},[132,166,171,176,189,202],{"type":47,"tag":133,"props":134,"children":135},"li",{},[136,149,151,157,159,164],{"type":47,"tag":137,"props":138,"children":139},"strong",{},[140,142,147],{"type":53,"value":141},"Never use ",{"type":47,"tag":62,"props":143,"children":145},{"className":144},[],[146],{"type":53,"value":67},{"type":53,"value":148}," for JPA entities.",{"type":53,"value":150}," Use a regular ",{"type":47,"tag":62,"props":152,"children":154},{"className":153},[],[155],{"type":53,"value":156},"class",{"type":53,"value":158},". Keep ",{"type":47,"tag":62,"props":160,"children":162},{"className":161},[],[163],{"type":53,"value":67},{"type":53,"value":165}," for DTOs.",{"type":47,"tag":133,"props":167,"children":168},{},[169],{"type":53,"value":170},"Keep transport DTOs and persistence entities separate unless the project clearly uses a shared model.",{"type":47,"tag":133,"props":172,"children":173},{},[174],{"type":53,"value":175},"Model required columns as non-null only when object construction and persistence lifecycle make it safe.",{"type":47,"tag":133,"props":177,"children":178},{},[179,181,187],{"type":53,"value":180},"Use ",{"type":47,"tag":62,"props":182,"children":184},{"className":183},[],[185],{"type":53,"value":186},"lateinit",{"type":53,"value":188}," only when the project already accepts that tradeoff and the lifecycle is safe.",{"type":47,"tag":133,"props":190,"children":191},{},[192,194,200],{"type":53,"value":193},"Verify ",{"type":47,"tag":62,"props":195,"children":197},{"className":196},[],[198],{"type":53,"value":199},"kotlin(\"plugin.jpa\")",{"type":53,"value":201}," or equivalent no-arg support when JPA entities exist.",{"type":47,"tag":133,"props":203,"children":204},{},[205],{"type":53,"value":206},"Verify classes and members are compatible with proxying where needed.",{"type":47,"tag":122,"props":208,"children":210},{"id":209},"identity-and-equality",[211],{"type":53,"value":212},"Identity and Equality",{"type":47,"tag":129,"props":214,"children":215},{},[216,241,246,258,287],{"type":47,"tag":133,"props":217,"children":218},{},[219,221,226,227,232,234,239],{"type":53,"value":220},"Never accept all-field ",{"type":47,"tag":62,"props":222,"children":224},{"className":223},[],[225],{"type":53,"value":82},{"type":53,"value":84},{"type":47,"tag":62,"props":228,"children":230},{"className":229},[],[231],{"type":53,"value":90},{"type":53,"value":233}," generated by ",{"type":47,"tag":62,"props":235,"children":237},{"className":236},[],[238],{"type":53,"value":67},{"type":53,"value":240}," on an entity.",{"type":47,"tag":133,"props":242,"children":243},{},[244],{"type":53,"value":245},"Follow project conventions when they already define an identity strategy.",{"type":47,"tag":133,"props":247,"children":248},{},[249,251,256],{"type":53,"value":250},"If no convention exists, use ID-based equality with a stable ",{"type":47,"tag":62,"props":252,"children":254},{"className":253},[],[255],{"type":53,"value":90},{"type":53,"value":257},".",{"type":47,"tag":133,"props":259,"children":260},{},[261,263,269,271,277,279,285],{"type":53,"value":262},"For DB-generated IDs, model the unsaved state with nullable ",{"type":47,"tag":62,"props":264,"children":266},{"className":265},[],[267],{"type":53,"value":268},"var id: Long? = null",{"type":53,"value":270},"\nand a ",{"type":47,"tag":62,"props":272,"children":274},{"className":273},[],[275],{"type":53,"value":276},"protected set",{"type":53,"value":278},"; do not use ",{"type":47,"tag":62,"props":280,"children":282},{"className":281},[],[283],{"type":53,"value":284},"0L",{"type":53,"value":286}," as a sentinel value.",{"type":47,"tag":133,"props":288,"children":289},{},[290],{"type":53,"value":291},"Be explicit about mutable fields and lazy associations when discussing equality.",{"type":47,"tag":293,"props":294,"children":296},"h3",{"id":295},"broken-data-class-entity",[297,299,304],{"type":53,"value":298},"Broken: ",{"type":47,"tag":62,"props":300,"children":302},{"className":301},[],[303],{"type":53,"value":67},{"type":53,"value":305}," Entity",{"type":47,"tag":307,"props":308,"children":312},"pre",{"className":309,"code":310,"language":8,"meta":311,"style":311},"language-kotlin shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","\u002F\u002F WRONG: data class generates equals\u002FhashCode from ALL fields,\n\u002F\u002F and the generated ID uses a 0 sentinel instead of null\ndata class Order(\n    @Id @GeneratedValue val id: Long = 0,\n    var status: String,\n    var total: BigDecimal\n)\n\u002F\u002F BUG: order.status = \"SHIPPED\"; set.contains(order) → false (hash changed)\n\u002F\u002F BUG: Hibernate proxy.equals(entity) → false (proxy has lazy fields uninitialized)\n","",[313],{"type":47,"tag":62,"props":314,"children":315},{"__ignoreMap":311},[316,327,336,345,354,363,372,381,390],{"type":47,"tag":317,"props":318,"children":321},"span",{"class":319,"line":320},"line",1,[322],{"type":47,"tag":317,"props":323,"children":324},{},[325],{"type":53,"value":326},"\u002F\u002F WRONG: data class generates equals\u002FhashCode from ALL fields,\n",{"type":47,"tag":317,"props":328,"children":330},{"class":319,"line":329},2,[331],{"type":47,"tag":317,"props":332,"children":333},{},[334],{"type":53,"value":335},"\u002F\u002F and the generated ID uses a 0 sentinel instead of null\n",{"type":47,"tag":317,"props":337,"children":339},{"class":319,"line":338},3,[340],{"type":47,"tag":317,"props":341,"children":342},{},[343],{"type":53,"value":344},"data class Order(\n",{"type":47,"tag":317,"props":346,"children":348},{"class":319,"line":347},4,[349],{"type":47,"tag":317,"props":350,"children":351},{},[352],{"type":53,"value":353},"    @Id @GeneratedValue val id: Long = 0,\n",{"type":47,"tag":317,"props":355,"children":357},{"class":319,"line":356},5,[358],{"type":47,"tag":317,"props":359,"children":360},{},[361],{"type":53,"value":362},"    var status: String,\n",{"type":47,"tag":317,"props":364,"children":366},{"class":319,"line":365},6,[367],{"type":47,"tag":317,"props":368,"children":369},{},[370],{"type":53,"value":371},"    var total: BigDecimal\n",{"type":47,"tag":317,"props":373,"children":375},{"class":319,"line":374},7,[376],{"type":47,"tag":317,"props":377,"children":378},{},[379],{"type":53,"value":380},")\n",{"type":47,"tag":317,"props":382,"children":384},{"class":319,"line":383},8,[385],{"type":47,"tag":317,"props":386,"children":387},{},[388],{"type":53,"value":389},"\u002F\u002F BUG: order.status = \"SHIPPED\"; set.contains(order) → false (hash changed)\n",{"type":47,"tag":317,"props":391,"children":393},{"class":319,"line":392},9,[394],{"type":47,"tag":317,"props":395,"children":396},{},[397],{"type":53,"value":398},"\u002F\u002F BUG: Hibernate proxy.equals(entity) → false (proxy has lazy fields uninitialized)\n",{"type":47,"tag":293,"props":400,"children":402},{"id":401},"correct-regular-class-with-id-based-identity",[403],{"type":53,"value":404},"Correct: Regular Class with ID-Based Identity",{"type":47,"tag":307,"props":406,"children":408},{"className":309,"code":407,"language":8,"meta":311,"style":311},"@Entity\n@Table(name = \"orders\")\nclass Order(\n    @Column(nullable = false)\n    var status: String,\n\n    @Column(nullable = false)\n    var total: BigDecimal\n) {\n    @Id\n    @GeneratedValue(strategy = GenerationType.IDENTITY)\n    var id: Long? = null\n        protected set\n\n    override fun equals(other: Any?): Boolean {\n        if (this === other) return true\n        if (other !is Order) return false\n        return id != null && id == other.id\n    }\n\n    override fun hashCode(): Int = javaClass.hashCode()\n\n    \u002F\u002F toString must NOT reference lazy collections\n    override fun toString(): String = \"Order(id=$id, status=$status)\"\n}\n",[409],{"type":47,"tag":62,"props":410,"children":411},{"__ignoreMap":311},[412,420,428,436,444,451,460,467,474,482,491,500,509,518,526,535,544,553,562,571,579,588,596,605,614],{"type":47,"tag":317,"props":413,"children":414},{"class":319,"line":320},[415],{"type":47,"tag":317,"props":416,"children":417},{},[418],{"type":53,"value":419},"@Entity\n",{"type":47,"tag":317,"props":421,"children":422},{"class":319,"line":329},[423],{"type":47,"tag":317,"props":424,"children":425},{},[426],{"type":53,"value":427},"@Table(name = \"orders\")\n",{"type":47,"tag":317,"props":429,"children":430},{"class":319,"line":338},[431],{"type":47,"tag":317,"props":432,"children":433},{},[434],{"type":53,"value":435},"class Order(\n",{"type":47,"tag":317,"props":437,"children":438},{"class":319,"line":347},[439],{"type":47,"tag":317,"props":440,"children":441},{},[442],{"type":53,"value":443},"    @Column(nullable = false)\n",{"type":47,"tag":317,"props":445,"children":446},{"class":319,"line":356},[447],{"type":47,"tag":317,"props":448,"children":449},{},[450],{"type":53,"value":362},{"type":47,"tag":317,"props":452,"children":453},{"class":319,"line":365},[454],{"type":47,"tag":317,"props":455,"children":457},{"emptyLinePlaceholder":456},true,[458],{"type":53,"value":459},"\n",{"type":47,"tag":317,"props":461,"children":462},{"class":319,"line":374},[463],{"type":47,"tag":317,"props":464,"children":465},{},[466],{"type":53,"value":443},{"type":47,"tag":317,"props":468,"children":469},{"class":319,"line":383},[470],{"type":47,"tag":317,"props":471,"children":472},{},[473],{"type":53,"value":371},{"type":47,"tag":317,"props":475,"children":476},{"class":319,"line":392},[477],{"type":47,"tag":317,"props":478,"children":479},{},[480],{"type":53,"value":481},") {\n",{"type":47,"tag":317,"props":483,"children":485},{"class":319,"line":484},10,[486],{"type":47,"tag":317,"props":487,"children":488},{},[489],{"type":53,"value":490},"    @Id\n",{"type":47,"tag":317,"props":492,"children":494},{"class":319,"line":493},11,[495],{"type":47,"tag":317,"props":496,"children":497},{},[498],{"type":53,"value":499},"    @GeneratedValue(strategy = GenerationType.IDENTITY)\n",{"type":47,"tag":317,"props":501,"children":503},{"class":319,"line":502},12,[504],{"type":47,"tag":317,"props":505,"children":506},{},[507],{"type":53,"value":508},"    var id: Long? = null\n",{"type":47,"tag":317,"props":510,"children":512},{"class":319,"line":511},13,[513],{"type":47,"tag":317,"props":514,"children":515},{},[516],{"type":53,"value":517},"        protected set\n",{"type":47,"tag":317,"props":519,"children":521},{"class":319,"line":520},14,[522],{"type":47,"tag":317,"props":523,"children":524},{"emptyLinePlaceholder":456},[525],{"type":53,"value":459},{"type":47,"tag":317,"props":527,"children":529},{"class":319,"line":528},15,[530],{"type":47,"tag":317,"props":531,"children":532},{},[533],{"type":53,"value":534},"    override fun equals(other: Any?): Boolean {\n",{"type":47,"tag":317,"props":536,"children":538},{"class":319,"line":537},16,[539],{"type":47,"tag":317,"props":540,"children":541},{},[542],{"type":53,"value":543},"        if (this === other) return true\n",{"type":47,"tag":317,"props":545,"children":547},{"class":319,"line":546},17,[548],{"type":47,"tag":317,"props":549,"children":550},{},[551],{"type":53,"value":552},"        if (other !is Order) return false\n",{"type":47,"tag":317,"props":554,"children":556},{"class":319,"line":555},18,[557],{"type":47,"tag":317,"props":558,"children":559},{},[560],{"type":53,"value":561},"        return id != null && id == other.id\n",{"type":47,"tag":317,"props":563,"children":565},{"class":319,"line":564},19,[566],{"type":47,"tag":317,"props":567,"children":568},{},[569],{"type":53,"value":570},"    }\n",{"type":47,"tag":317,"props":572,"children":574},{"class":319,"line":573},20,[575],{"type":47,"tag":317,"props":576,"children":577},{"emptyLinePlaceholder":456},[578],{"type":53,"value":459},{"type":47,"tag":317,"props":580,"children":582},{"class":319,"line":581},21,[583],{"type":47,"tag":317,"props":584,"children":585},{},[586],{"type":53,"value":587},"    override fun hashCode(): Int = javaClass.hashCode()\n",{"type":47,"tag":317,"props":589,"children":591},{"class":319,"line":590},22,[592],{"type":47,"tag":317,"props":593,"children":594},{"emptyLinePlaceholder":456},[595],{"type":53,"value":459},{"type":47,"tag":317,"props":597,"children":599},{"class":319,"line":598},23,[600],{"type":47,"tag":317,"props":601,"children":602},{},[603],{"type":53,"value":604},"    \u002F\u002F toString must NOT reference lazy collections\n",{"type":47,"tag":317,"props":606,"children":608},{"class":319,"line":607},24,[609],{"type":47,"tag":317,"props":610,"children":611},{},[612],{"type":53,"value":613},"    override fun toString(): String = \"Order(id=$id, status=$status)\"\n",{"type":47,"tag":317,"props":615,"children":617},{"class":319,"line":616},25,[618],{"type":47,"tag":317,"props":619,"children":620},{},[621],{"type":53,"value":622},"}\n",{"type":47,"tag":56,"props":624,"children":625},{},[626],{"type":47,"tag":137,"props":627,"children":628},{},[629],{"type":53,"value":630},"Key rules:",{"type":47,"tag":129,"props":632,"children":633},{},[634,644,667,684],{"type":47,"tag":133,"props":635,"children":636},{},[637,642],{"type":47,"tag":62,"props":638,"children":640},{"className":639},[],[641],{"type":53,"value":82},{"type":53,"value":643}," compares by ID only — stable under dirty tracking and proxy unwrapping",{"type":47,"tag":133,"props":645,"children":646},{},[647,652,654,659,660,665],{"type":47,"tag":62,"props":648,"children":650},{"className":649},[],[651],{"type":53,"value":90},{"type":53,"value":653}," returns class-based constant — avoids ",{"type":47,"tag":62,"props":655,"children":657},{"className":656},[],[658],{"type":53,"value":98},{"type":53,"value":84},{"type":47,"tag":62,"props":661,"children":663},{"className":662},[],[664],{"type":53,"value":105},{"type":53,"value":666}," corruption after persist",{"type":47,"tag":133,"props":668,"children":669},{},[670,676,678],{"type":47,"tag":62,"props":671,"children":673},{"className":672},[],[674],{"type":53,"value":675},"toString",{"type":53,"value":677}," excludes lazy-loaded relations — prevents ",{"type":47,"tag":62,"props":679,"children":681},{"className":680},[],[682],{"type":53,"value":683},"LazyInitializationException",{"type":47,"tag":133,"props":685,"children":686},{},[687,689,695],{"type":53,"value":688},"Constructor params are mutable entity fields; DB-generated ",{"type":47,"tag":62,"props":690,"children":692},{"className":691},[],[693],{"type":53,"value":694},"id",{"type":53,"value":696}," is nullable with a protected setter",{"type":47,"tag":122,"props":698,"children":700},{"id":699},"uniqueness-constraints",[701],{"type":53,"value":702},"Uniqueness Constraints",{"type":47,"tag":56,"props":704,"children":705},{},[706],{"type":53,"value":707},"When an API must be idempotent (e.g., \"reserve stock for order X\"), enforce uniqueness\nat both layers: database constraint for correctness, application check for clean errors.",{"type":47,"tag":293,"props":709,"children":711},{"id":710},"broken-no-duplicate-guard",[712],{"type":53,"value":713},"Broken: No Duplicate Guard",{"type":47,"tag":307,"props":715,"children":717},{"className":309,"code":716,"language":8,"meta":311,"style":311},"@Service\nclass ReservationService(private val repo: ReservationRepository) {\n    @Transactional\n    fun createReservation(variantId: Long, orderId: String, qty: Int): Reservation {\n        \u002F\u002F BUG: no check — duplicates silently accumulate\n        return repo.save(Reservation(variantId = variantId, orderId = orderId, quantity = qty))\n    }\n}\n",[718],{"type":47,"tag":62,"props":719,"children":720},{"__ignoreMap":311},[721,729,737,745,753,761,769,776],{"type":47,"tag":317,"props":722,"children":723},{"class":319,"line":320},[724],{"type":47,"tag":317,"props":725,"children":726},{},[727],{"type":53,"value":728},"@Service\n",{"type":47,"tag":317,"props":730,"children":731},{"class":319,"line":329},[732],{"type":47,"tag":317,"props":733,"children":734},{},[735],{"type":53,"value":736},"class ReservationService(private val repo: ReservationRepository) {\n",{"type":47,"tag":317,"props":738,"children":739},{"class":319,"line":338},[740],{"type":47,"tag":317,"props":741,"children":742},{},[743],{"type":53,"value":744},"    @Transactional\n",{"type":47,"tag":317,"props":746,"children":747},{"class":319,"line":347},[748],{"type":47,"tag":317,"props":749,"children":750},{},[751],{"type":53,"value":752},"    fun createReservation(variantId: Long, orderId: String, qty: Int): Reservation {\n",{"type":47,"tag":317,"props":754,"children":755},{"class":319,"line":356},[756],{"type":47,"tag":317,"props":757,"children":758},{},[759],{"type":53,"value":760},"        \u002F\u002F BUG: no check — duplicates silently accumulate\n",{"type":47,"tag":317,"props":762,"children":763},{"class":319,"line":365},[764],{"type":47,"tag":317,"props":765,"children":766},{},[767],{"type":53,"value":768},"        return repo.save(Reservation(variantId = variantId, orderId = orderId, quantity = qty))\n",{"type":47,"tag":317,"props":770,"children":771},{"class":319,"line":374},[772],{"type":47,"tag":317,"props":773,"children":774},{},[775],{"type":53,"value":570},{"type":47,"tag":317,"props":777,"children":778},{"class":319,"line":383},[779],{"type":47,"tag":317,"props":780,"children":781},{},[782],{"type":53,"value":622},{"type":47,"tag":293,"props":784,"children":786},{"id":785},"correct-database-constraint-application-guard",[787],{"type":53,"value":788},"Correct: Database Constraint + Application Guard",{"type":47,"tag":307,"props":790,"children":792},{"className":309,"code":791,"language":8,"meta":311,"style":311},"@Entity\n@Table(\n    name = \"reservations\",\n    uniqueConstraints = [\n        UniqueConstraint(columnNames = [\"variant_id\", \"order_id\"])\n    ]\n)\nclass Reservation(\n    @Column(name = \"variant_id\", nullable = false)\n    val variantId: Long,\n\n    @Column(name = \"order_id\", nullable = false)\n    val orderId: String,\n\n    @Column(nullable = false)\n    var quantity: Int\n) {\n    @Id @GeneratedValue(strategy = GenerationType.IDENTITY)\n    var id: Long? = null\n        protected set\n}\n\ninterface ReservationRepository : JpaRepository\u003CReservation, Long> {\n    fun findByVariantIdAndOrderId(variantId: Long, orderId: String): Reservation?\n}\n\n@Service\nclass ReservationService(private val repo: ReservationRepository) {\n    @Transactional\n    fun createReservation(variantId: Long, orderId: String, qty: Int): Reservation {\n        repo.findByVariantIdAndOrderId(variantId, orderId)?.let {\n            throw IllegalStateException(\n                \"Reservation already exists for variant=$variantId, order=$orderId\"\n            )\n        }\n        return repo.save(Reservation(variantId = variantId, orderId = orderId, quantity = qty))\n    }\n}\n",[793],{"type":47,"tag":62,"props":794,"children":795},{"__ignoreMap":311},[796,803,811,819,827,835,843,850,858,866,874,881,889,897,904,911,919,926,934,941,948,955,962,970,978,985,993,1001,1009,1017,1025,1034,1043,1052,1061,1069,1077,1085],{"type":47,"tag":317,"props":797,"children":798},{"class":319,"line":320},[799],{"type":47,"tag":317,"props":800,"children":801},{},[802],{"type":53,"value":419},{"type":47,"tag":317,"props":804,"children":805},{"class":319,"line":329},[806],{"type":47,"tag":317,"props":807,"children":808},{},[809],{"type":53,"value":810},"@Table(\n",{"type":47,"tag":317,"props":812,"children":813},{"class":319,"line":338},[814],{"type":47,"tag":317,"props":815,"children":816},{},[817],{"type":53,"value":818},"    name = \"reservations\",\n",{"type":47,"tag":317,"props":820,"children":821},{"class":319,"line":347},[822],{"type":47,"tag":317,"props":823,"children":824},{},[825],{"type":53,"value":826},"    uniqueConstraints = [\n",{"type":47,"tag":317,"props":828,"children":829},{"class":319,"line":356},[830],{"type":47,"tag":317,"props":831,"children":832},{},[833],{"type":53,"value":834},"        UniqueConstraint(columnNames = [\"variant_id\", \"order_id\"])\n",{"type":47,"tag":317,"props":836,"children":837},{"class":319,"line":365},[838],{"type":47,"tag":317,"props":839,"children":840},{},[841],{"type":53,"value":842},"    ]\n",{"type":47,"tag":317,"props":844,"children":845},{"class":319,"line":374},[846],{"type":47,"tag":317,"props":847,"children":848},{},[849],{"type":53,"value":380},{"type":47,"tag":317,"props":851,"children":852},{"class":319,"line":383},[853],{"type":47,"tag":317,"props":854,"children":855},{},[856],{"type":53,"value":857},"class Reservation(\n",{"type":47,"tag":317,"props":859,"children":860},{"class":319,"line":392},[861],{"type":47,"tag":317,"props":862,"children":863},{},[864],{"type":53,"value":865},"    @Column(name = \"variant_id\", nullable = false)\n",{"type":47,"tag":317,"props":867,"children":868},{"class":319,"line":484},[869],{"type":47,"tag":317,"props":870,"children":871},{},[872],{"type":53,"value":873},"    val variantId: Long,\n",{"type":47,"tag":317,"props":875,"children":876},{"class":319,"line":493},[877],{"type":47,"tag":317,"props":878,"children":879},{"emptyLinePlaceholder":456},[880],{"type":53,"value":459},{"type":47,"tag":317,"props":882,"children":883},{"class":319,"line":502},[884],{"type":47,"tag":317,"props":885,"children":886},{},[887],{"type":53,"value":888},"    @Column(name = \"order_id\", nullable = false)\n",{"type":47,"tag":317,"props":890,"children":891},{"class":319,"line":511},[892],{"type":47,"tag":317,"props":893,"children":894},{},[895],{"type":53,"value":896},"    val orderId: String,\n",{"type":47,"tag":317,"props":898,"children":899},{"class":319,"line":520},[900],{"type":47,"tag":317,"props":901,"children":902},{"emptyLinePlaceholder":456},[903],{"type":53,"value":459},{"type":47,"tag":317,"props":905,"children":906},{"class":319,"line":528},[907],{"type":47,"tag":317,"props":908,"children":909},{},[910],{"type":53,"value":443},{"type":47,"tag":317,"props":912,"children":913},{"class":319,"line":537},[914],{"type":47,"tag":317,"props":915,"children":916},{},[917],{"type":53,"value":918},"    var quantity: Int\n",{"type":47,"tag":317,"props":920,"children":921},{"class":319,"line":546},[922],{"type":47,"tag":317,"props":923,"children":924},{},[925],{"type":53,"value":481},{"type":47,"tag":317,"props":927,"children":928},{"class":319,"line":555},[929],{"type":47,"tag":317,"props":930,"children":931},{},[932],{"type":53,"value":933},"    @Id @GeneratedValue(strategy = GenerationType.IDENTITY)\n",{"type":47,"tag":317,"props":935,"children":936},{"class":319,"line":564},[937],{"type":47,"tag":317,"props":938,"children":939},{},[940],{"type":53,"value":508},{"type":47,"tag":317,"props":942,"children":943},{"class":319,"line":573},[944],{"type":47,"tag":317,"props":945,"children":946},{},[947],{"type":53,"value":517},{"type":47,"tag":317,"props":949,"children":950},{"class":319,"line":581},[951],{"type":47,"tag":317,"props":952,"children":953},{},[954],{"type":53,"value":622},{"type":47,"tag":317,"props":956,"children":957},{"class":319,"line":590},[958],{"type":47,"tag":317,"props":959,"children":960},{"emptyLinePlaceholder":456},[961],{"type":53,"value":459},{"type":47,"tag":317,"props":963,"children":964},{"class":319,"line":598},[965],{"type":47,"tag":317,"props":966,"children":967},{},[968],{"type":53,"value":969},"interface ReservationRepository : JpaRepository\u003CReservation, Long> {\n",{"type":47,"tag":317,"props":971,"children":972},{"class":319,"line":607},[973],{"type":47,"tag":317,"props":974,"children":975},{},[976],{"type":53,"value":977},"    fun findByVariantIdAndOrderId(variantId: Long, orderId: String): Reservation?\n",{"type":47,"tag":317,"props":979,"children":980},{"class":319,"line":616},[981],{"type":47,"tag":317,"props":982,"children":983},{},[984],{"type":53,"value":622},{"type":47,"tag":317,"props":986,"children":988},{"class":319,"line":987},26,[989],{"type":47,"tag":317,"props":990,"children":991},{"emptyLinePlaceholder":456},[992],{"type":53,"value":459},{"type":47,"tag":317,"props":994,"children":996},{"class":319,"line":995},27,[997],{"type":47,"tag":317,"props":998,"children":999},{},[1000],{"type":53,"value":728},{"type":47,"tag":317,"props":1002,"children":1004},{"class":319,"line":1003},28,[1005],{"type":47,"tag":317,"props":1006,"children":1007},{},[1008],{"type":53,"value":736},{"type":47,"tag":317,"props":1010,"children":1012},{"class":319,"line":1011},29,[1013],{"type":47,"tag":317,"props":1014,"children":1015},{},[1016],{"type":53,"value":744},{"type":47,"tag":317,"props":1018,"children":1020},{"class":319,"line":1019},30,[1021],{"type":47,"tag":317,"props":1022,"children":1023},{},[1024],{"type":53,"value":752},{"type":47,"tag":317,"props":1026,"children":1028},{"class":319,"line":1027},31,[1029],{"type":47,"tag":317,"props":1030,"children":1031},{},[1032],{"type":53,"value":1033},"        repo.findByVariantIdAndOrderId(variantId, orderId)?.let {\n",{"type":47,"tag":317,"props":1035,"children":1037},{"class":319,"line":1036},32,[1038],{"type":47,"tag":317,"props":1039,"children":1040},{},[1041],{"type":53,"value":1042},"            throw IllegalStateException(\n",{"type":47,"tag":317,"props":1044,"children":1046},{"class":319,"line":1045},33,[1047],{"type":47,"tag":317,"props":1048,"children":1049},{},[1050],{"type":53,"value":1051},"                \"Reservation already exists for variant=$variantId, order=$orderId\"\n",{"type":47,"tag":317,"props":1053,"children":1055},{"class":319,"line":1054},34,[1056],{"type":47,"tag":317,"props":1057,"children":1058},{},[1059],{"type":53,"value":1060},"            )\n",{"type":47,"tag":317,"props":1062,"children":1063},{"class":319,"line":30},[1064],{"type":47,"tag":317,"props":1065,"children":1066},{},[1067],{"type":53,"value":1068},"        }\n",{"type":47,"tag":317,"props":1070,"children":1072},{"class":319,"line":1071},36,[1073],{"type":47,"tag":317,"props":1074,"children":1075},{},[1076],{"type":53,"value":768},{"type":47,"tag":317,"props":1078,"children":1080},{"class":319,"line":1079},37,[1081],{"type":47,"tag":317,"props":1082,"children":1083},{},[1084],{"type":53,"value":570},{"type":47,"tag":317,"props":1086,"children":1088},{"class":319,"line":1087},38,[1089],{"type":47,"tag":317,"props":1090,"children":1091},{},[1092],{"type":53,"value":622},{"type":47,"tag":56,"props":1094,"children":1095},{},[1096],{"type":47,"tag":137,"props":1097,"children":1098},{},[1099],{"type":53,"value":630},{"type":47,"tag":129,"props":1101,"children":1102},{},[1103,1108,1119,1124],{"type":47,"tag":133,"props":1104,"children":1105},{},[1106],{"type":53,"value":1107},"Database constraint is mandatory — application checks alone have race conditions",{"type":47,"tag":133,"props":1109,"children":1110},{},[1111,1113],{"type":53,"value":1112},"Application check provides clean error messages — without it, users get raw ",{"type":47,"tag":62,"props":1114,"children":1116},{"className":1115},[],[1117],{"type":53,"value":1118},"DataIntegrityViolationException",{"type":47,"tag":133,"props":1120,"children":1121},{},[1122],{"type":53,"value":1123},"Both layers together: application catches the common case, database catches the race",{"type":47,"tag":133,"props":1125,"children":1126},{},[1127,1129,1135],{"type":53,"value":1128},"Spring Data derives ",{"type":47,"tag":62,"props":1130,"children":1132},{"className":1131},[],[1133],{"type":53,"value":1134},"findByXAndY",{"type":53,"value":1136}," queries automatically",{"type":47,"tag":122,"props":1138,"children":1140},{"id":1139},"query-and-fetch-rules",[1141],{"type":53,"value":1142},"Query and Fetch Rules",{"type":47,"tag":129,"props":1144,"children":1145},{},[1146,1151,1172,1177],{"type":47,"tag":133,"props":1147,"children":1148},{},[1149],{"type":53,"value":1150},"Diagnose N+1 by looking at actual query count or SQL logs, not by guessing from annotations.",{"type":47,"tag":133,"props":1152,"children":1153},{},[1154,1156,1162,1164,1170],{"type":53,"value":1155},"Prefer targeted fetch solutions: ",{"type":47,"tag":62,"props":1157,"children":1159},{"className":1158},[],[1160],{"type":53,"value":1161},"@EntityGraph",{"type":53,"value":1163},", ",{"type":47,"tag":62,"props":1165,"children":1167},{"className":1166},[],[1168],{"type":53,"value":1169},"JOIN FETCH",{"type":53,"value":1171},", batch fetching, or DTO projection.",{"type":47,"tag":133,"props":1173,"children":1174},{},[1175],{"type":53,"value":1176},"Be careful with collection fetch joins plus pagination — call out the tradeoff.",{"type":47,"tag":133,"props":1178,"children":1179},{},[1180],{"type":53,"value":1181},"Use indexes and uniqueness constraints to support real query patterns.",{"type":47,"tag":122,"props":1183,"children":1185},{"id":1184},"common-orm-traps",[1186],{"type":53,"value":1187},"Common ORM Traps",{"type":47,"tag":129,"props":1189,"children":1190},{},[1191,1201,1217,1234,1244,1254,1269,1285],{"type":47,"tag":133,"props":1192,"children":1193},{},[1194,1199],{"type":47,"tag":137,"props":1195,"children":1196},{},[1197],{"type":53,"value":1198},"Bidirectional associations:",{"type":53,"value":1200}," maintain both sides in domain methods. Half-updated graphs cause subtle bugs.",{"type":47,"tag":133,"props":1202,"children":1203},{},[1204,1215],{"type":47,"tag":137,"props":1205,"children":1206},{},[1207,1213],{"type":47,"tag":62,"props":1208,"children":1210},{"className":1209},[],[1211],{"type":53,"value":1212},"orphanRemoval",{"type":53,"value":1214}," vs cascade remove:",{"type":53,"value":1216}," not interchangeable. Explain lifecycle semantics before choosing.",{"type":47,"tag":133,"props":1218,"children":1219},{},[1220,1225,1227,1232],{"type":47,"tag":137,"props":1221,"children":1222},{},[1223],{"type":53,"value":1224},"Lazy load triggers:",{"type":53,"value":1226}," ",{"type":47,"tag":62,"props":1228,"children":1230},{"className":1229},[],[1231],{"type":53,"value":675},{"type":53,"value":1233},", debug logging, JSON serialization, and IDE inspection can all trigger lazy loads.",{"type":47,"tag":133,"props":1235,"children":1236},{},[1237,1242],{"type":47,"tag":137,"props":1238,"children":1239},{},[1240],{"type":53,"value":1241},"Bulk updates\u002Fdeletes:",{"type":53,"value":1243}," bypass persistence context and lifecycle callbacks. Subsequent reads may be stale.",{"type":47,"tag":133,"props":1245,"children":1246},{},[1247,1252],{"type":47,"tag":137,"props":1248,"children":1249},{},[1250],{"type":53,"value":1251},"Multiple bag fetches:",{"type":53,"value":1253}," can cause Cartesian explosion. Verify the ORM can execute collection-heavy fetch plans safely.",{"type":47,"tag":133,"props":1255,"children":1256},{},[1257,1267],{"type":47,"tag":137,"props":1258,"children":1259},{},[1260,1265],{"type":47,"tag":62,"props":1261,"children":1263},{"className":1262},[],[1264],{"type":53,"value":98},{"type":53,"value":1266}," + mutable equality:",{"type":53,"value":1268}," collection membership can break after entity state changes.",{"type":47,"tag":133,"props":1270,"children":1271},{},[1272,1283],{"type":47,"tag":137,"props":1273,"children":1274},{},[1275,1281],{"type":47,"tag":62,"props":1276,"children":1278},{"className":1277},[],[1279],{"type":53,"value":1280},"@Version",{"type":53,"value":1282},":",{"type":53,"value":1284}," the clearest optimistic concurrency mechanism when concurrent updates matter.",{"type":47,"tag":133,"props":1286,"children":1287},{},[1288,1299],{"type":47,"tag":137,"props":1289,"children":1290},{},[1291,1297],{"type":47,"tag":62,"props":1292,"children":1294},{"className":1293},[],[1295],{"type":53,"value":1296},"open-in-view",{"type":53,"value":1298}," disabled:",{"type":53,"value":1300}," DTO mapping touching lazy fields must happen inside a transaction boundary.",{"type":47,"tag":122,"props":1302,"children":1304},{"id":1303},"guardrails",[1305],{"type":53,"value":1306},"Guardrails",{"type":47,"tag":129,"props":1308,"children":1309},{},[1310,1321,1334,1339],{"type":47,"tag":133,"props":1311,"children":1312},{},[1313,1315,1320],{"type":53,"value":1314},"Do not use ",{"type":47,"tag":62,"props":1316,"children":1318},{"className":1317},[],[1319],{"type":53,"value":67},{"type":53,"value":148},{"type":47,"tag":133,"props":1322,"children":1323},{},[1324,1326,1332],{"type":53,"value":1325},"Do not recommend ",{"type":47,"tag":62,"props":1327,"children":1329},{"className":1328},[],[1330],{"type":53,"value":1331},"FetchType.EAGER",{"type":53,"value":1333}," everywhere to silence lazy loading symptoms.",{"type":47,"tag":133,"props":1335,"children":1336},{},[1337],{"type":53,"value":1338},"Do not expose entities directly through API responses by default.",{"type":47,"tag":133,"props":1340,"children":1341},{},[1342],{"type":53,"value":1343},"Do not claim an N+1 fix without explaining how the fetch plan changes query behavior.",{"type":47,"tag":1345,"props":1346,"children":1347},"style",{},[1348],{"type":53,"value":1349},"html .light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html.light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}",{"items":1351,"total":365},[1352,1360,1377,1393,1411,1424],{"slug":4,"name":4,"fn":5,"description":6,"org":1353,"tags":1354,"stars":26,"repoUrl":27,"updatedAt":28},{"slug":8,"name":9,"logoUrl":10,"githubOrg":8},[1355,1356,1357,1358,1359],{"name":15,"slug":16,"type":13},{"name":24,"slug":25,"type":13},{"name":18,"slug":19,"type":13},{"name":9,"slug":8,"type":13},{"name":21,"slug":22,"type":13},{"slug":1361,"name":1361,"fn":1362,"description":1363,"org":1364,"tags":1365,"stars":26,"repoUrl":27,"updatedAt":1376},"kotlin-tooling-agp9-migration","migrate KMP projects to AGP 9.0","Migrates Kotlin Multiplatform (KMP) projects to Android Gradle Plugin 9.0+. Handles plugin replacement (com.android.kotlin.multiplatform.library), module splitting, DSL migration, and the new default project structure. Use when upgrading AGP, when build fails due to KMP+AGP incompatibility, or when the user mentions AGP 9.0, android multiplatform plugin, KMP migration, or com.android.kotlin.multiplatform.library.\n",{"slug":8,"name":9,"logoUrl":10,"githubOrg":8},[1366,1369,1370,1373],{"name":1367,"slug":1368,"type":13},"Android","android",{"name":9,"slug":8,"type":13},{"name":1371,"slug":1372,"type":13},"Migration","migration",{"name":1374,"slug":1375,"type":13},"Mobile","mobile","2026-04-06T18:25:58.945632",{"slug":1378,"name":1378,"fn":1379,"description":1380,"org":1381,"tags":1382,"stars":26,"repoUrl":27,"updatedAt":1392},"kotlin-tooling-cocoapods-spm-migration","migrate KMP projects from CocoaPods to SPM","Migrate KMP projects from CocoaPods (kotlin(\"native.cocoapods\")) to Swift Package Manager (swiftPMDependencies DSL) — replaces pod() with swiftPackage(), transforms cocoapods.* imports to swiftPMImport.*, and reconfigures the Xcode project.",{"slug":8,"name":9,"logoUrl":10,"githubOrg":8},[1383,1386,1387,1388,1389],{"name":1384,"slug":1385,"type":13},"iOS","ios",{"name":9,"slug":8,"type":13},{"name":1371,"slug":1372,"type":13},{"name":1374,"slug":1375,"type":13},{"name":1390,"slug":1391,"type":13},"Swift","swift","2026-04-06T18:25:57.691016",{"slug":1394,"name":1394,"fn":1395,"description":1396,"org":1397,"tags":1398,"stars":26,"repoUrl":27,"updatedAt":1410},"kotlin-tooling-immutable-collections-0-5-x-migration","migrate Kotlin projects to immutable collections 0.5.x","Migrate Kotlin (and Java) code from kotlinx.collections.immutable 0.3.x \u002F 0.4.x to the latest 0.5.x. The 0.5.x line renames every copy-returning method on PersistentList \u002F PersistentMap \u002F PersistentSet \u002F PersistentCollection to a participial form per KEEP-0459 (add→adding, removeAt→removingAt, set→replacingAt, put→putting, clear→cleared, …) and deprecates the old names (WARNING, with ReplaceWith). Driven by the compiler: bump the version, recompile, and apply the rename each deprecation warning names. Use when the user mentions kotlinx.collections.immutable 0.5.x, PersistentList migration, \"Use adding() instead\", KEEP-0459, or sees deprecation warnings from kotlinx.collections.immutable.\n",{"slug":8,"name":9,"logoUrl":10,"githubOrg":8},[1399,1402,1405,1406,1407],{"name":1400,"slug":1401,"type":13},"Engineering","engineering",{"name":1403,"slug":1404,"type":13},"Java","java",{"name":9,"slug":8,"type":13},{"name":1371,"slug":1372,"type":13},{"name":1408,"slug":1409,"type":13},"Tech Debt","tech-debt","2026-06-09T07:19:44.883299",{"slug":1412,"name":1412,"fn":1413,"description":1414,"org":1415,"tags":1416,"stars":26,"repoUrl":27,"updatedAt":1423},"kotlin-tooling-java-to-kotlin","convert Java code to idiomatic Kotlin","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\u002FHilt, RxJava, JUnit, Guice, Retrofit, and Mockito.\n",{"slug":8,"name":9,"logoUrl":10,"githubOrg":8},[1417,1420,1421,1422],{"name":1418,"slug":1419,"type":13},"Code Analysis","code-analysis",{"name":1403,"slug":1404,"type":13},{"name":9,"slug":8,"type":13},{"name":1371,"slug":1372,"type":13},"2026-04-06T18:26:00.191761",{"slug":1425,"name":1425,"fn":1426,"description":1427,"org":1428,"tags":1429,"stars":26,"repoUrl":27,"updatedAt":1436},"kotlin-tooling-native-build-performance","optimize Kotlin Native build performance","Diagnoses and fixes slow Kotlin\u002FNative compilation and linking in Kotlin Multiplatform projects that target iOS. Use when the user reports slow iOS or shared-framework builds, long linkDebug*\u002FlinkRelease* or XCFramework tasks, cold CI builds that re-download the Kotlin\u002FNative toolchain, KSP or other generated code on the native path, transitiveExport usage, or asks for a local-development versus CI build performance plan.\n",{"slug":8,"name":9,"logoUrl":10,"githubOrg":8},[1430,1431,1432,1433],{"name":1384,"slug":1385,"type":13},{"name":9,"slug":8,"type":13},{"name":1374,"slug":1375,"type":13},{"name":1434,"slug":1435,"type":13},"Performance","performance","2026-07-18T05:47:25.998032",{"items":1438,"total":365},[1439,1447,1454,1462,1470,1477],{"slug":4,"name":4,"fn":5,"description":6,"org":1440,"tags":1441,"stars":26,"repoUrl":27,"updatedAt":28},{"slug":8,"name":9,"logoUrl":10,"githubOrg":8},[1442,1443,1444,1445,1446],{"name":15,"slug":16,"type":13},{"name":24,"slug":25,"type":13},{"name":18,"slug":19,"type":13},{"name":9,"slug":8,"type":13},{"name":21,"slug":22,"type":13},{"slug":1361,"name":1361,"fn":1362,"description":1363,"org":1448,"tags":1449,"stars":26,"repoUrl":27,"updatedAt":1376},{"slug":8,"name":9,"logoUrl":10,"githubOrg":8},[1450,1451,1452,1453],{"name":1367,"slug":1368,"type":13},{"name":9,"slug":8,"type":13},{"name":1371,"slug":1372,"type":13},{"name":1374,"slug":1375,"type":13},{"slug":1378,"name":1378,"fn":1379,"description":1380,"org":1455,"tags":1456,"stars":26,"repoUrl":27,"updatedAt":1392},{"slug":8,"name":9,"logoUrl":10,"githubOrg":8},[1457,1458,1459,1460,1461],{"name":1384,"slug":1385,"type":13},{"name":9,"slug":8,"type":13},{"name":1371,"slug":1372,"type":13},{"name":1374,"slug":1375,"type":13},{"name":1390,"slug":1391,"type":13},{"slug":1394,"name":1394,"fn":1395,"description":1396,"org":1463,"tags":1464,"stars":26,"repoUrl":27,"updatedAt":1410},{"slug":8,"name":9,"logoUrl":10,"githubOrg":8},[1465,1466,1467,1468,1469],{"name":1400,"slug":1401,"type":13},{"name":1403,"slug":1404,"type":13},{"name":9,"slug":8,"type":13},{"name":1371,"slug":1372,"type":13},{"name":1408,"slug":1409,"type":13},{"slug":1412,"name":1412,"fn":1413,"description":1414,"org":1471,"tags":1472,"stars":26,"repoUrl":27,"updatedAt":1423},{"slug":8,"name":9,"logoUrl":10,"githubOrg":8},[1473,1474,1475,1476],{"name":1418,"slug":1419,"type":13},{"name":1403,"slug":1404,"type":13},{"name":9,"slug":8,"type":13},{"name":1371,"slug":1372,"type":13},{"slug":1425,"name":1425,"fn":1426,"description":1427,"org":1478,"tags":1479,"stars":26,"repoUrl":27,"updatedAt":1436},{"slug":8,"name":9,"logoUrl":10,"githubOrg":8},[1480,1481,1482,1483],{"name":1384,"slug":1385,"type":13},{"name":9,"slug":8,"type":13},{"name":1374,"slug":1375,"type":13},{"name":1434,"slug":1435,"type":13}]