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**.

---
### 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

📄 **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).
---