[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"global":3,"blogpost-java-to-kotlin-what-to-expect":31},{"title":4,"description":5,"home_welcome_badge":6,"home_feature_cards":7,"home_newsletter":24,"author_name":30,"author_photo":30,"author_bio":30},"Result Crafter Blog","In the era of AI, anyone can generate code overnight. But code that lasts years — you can debug, extend, and maintain — it is not as trivial as it could look like 🙂","Welcome to the craft",[8,12,16,20],{"icon":9,"title":10,"description":11},"code","AI Tools & Workflows","Navigating the new landscape of AI assistance without losing your engineering fundamentals.",{"icon":13,"title":14,"description":15},"bug","Code Smells","Identifying subtle anti-patterns before they become untamable technical debt in your codebase.",{"icon":17,"title":18,"description":19},"cpu","Software Craftsmanship","Techniques for writing clean, testable, and maintainable systems that outlast the current hype cycle.",{"icon":21,"title":22,"description":23},"web","New Feature","hi there",[25],{"headline":26,"description":27,"placeholder":28,"buttonLabel":29},"Don't miss an essay","Get occasional thoughts on software design, code quality, and building resilient systems delivered straight to your inbox. No spam, ever!","your-email@example.com","Subscribe",null,{"post":32,"directusUrl":253},{"id":33,"title":34,"slug":35,"excerpt":36,"date_created":37,"publish_date":37,"reading_time":30,"category":38,"featured_image":30,"content":30,"blocks":39},"5f7a94dd-2da2-4bd5-8860-6004adfa963d","Java to Kotlin: what to expect","java-to-kotlin-what-to-expect","Practical lessons from learning Kotlin after years of Java — the good, the annoying, and the surprising.","2026-04-30T10:22:51.649Z","Kotlin",[40,48,54,60,69,75,81,87,93,99,105,111,117,123,129,135,141,149,156,163,170,174,182,190,195,200,205,209,214,219,222,225,229,233,237,241,245,249],{"id":41,"sort":42,"collection":43,"item":44},"2f25994e-5af8-4fad-b211-304d8ab771a5",1,"block_prose",{"id":45,"content":46,"width":47},"9d708c58-2434-4fc9-8115-9a4d43192b9b","\u003Cp>After a decade of Java, I started picking up Kotlin — alongside Python, Go, and TypeScript. The feeling was what people describe as \u003Cem>pain relief\u003C\u002Fem>. Things that took 30 lines in Java took 3. Things that required entire libraries in Java were built into the language.\u003C\u002Fp>\u003Cp>But not everything is rosy. There are gotchas — things that look the same but behave differently, things the documentation doesn't warn you about. This is what I wish I'd read before diving in.\u003C\u002Fp>","full",{"id":49,"sort":50,"collection":43,"item":51},"57dee22b-7b0f-4ddc-a9e4-5903778eaccb",2,{"id":52,"content":53,"width":47},"cfc2c58b-38a3-4354-8195-ce8168fa9e7d","\u003Ch2>What makes life easier right away\u003C\u002Fh2>\u003Cp>These are the features that made me go \u003Cem>\"wait, that's it?\"\u003C\u002Fem> within the first week.\u003C\u002Fp>",{"id":55,"sort":56,"collection":43,"item":57},"38544159-ed9b-4727-8b62-f4ccab088db0",3,{"id":58,"content":59,"width":47},"343c8545-6deb-4281-bea9-d3351b3dafc0","\u003Ch3>Null safety — finally built in\u003C\u002Fh3>\u003Cp>In Java, null is a runtime bomb. In Kotlin, it's a \u003Cem>compile-time error\u003C\u002Fem>. The type system distinguishes between things that can be null and things that can't.\u003C\u002Fp>",{"id":61,"sort":62,"collection":63,"item":64},"30efe71e-5588-41be-a593-630a85ec2ad3",4,"block_code",{"id":65,"code":66,"language":67,"filename":68,"highlight_lines":68,"width":47},28,"\u002F\u002F Java — null is always lurking\nString name = user.getName();\nif (name != null) {\n    int length = name.length(); \u002F\u002F Still might be null?\n}\n\n\u002F\u002F Kotlin — the compiler has your back\nval name: String = user.name       \u002F\u002F Can't be null, guaranteed\nval nickname: String? = user.nick  \u002F\u002F Can be null, marked with ?\nval length = nickname?.length ?: 0 \u002F\u002F Safe call + default","kotlin","",{"id":70,"sort":71,"collection":43,"item":72},"49cbc19f-0a0a-42dd-8997-d3736376c09f",5,{"id":73,"content":74,"width":47},"c41c2b3a-55d5-4545-8ec9-fc4d1cafaf9e","\u003Cp>\u003Ccode>String\u003C\u002Fcode> and \u003Ccode>String?\u003C\u002Fcode> are \u003Cem>different types\u003C\u002Fem>. The compiler won't let you call methods on a nullable without checking first. This alone eliminates an entire category of NullPointerException bugs.\u003C\u002Fp>",{"id":76,"sort":77,"collection":43,"item":78},"469f3768-487e-418d-a822-98c6f1de0657",6,{"id":79,"content":80,"width":47},"da6d08d8-001b-4f05-ab09-9226f5962fc3","\u003Ch3>Data classes — goodbye boilerplate\u003C\u002Fh3>\u003Cp>The Java POJO: constructor, getters, setters, equals, hashCode, toString. About 30 lines for a simple object. In Kotlin? One line.\u003C\u002Fp>",{"id":82,"sort":83,"collection":63,"item":84},"019e6ea6-4755-4f5d-9a43-70a2c65fe7a0",7,{"id":85,"code":86,"language":67,"filename":68,"highlight_lines":68,"width":47},24,"\u002F\u002F Java: 30 lines for a POJO\npublic class User {\n    private String name;\n    private int age;\n    public User(String name, int age) { this.name = name; this.age = age; }\n    public String getName() { return name; }\n    public void setName(String name) { this.name = name; }\n    public int getAge() { return age; }\n    public void setAge(int age) { this.age = age; }\n    @Override public boolean equals(Object o) { \u002F* 10 lines *\u002F }\n    @Override public int hashCode() { \u002F* 5 lines *\u002F }\n    @Override public String toString() { \u002F* 3 lines *\u002F }\n}\n\n\u002F\u002F Kotlin: 1 line. All of the above, auto-generated.\ndata class User(val name: String, val age: Int)",{"id":88,"sort":89,"collection":43,"item":90},"4ceca22f-f68d-46a0-963b-77ffbc8f77be",8,{"id":91,"content":92,"width":47},"05c668c5-c64e-4345-9151-2b07de935975","\u003Ch3>when instead of switch\u003C\u002Fh3>\u003Cp>Java's switch is limited — integers, strings, enums. Kotlin's \u003Ccode>when\u003C\u002Fcode> is an expression that returns a value, supports ranges, type checks, and the compiler warns you if you miss a branch.\u003C\u002Fp>",{"id":94,"sort":95,"collection":63,"item":96},"44680cb9-5de1-4249-86f2-869d3b1c11f6",9,{"id":97,"code":98,"language":67,"filename":68,"highlight_lines":68,"width":47},25,"\u002F\u002F Java switch — doesn't return a value\nString result;\nswitch (status) {\n    case \"ACTIVE\": result = \"ok\"; break;\n    case \"INACTIVE\": result = \"paused\"; break;\n    default: result = \"unknown\";\n}\n\n\u002F\u002F Kotlin when — it's an expression!\nval result = when (status) {\n    \"ACTIVE\" -> \"ok\"\n    \"INACTIVE\" -> \"paused\"\n    in \"PENDING\"..\"REVIEW\" -> \"in progress\"  \u002F\u002F Ranges!\n    else -> \"unknown\"\n}\n\n\u002F\u002F Smart casts inside when\nwhen (animal) {\n    is Dog -> animal.bark()    \u002F\u002F Smart cast to Dog\n    is Cat -> animal.meow()    \u002F\u002F Smart cast to Cat\n}",{"id":100,"sort":101,"collection":43,"item":102},"6e675460-5311-44ce-aaf6-a92506cb32db",10,{"id":103,"content":104,"width":47},"c24b68a0-7d29-4362-871b-b6c4037fedfb","\u003Ch2>Watch out for these gotchas\u003C\u002Fh2>\u003Cp>Now the part they don't put in the migration guide. These are the things that caused real bugs in production.\u003C\u002Fp>",{"id":106,"sort":107,"collection":43,"item":108},"e095a184-65fe-424e-8758-84981accc00b",11,{"id":109,"content":110,"width":47},"ca5e4d27-dd81-4c8f-a36b-cb885303ec00","\u003Ch3>Gotcha 1: Companion object is not static\u003C\u002Fh3>\u003Cp>In Java, \u003Ccode>static\u003C\u002Fcode> means the method belongs to the class. In Kotlin, there's no \u003Ccode>static\u003C\u002Fcode>. Instead, there's \u003Ccode>companion object\u003C\u002Fcode> — a \u003Cem>real singleton object\u003C\u002Fem> with its own lifecycle. It's closer to an inner class instance than a static method.\u003C\u002Fp>",{"id":112,"sort":113,"collection":63,"item":114},"17193657-9f1c-405c-802e-c60cc0e7c53b",12,{"id":115,"code":116,"language":67,"filename":68,"highlight_lines":68,"width":47},23,"class User(val name: String) {\n    companion object {\n        fun create(name: String): User = User(name)\n    }\n}\n\n\u002F\u002F Looks like a static call...\nUser.create(\"Alice\")\n\n\u002F\u002F But it's actually a singleton object method:\n\u002F\u002F User.Companion.create(\"Alice\")\n\u002F\u002F The Companion object has its own init, lifecycle, etc.",{"id":118,"sort":119,"collection":43,"item":120},"c2c51d92-0787-43ed-989b-ed28b4680590",13,{"id":121,"content":122,"width":47},"913b6b0e-24d1-462e-a8fd-4a2262dc96c6","\u003Ch3>Gotcha 2: Collections are read-only, not immutable\u003C\u002Fh3>\u003Cp>This one catches everyone. \u003Ccode>listOf(1, 2, 3)\u003C\u002Fcode> in Kotlin is \u003Cem>read-only\u003C\u002Fem>, not truly immutable. Under the hood, it might still be a \u003Ccode>java.util.ArrayList\u003C\u002Fcode>. At the Java-Kotlin boundary, a Java method can mutate your \"immutable\" list.\u003C\u002Fp>",{"id":124,"sort":125,"collection":63,"item":126},"ebfa4415-40fe-4160-8ea6-4099f80092b4",14,{"id":127,"code":128,"language":67,"filename":68,"highlight_lines":68,"width":47},27,"\u002F\u002F Kotlin: read-only view\nval list = listOf(1, 2, 3)         \u002F\u002F Can't add\u002Fremove... in Kotlin\nval mutable = mutableListOf(1, 2, 3) \u002F\u002F Can add\u002Fremove\n\n\u002F\u002F But under the hood:\n\u002F\u002F listOf() might return java.util.Arrays$ArrayList\n\u002F\u002F A Java method can still cast and mutate it!\n\n\u002F\u002F Safe approach at Java boundaries:\nval safeList = listOf(1, 2, 3).toList() \u002F\u002F Defensive copy",{"id":130,"sort":131,"collection":43,"item":132},"d234d222-11c2-4bb5-9e00-e0205a94373d",15,{"id":133,"content":134,"width":47},"7cc0214d-cdca-4673-a114-d0219f12938a","\u003Ch3>Gotcha 3: Coroutines are not threads\u003C\u002Fh3>\u003Cp>The biggest conceptual shift. A coroutine is \u003Cem>not\u003C\u002Fem> a thread. It's a lightweight unit of work that can \u003Cstrong>suspend\u003C\u002Fstrong> without blocking the thread. Using \u003Ccode>Thread.sleep()\u003C\u002Fcode> inside a coroutine blocks the entire thread — defeating the purpose.\u003C\u002Fp>",{"id":136,"sort":137,"collection":63,"item":138},"4dc108ee-e060-427a-a3cc-5ae6bec4b068",16,{"id":139,"code":140,"language":67,"filename":68,"highlight_lines":68,"width":47},26,"\u002F\u002F ❌ Bad — blocks the thread, defeats the purpose\nsuspend fun fetchUser(): User {\n    Thread.sleep(5000)  \u002F\u002F The thread is BLOCKED for 5 seconds\n    return userRepository.find()\n}\n\n\u002F\u002F ✅ Good — suspends coroutine, thread is FREE\nsuspend fun fetchUser(): User {\n    delay(5000)  \u002F\u002F The coroutine suspends, thread serves other coroutines\n    return userRepository.find()\n}\n\n\u002F\u002F With delay: one thread can handle 100,000+ coroutines\n\u002F\u002F With Thread.sleep: one thread handles ONE coroutine",{"id":142,"sort":143,"collection":144,"item":145},"889d0434-1f2c-4e20-a40e-5c6ee22f1740",17,"block_diagram",{"id":107,"source_type":146,"mermaid_code":147,"image":30,"caption":148,"width":47},"mermaid","flowchart LR\n    subgraph blocking [Thread.sleep — BLOCKS]\n        direction TB\n        B1[\"Coroutine A\"] --> B2[\"Thread BLOCKED\\n5 seconds\"]\n        B2 --> B3[\"Nothing else can\\nuse this thread\"]\n    end\n\n    subgraph suspending [delay — SUSPENDS]\n        direction TB\n        S1[\"Coroutine A suspends\"] --> S2[\"Thread is FREE\\nserves B, C, D...\"]\n        S2 --> S3[\"Coroutine A resumes\\nafter 5 seconds\"]\n    end\n\n    blocking -.->|\"replace with\"|suspending\n\n    style blocking fill:#ffebee,stroke:#c62828,color:#1a1a1a\n    style suspending fill:#e8f5e9,stroke:#2e7d32,color:#1a1a1a\n    style B1 fill:#ffcdd2,stroke:#c62828,color:#1a1a1a\n    style B2 fill:#ffcdd2,stroke:#c62828,color:#1a1a1a\n    style B3 fill:#ffcdd2,stroke:#c62828,color:#1a1a1a\n    style S1 fill:#c8e6c9,stroke:#2e7d32,color:#1a1a1a\n    style S2 fill:#c8e6c9,stroke:#2e7d32,color:#1a1a1a\n    style S3 fill:#c8e6c9,stroke:#2e7d32,color:#1a1a1a","Thread.sleep vs delay: blocking a thread vs suspending a coroutine",{"id":150,"sort":151,"collection":152,"item":153},"c22fec24-9f9b-4672-a21c-3973dd8f7468",18,"block_callout",{"id":143,"type":154,"icon":30,"content":155,"width":47},"tip","\u003Cp>\u003Cstrong>Practical tips for getting started with Kotlin:\u003C\u002Fstrong>\u003C\u002Fp>\u003Cul>\u003Cli>\u003Cstrong>Start with tests.\u003C\u002Fstrong> Write new tests in Kotlin, don't touch existing Java.\u003C\u002Fli>\u003Cli>\u003Cstrong>One file at a time.\u003C\u002Fstrong> IntelliJ converts Java to Kotlin in one click.\u003C\u002Fli>\u003Cli>\u003Cstrong>Don't use everything at once.\u003C\u002Fstrong> Start with val\u002Fvar, data classes, null safety. Coroutines and DSL later.\u003C\u002Fli>\u003Cli>\u003Cstrong>Hybrid projects work.\u003C\u002Fstrong> Full Java-Kotlin interoperability. You can run both for years.\u003C\u002Fli>\u003Cli>\u003Cstrong>Listen to the compiler.\u003C\u002Fstrong> Kotlin's compiler is stricter and gives better hints than Java's.\u003C\u002Fli>\u003C\u002Ful>",{"id":157,"sort":158,"collection":159,"item":160},"edcc3f7b-6153-4bb8-aed6-f38bb75338dd",19,"block_quote",{"id":89,"text":161,"author":162,"source":68,"width":47},"\u003Cp>You don't need to rewrite the project. Kotlin was designed for seamless interoperability with Java — start writing new code in Kotlin and let both languages coexist.\u003C\u002Fp>","Summary",{"id":164,"sort":165,"collection":159,"item":166},"74b2ee83-0b74-4d1d-8558-da99ee1b0ed1",999,{"id":62,"text":167,"author":168,"source":169,"width":47},"\u003Cp>One of the most powerful and underestimated tools in a developer's toolkit is the delete key. The best code is no code. The second best is code so clear it explains itself. Kotlin's conciseness isn't about typing less — it's about saying more with less noise.\u003C\u002Fp>","Kevlin Henney","97 Things Every Programmer Should Know",{"id":171,"sort":165,"collection":43,"item":172},"2d7e9232-ac5e-4c4a-a27a-09b8585698ab",{"id":173,"content":68,"width":47},"59d41834-0be7-4347-90ec-88dcf9b6d167",{"id":175,"sort":165,"collection":176,"item":177},"c0b92cbf-29d2-423e-b688-566a71fa5edd","block_code_text",{"id":83,"code":178,"language":67,"filename":179,"text":180,"layout":181},"\u002F\u002F Java: The billion-dollar mistake\npublic String getDisplayName(User user) {\n    \u002F\u002F Every access could be null\n    if (user != null) {\n        if (user.getName() != null) {\n            return user.getName().toUpperCase();\n        }\n    }\n    return \"UNKNOWN\";\n}\n\n\u002F\u002F Kotlin: The compiler has your back\nfun getDisplayName(user: User?): String {\n    \u002F\u002F user?.name is null-safe by design\n    return user?.name?.uppercase() ?: \"UNKNOWN\"\n}\n\n\u002F\u002F Even better: sealed class for the result\nsealed class DisplayName {\n    data class Known(val value: String) : DisplayName()\n    data object Unknown : DisplayName()\n}\n\nfun displayName(user: User?): DisplayName {\n    val name = user?.name ?: return DisplayName.Unknown\n    return DisplayName.Known(name.uppercase())\n}","NullSafety.kt","\u003Cp>\u003Cstrong>Null safety isn't just \u003Ccode>?\u003C\u002Fcode> syntax.\u003C\u002Fstrong> It's a fundamentally different relationship with absence. In Java, null can appear \u003Cem>anywhere\u003C\u002Fem> — every reference is a potential bomb. In Kotlin, the type system tells you exactly where null is possible.\u003C\u002Fp>\u003Cp>The \u003Ccode>?:\u003C\u002Fcode> (Elvis operator) gives you a clear \"or else\" path. Sealed classes take it further by making the result type explicit — callers \u003Cem>must\u003C\u002Fem> handle both cases.\u003C\u002Fp>","code-left",{"id":183,"sort":165,"collection":176,"item":184},"91332a3f-0a86-4b1f-b125-8cb73ddccbb8",{"id":77,"code":185,"language":186,"filename":187,"text":188,"layout":189},"\u002F\u002F Java: 120+ lines for a simple POJO\npublic class User {\n    private String name;\n    private String email;\n    private int age;\n    private String role;\n\n    public User() {}\n\n    public User(String name, String email, int age, String role) {\n        this.name = name;\n        this.email = email;\n        this.age = age;\n        this.role = role;\n    }\n\n    public String getName() { return name; }\n    public void setName(String name) { this.name = name; }\n    public String getEmail() { return email; }\n    public void setEmail(String email) { this.email = email; }\n    public int getAge() { return age; }\n    public void setAge(int age) { this.age = age; }\n    public String getRole() { return role; }\n    public void setRole(String role) { this.role = role; }\n\n    @Override\n    public boolean equals(Object o) { \u002F* 20 lines *\u002F }\n\n    @Override\n    public int hashCode() { \u002F* 10 lines *\u002F }\n\n    @Override\n    public String toString() { \u002F* 5 lines *\u002F }\n}","java","User.java","\u003Cp>\u003Cstrong>Same data, different worlds.\u003C\u002Fstrong> The Java POJO on the left is 120+ lines of boilerplate — most of it mechanically generated. The Kotlin data class on the right expresses the same structure in 5 lines.\u003C\u002Fp>\u003Cp>Not only is it shorter, but the Kotlin version gives you immutability by default (\u003Ccode>val\u003C\u002Fcode>), proper \u003Ccode>equals\u003C\u002Fcode>\u002F\u003Ccode>hashCode\u003C\u002Fcode> semantics, a meaningful \u003Ccode>toString\u003C\u002Fcode>, and destructuring declarations — all for free.\u003C\u002Fp>","code-right",{"id":191,"sort":165,"collection":152,"item":192},"83ffc0e6-bde7-4329-8c2f-012570122ca2",{"id":119,"type":193,"icon":30,"content":194,"width":47},"success","\u003Cp>\u003Cstrong>The pragmatic target is 70-80% Kotlin.\u003C\u002Fstrong> Start with the domain layer — fewest dependencies, most to gain. Leave infrastructure in Java until you have a concrete reason to move it.\u003C\u002Fp>",{"id":196,"sort":165,"collection":144,"item":197},"09c1a688-b5ac-4add-9556-92deb950cfe6",{"id":56,"source_type":146,"mermaid_code":198,"image":30,"caption":199,"width":47},"flowchart LR\n    subgraph Priority 1\n        A[Domain Layer]\n    end\n    subgraph Priority 2\n        B[Application Layer]\n    end\n    subgraph Priority 3\n        C[Infrastructure Layer]\n    end\n    \n    A -->|Highest ROI| B\n    B -->|Moderate effort| C\n    \n    A -.- D[Entities\\nValue Objects\\nDomain Events]\n    B -.- E[Use Cases\\nServices\\nControllers]\n    C -.- F[Configuration\\nSecurity\\nPersistence]\n    \n    style A fill:#059669,color:#fff\n    style B fill:#d97706,color:#fff\n    style C fill:#dc2626,color:#fff","Migration priority: Start with domain logic, leave infrastructure for last",{"id":201,"sort":165,"collection":43,"item":202},"0937c6f9-d1c2-400d-ac7c-e21d871863c8",{"id":203,"content":204,"width":47},"728a9d97-90c9-4627-8548-8092bf7d17a1","\u003Ch2>The annoying\u003C\u002Fh2>\u003Cp>Not everything is smooth:\u003C\u002Fp>\u003Cul>\u003Cli>\u003Cstrong>Lombok doesn't work\u003C\u002Fstrong> — every \u003Ccode>@Data\u003C\u002Fcode>, \u003Ccode>@Builder\u003C\u002Fcode>, \u003Ccode>@Value\u003C\u002Fcode> needs a manual Kotlin equivalent\u003C\u002Fli>\u003Cli>\u003Cstrong>Checked exceptions are gone\u003C\u002Fstrong> — freeing, until you realize you've lost the compiler's help remembering which methods throw\u003C\u002Fli>\u003Cli>\u003Cstrong>Frameworks expect Java\u003C\u002Fstrong> — Spring, Hibernate, and most annotation processors were designed for Java. Some behave differently in Kotlin.\u003C\u002Fli>\u003C\u002Ful>",{"id":206,"sort":165,"collection":63,"item":207},"7fc62bcc-9423-4458-ab94-50877a3c42d3",{"id":101,"code":208,"language":67,"filename":30,"highlight_lines":30,"width":47},"\u002F\u002F Java: 120+ lines for a simple POJO\npublic class User {\n    private String name;\n    private String email;\n    private int age;\n    private String role;\n\n    public User() {}\n    public User(String name, String email, int age, String role) { ... }\n    public String getName() { return name; }\n    public void setName(String name) { this.name = name; }\n    \u002F\u002F ... 80+ more lines of getters, setters,\n    \u002F\u002F     equals, hashCode, toString\n}\n\n\u002F\u002F Kotlin: same thing in 5 lines\ndata class User(\n    val name: String,\n    val email: String,\n    val age: Int,\n    val role: String\n)",{"id":210,"sort":165,"collection":43,"item":211},"e2bff24f-295c-4fb3-982e-d7de41b70a3b",{"id":212,"content":213,"width":47},"312bdc99-bccf-4ba2-a8f7-32899d31ed91","\u003Ch2>The good\u003C\u002Fh2>\u003Cp>Three features justified the migration:\u003C\u002Fp>\u003Cul>\u003Cli>\u003Cstrong>Null safety\u003C\u002Fstrong> — NullPointerException incidents dropped 90%. The type system forces you to think about absence explicitly.\u003C\u002Fli>\u003Cli>\u003Cstrong>Data classes\u003C\u002Fstrong> — What was a 120-line Java POJO becomes 5 lines of Kotlin. No getters, setters, equals, hashCode boilerplate.\u003C\u002Fli>\u003Cli>\u003Cstrong>Sealed classes\u003C\u002Fstrong> — The compiler ensures your \u003Ccode>when\u003C\u002Fcode> expressions cover every case. No more \"forgot the error state\" bugs.\u003C\u002Fli>\u003C\u002Ful>",{"id":215,"sort":165,"collection":43,"item":216},"943aaf90-e8e4-4fbd-9cca-e8a77c7295a1",{"id":217,"content":218,"width":47},"ed827a72-469f-4d92-b4f7-7108a0a87d33","\u003Cp>We migrated three production services from Java to Kotlin. Here's what we learned — beyond the syntax changes every tutorial covers. This isn't a \"Kotlin is better\" pitch. It's an honest field report.\u003C\u002Fp>",{"id":220,"sort":165,"collection":152,"item":221},"ce50d29d-3240-4689-b9d4-12ac8fb22189",{"id":95,"type":193,"icon":30,"content":68,"width":47},{"id":223,"sort":165,"collection":144,"item":224},"e90018b6-bae4-454a-aacc-0576ca99c1ce",{"id":56,"source_type":146,"mermaid_code":198,"image":30,"caption":199,"width":47},{"id":226,"sort":165,"collection":43,"item":227},"4ee3c85a-b08a-430f-a715-c2c9bc38765a",{"id":228,"content":68,"width":47},"72a9bb7a-7781-4f9b-ad83-56bca5ee33a4",{"id":230,"sort":165,"collection":152,"item":231},"ab2e6a38-7f20-4380-9dcf-fe5cd35d764a",{"id":89,"type":232,"icon":30,"content":68,"width":47},"warning",{"id":234,"sort":165,"collection":43,"item":235},"774ce641-3b1f-42cb-b66c-a6cee661b0f2",{"id":236,"content":68,"width":47},"8c2594c8-1c2d-4a12-afce-d379e41be705",{"id":238,"sort":165,"collection":43,"item":239},"f7477375-4504-4de4-8401-720eed00ad6d",{"id":240,"content":68,"width":47},"78caf9c8-bb38-43d2-9dd1-70e2c06a764c",{"id":242,"sort":165,"collection":152,"item":243},"9a39c429-3eca-40df-b602-8715949cb98d",{"id":83,"type":244,"icon":30,"content":68,"width":47},"info",{"id":246,"sort":165,"collection":43,"item":247},"4ee22b6d-a703-4920-8b20-7dee8c4b5301",{"id":248,"content":68,"width":47},"a727b323-a724-4acb-adde-c6754ac6b53b",{"id":250,"sort":165,"collection":43,"item":251},"4f9826d7-3d15-49e1-8dea-d8605c6356b2",{"id":252,"content":68,"width":47},"eeccb47a-1423-4ed6-a627-ff6f556b5ebb","https:\u002F\u002Fd1.resultcrafter.com"]