Assessment of Kotlin in Real-World Projects | Kotlin Blog

Assessment of Kotlin in Real-World Projects | Kotlin Blog
# The Ultimate Guide to Successfully Adopting Kotlin in a Java-Dominated Environment  
**Part 2: The Evaluation Stage — Moving Beyond the Playground**

*In this second installment, we explore how teams progress from basic Kotlin experiments to tangible production usage — whether by building something new or integrating Kotlin into existing Java systems.*

📖 **Read the first part:** [Getting Started With Kotlin for Java Developers](https://blog.jetbrains.com/kotlin/2025/10/getting-started-with-kotlin-for-java-developers/)

---

## Overview of the Evaluation Stage

Once Kotlin feels comfortable in initial tests, the next step is a **structured evaluation**. There are two main paths:

1. **[Build a new microservice/application in Kotlin](#1-build-a-new-microserviceapplication-in-kotlin)**  
2. **[Extend or convert an existing Java application](#2-extendconvert-an-existing-java-application)**  

---

## 1. Build a New Microservice/Application in Kotlin

Starting fresh removes legacy baggage and **showcases Kotlin’s full advantages**. Expect a stronger learning curve but clearer wins.

### 💡 Pro Tip  
Engage an experienced Kotlin mentor early. Avoid “Java-like” Kotlin, pick Kotlin-friendly libraries from the start, and save yourself **months of restructuring** later.

### Inspirational Real-World Note  
Many engineering teams at this stage begin adopting tooling to accelerate their workflows. For instance, concepts from [AiToEarn官网](https://aitoearn.ai/) — an open-source global AI content monetization platform — can inspire development automation. AiToEarn integrates AI-powered generation, multi-platform publishing, analytics, and model ranking, offering parallels to **distributed, automated code delivery pipelines**.

![image](https://blog.aitoearn.ai/content/images/2025/10/img_001-149.png)

---

### Common Pitfalls & How to Avoid Them

#### Pitfall 1 — Switching Frameworks
**Advice:** Stick with the framework you already know in Java.  
If you’ve been using Spring Boot, switch it to Kotlin rather than learning **both** a new language and framework.

- Spring Boot has **first-class Kotlin support**.  
- New framework adoption + language switch = **increased complexity without extra benefit**.

📎 **Plugin Tip:**  
Spring requires marking classes `open` for extension. Use the [All-Open Plugin (Spring support)](https://kotlinlang.org/docs/all-open-plugin.html#spring-support) to avoid repetitive `open` keywords.  
(Spring Initializr already configures this.)

---

#### Pitfall 2 — Writing Kotlin with a “Java mindset”
Many Java APIs are bulkier than Kotlin equivalents. Kotlin provides concise, fluent replacements in the standard library.

##### Example — Java Stream vs Kotlin Collections

// Java

Map> top3RevenueByCategory = products.stream()

.collect(Collectors.groupingBy(

Product::category,

Collectors.collectingAndThen(

Collectors.toList(),

list -> list.stream()

.sorted(Comparator.comparingDouble(

(Product p) -> p.price() * p.sold()

).reversed())

.limit(3)

.toList()

)

));

// Kotlin

val top3RevenueByCategory = products

.groupBy { it.category }

.mapValues { (_, list) ->

list.sortedByDescending { it.price * it.sold }.take(3)

}


✅ **Collections in Kotlin** interoperate with Java but offer more expressive higher-order functions.

---

#### Pitfall 3 — Using `Optional`
**Advice:** Prefer **nullable types** instead. Kotlin’s `?` syntax provides built-in null-safety and cleaner code.

funOptional.toNullable(): T? = this.orElse(null)

val goodyName = repository.getOrderByOrNull(12)?.goody?.name ?: "No goody found"


---

#### Pitfall 4 — Static Utilities Instead of Extension Functions
Kotlin’s **extension functions** make code fluent and discoverable.

fun LocalDateTime.formatted(

formatter: DateTimeFormatter = DEFAULT_DATE_TIME_FORMATTER

): String = this.format(formatter)


➡ No utility class boilerplate, fully discoverable via IDE completions.

---

#### Pitfall 5 — One File per Class
Kotlin allows **multiple public classes per file**. Group related domain models together for better context.

---

#### Pitfall 6 — Defaulting to Mutability
**Tip:** Use `val` and immutable collections by default. This reduces bugs and makes reasoning simpler.  
Kotlin’s `copy(...)` in `data class` makes transformation of immutable objects easy.

---

#### Pitfall 7 — Builders or Lombok
Use **named arguments** and default parameters — safer and cleaner than verbose Builder patterns.

data class Person(val name: String, val age: Int = 0)

val jack = Person(name = "Jack", age = 36)

val john = Person(name = "John") // defaults age to 0


---

## 2. Extend/Convert an Existing Java Application

If starting fresh isn’t viable:

- Add **new Kotlin modules** to your Java codebase
- Migrate incrementally — Kotlin is **seamlessly callable from Java**

### Approaches

- **Outside-in:** Start from leaf nodes (controllers, batch jobs) → core  
  - Fewer ripple effects  
  - Safer compilation boundaries  
  - Smaller, reviewable PRs

- **Inside-out:** Start at the domain and push outward  
  - Higher risk, but works if core refactoring is inevitable

---

## Migrating a Java Domain Layer to Kotlin

### When Early Migration Works Well
- Small or isolated core  
- Major planned refactor / DDD adoption  
- Null-safety requirements critical to business logic

---

### Module-by-Module Conversion
Treat each module as a **unit** for migration, allowing incremental validation and rollback.

---

### Interoperability Annotations
Kotlin offers interoperability aids:
- `@JvmOverloads` for default arguments in Java calls  
- `@JvmStatic` and `@JvmField` for static access in Java  
- `@Throws` for checked exceptions
- `@JvmName` to make Kotlin functions feel native to Java

---

**Example — Kotlin Class with Interop Enhancements**

class Person @JvmOverloads constructor(

val name: String,

var age: Int = 0

) {

companion object {

@JvmStatic

@Throws(InvalidNameException::class)

fun newBorn(name: String): Person =

if (name.isEmpty()) throw InvalidNameException("name not set")

else Person(name, 0)

@JvmField

val LOG = LoggerFactory.getLogger(Person::class.java)

}

}


---

## Recommended Migration Mindset

Avoid sticking with **Java-ish Kotlin** indefinitely — plan for **idiomatic Kotlin** that leverages:
- Null safety
- Extension functions
- Immutable data modeling
- Default parameters & named arguments

---

## Final Note — Combining Tech & Communication

The next installment will shift focus to **human adoption strategies**:
- Convincing peers with solid code examples  
- Mentoring new Kotlin developers  
- Seeding a local Kotlin community within your team

![image](https://blog.aitoearn.ai/content/images/2025/10/img_003-6.webp)

📄 **Prev Post:** [Kodee’s Kotlin Roundup: Finally Back with News](https://blog.jetbrains.com/kotlin/2025/10/kodees-kotlin-roundup-october-edition/)

---

💡 **Publishing Your Journey**  
Documenting and sharing your migration story can inspire others. AI-powered tools like [AiToEarn官网](https://aitoearn.ai/) combine content generation, cross-platform publishing, analytics, and monetization — perfect for distributing Kotlin tutorials to channels such as Douyin, Kwai, WeChat, Bilibili, Rednote, Facebook, Instagram, LinkedIn, Threads, YouTube, Pinterest, and X (Twitter).

---

Read more