Blog
No items found.
8
minutes

Feedback - Kotlin on the server-side - the good, and the bad

I've used Java as my primary server-side programming language for years. Before joining Qovery, I worked at SAP, where Java was the default choice to bootstrap new projects. Besides its known limitations and cumbersomeness.
September 26, 2025
Patryk Jeziorowski
Software Engineer
Summary
Twitter icon
linkedin icon

JVM ecosystem, decent performance, and stability make Java a common choice in enterprises. From a developer's point of view, all of these come with the cost of working with old language, lacking some of the essential features that newer languages provide (which Java tries to fix with the recent changes and more rapid, 6-month release cadence).

Convincing colleagues to consider a new language (like Kotlin) was impossible, especially after our negative experiences with Scala. That's the main reason why my experience with Kotlin before changing the job was limited to playing with it in micro side projects and reading articles.

In April, I started working at Qovery, a container as a service platform where a considerable part of the backend code is in Kotlin. In this post, I'll describe the last months of my journey from Java to Koltin, my experiences and thoughts on this process, and Kotlin as a server-side programming language.

From a developer experience point of view, Kotlin is a significant improvement, especially if you jump from 'old' Java - Patryk J.

My feedback

From Java to Kotlin

For me, a Java developer with basic knowledge of other languages (like JS, Go, and other popular programming languages), moving from Java to Kotlin was natural. It was more like a gradual transition/upgrade rather than jumping into something new. Most of the right parts from Kotlin that are missing in Java can be found in other programming languages. Suppose you are not limited to just one language (Java). In that case, there will be little in Kotlin that will surprise you (for the people thinking the transition from Java to Kotlin is hard - I recommend trying to use Rust without reading the Rust Bible twice - you'll change your mind quickly.).

Reading/Writing Kotlin code

Reading Kotlin code came without any effort. If you can't guess what the 'new' syntax means, all you need to do is take a quick look at the basic syntax page, and you are good to go. Writing idiomatic Kotlin code, however, is a bit trickier. After years of using Java, you may have habits that will lead you to write Java-like Kotlin code. Writing idiomatic code, at least in my case, requires more time spent with the language. I found this document invaluable to speed up this process.

How did I ramp up on Kotlin?

Getting ready to use Kotlin did not require more than spending a few days to get used to the codebase I would later work with and slowly read the Kotlin documentation in the meantime. This gives you all the basics to be productive with the language - proficiency and expertise will come with time.

After a few months

After switching from Java to Kotlin, my first impressions were that I deal with a modern programming language. Pascal notation, type inference, extension functions, no semicolons, immutable collections, coroutines, channels, and more - this all added to a feeling of using a powerful, modern language (even though most of the concepts are pretty old, borrowed from other languages, you can see similar trends in other new, modern programming languages).

Another thing that I could quickly notice is the improvement in code readability (in the majority of cases) and conciseness. The most striking, simple examples are, of course, data classes and null checks, which cost much more work (and lines of code) in Java.

Data Classes (Kotlin vs. Java):

Kotlin

data class Person(var name: String, var surname: String, var id: String)

Java

public class Person {
private String name;
private String surname;
private String id;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getSurname() {
return surname;
}
public void setSurname(String surname) {
this.surname = surname;
}
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
@Override public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Person person = (Person) o;
if (name != null ? !name.equals(person.name) : person.name != null) return false;
if (surname != null ? !surname.equals(person.surname) : person.surname != null)
return false;
return id != null ? id.equals(person.id) : person.id == null;
}
@Override public int hashCode() {
int result = name != null ? name.hashCode() : 0;
result = 31 * result + (surname != null ? surname.hashCode() : 0);
result = 31 * result + (id != null ? id.hashCode() : 0);
return result;
}
@Override public String toString() {
return "Person{" +
"name='" + name + ''' +
", surname='" + surname + ''' +
", id='" + id + ''' +
'}';
}
}

Null checks (Kotlin vs. Java):

Kotlin

fun main() {
val outer = OuterClass(null)
print(outer.inner?.someValue ?: “DEFAULT VALUE”)
}

Java

public class Example {

public static void main(String[] args) {
OuterClass outer = new OuterClass(null);
if (outer.inner != null && outer.inner.someValue != null) {
System.out.println(outer.inner.someValue);
} else {
System.out.println(“DEFAULT VALUE”);
}
}

}

After working with the language, I noticed another thing - the null safety, does make the difference. It does not solve all the issues with NPEs, but it drastically reduces this problem. Most of the time, when I get NPE, I intentionally cut corners due to my laziness. After the code is ready to go to production, it's improbable to throw an NPE.

Java-Kotlin interop

Java-Kotlin interop is excellent. You can use anything you would use in plain Java. Sometimes you may fall into a pitfall; you need to take care of nulls (they may come from Java libraries, which are not null-safe) or may need to use a plugin to make a given framework work properly. Still, in general, it's a very good interop between two languages. You can even copy-paste Java code, and IntelliJ will convert it to Kotlin on the fly - it works pretty well; most often, it produces working code - from time to time, you may need to correct something manually.

Automatic Java -> Kotlin code conversion

The good and the bad

Why you should consider it

The list of good things about Kotlin is very long. I'll name just a few that are my favorite, and those alone should justify at least considering using Kotlin for server-side programming on the JVM:

  • null is a different type - String (non-null) and String? (nullable String) are two different styles in Kotlin. If you don't mess with the compiler, you are pretty much safe from null pointer exceptions. The fewer 500 InternalServerErrors due to NPE, the better!
  • Extension Functions - Kotlin allows you to add new functions to any type (e.g., the String class). It's just syntactic sugar that replaces Java's static util functions, making the code much more readable.
fun String.myOwnStringFunction() = this.toUpperCase() + " | Made with Extension Function"

fun main() {
print("HelloWorld!".myOwnStringFunction()) // OUTPUT: HELLOWORLD! | Made with Extension Function
}
  • Functional programming support - default immutability in collections, value objects, higher-order functions, useful helper functions in the standard library - FP support in Kotlin is much better than in Java.
  • Type inference - most often, you don't need to provide the variable type - the compiler is smart enough to know. One could say it's the same in Java since Java 10, but Kotlin took it to the next level - it can infer method return type or class property type, which is not the case in Java.
class Example {
fun functionReturningString() = "NO RETURN TYPE SPECIFIED"
}

The dark side

Moving all language specificities aside, the thing that disappoints me the most is IntelliJ's performance while working with Kotlin. Code completion, syntax highlighting, refactoring - I can always feel a slight delay while working with Kotlin. The problem is not existing while working with any other language (besides the performance, JetBrains IDE works excellent with the language).

Renaming method parameter in Kotlin (pay attention to how long the red frame around the variable name after renaming action lasts):

renaming in kotlin with intellij

There are a few language design choices that some people dislike and/or find disappointing. Lack of possibility to extend data classes, lack of Java-like static methods & fields, classes that are final by default (which is inconvenient while using specific frameworks) - this kind of thing is bugging some people. For me, these are not critical and certainly do not outweigh all the pros that come with using Kotlin on the server. However, you should be aware that Kotlin, just like any other tool, is not perfect.

Final thoughts

From a developer experience point of view, Kotlin is a significant improvement, especially if you jump from 'old' Java (by old, I mean 8, which is still very common in big enterprises). By using Kotlin, you end up writing less code. The code is more readable and safer - which is critical in the backend, server-side applications. The transition from Java to Kotlin is very smooth - you can quickly become productive and develop your expertise with time. You don't need to learn new frameworks, as the same frameworks and libraries you have used before with Java do work with Kotlin as well. If you want to improve your server applications on the JVM - you should give Kotlin a try.

Share on :
Twitter icon
linkedin icon
Tired of fighting your Kubernetes platform?
Qovery provides a unified Kubernetes control plane for cluster provisioning, security, and deployments - giving you an enterprise-grade platform without the DIY overhead.
See it in action

Suggested articles

Kubernetes
8
 minutes
Kubernetes management in 2026: mastering Day-2 ops with agentic control

The cluster coming up is the easy part. What catches teams off guard is what happens six months later: certificates expire without a single alert, node pools run at 40% over-provisioned because nobody revisited the initial resource requests, and a manual kubectl patch applied during a 2am incident is now permanent state. Agentic control planes enforce declared state continuously. Monitoring tools just report the problem.

Mélanie Dallé
Senior Marketing Manager
Kubernetes
6
 minutes
Kubernetes observability at scale: how to cut APM costs without losing visibility

The instinct when setting up Kubernetes observability is to instrument everything and send it all to your APM vendor. That works fine at ten nodes. At a hundred, the bill becomes a board-level conversation. The less obvious problem is the fix most teams reach for: aggressive sampling. That is how intermittent failures affecting 1% of requests disappear from your monitoring entirely.

Mélanie Dallé
Senior Marketing Manager
Kubernetes
 minutes
How to automate environment sleeping and stop paying for idle Kubernetes resources

Scaling your deployments to zero is only half the battle. If your cluster autoscaler does not aggressively bin-pack and terminate the underlying worker nodes, you are still paying for idle metal. True environment sleeping requires tight integration between your ingress layer and your node provisioner to actually realize FinOps savings.

Mélanie Dallé
Senior Marketing Manager
Kubernetes
DevOps
6
 minutes
10 best Kubernetes management tools for enterprise fleets in 2026

The structure, table, tool list, and code blocks are all worth keeping. The main work is fixing AI-isms in the prose, updating the case study to real metrics, correcting the FAQ format, and replacing the CTAs with the proper HTML blocks. The tool descriptions need the "Core strengths / Potential weaknesses" headers made less template-y, and the intro needs a sharper human voice.

Mélanie Dallé
Senior Marketing Manager
DevOps
Kubernetes
Platform Engineering
6
 minutes
10 best Red Hat OpenShift alternatives to reduce licensing costs

For years, Red Hat OpenShift has been the safe choice for heavily regulated, on-premise environments. It operates as a secure fortress. But in the public cloud, that fortress acts as an expensive prison. Paying proprietary per-core licensing fees on top of your standard AWS or GCP compute bill is a redundant "middleware tax." Escaping OpenShift requires decoupling your infrastructure from your developer experience by running standard, vanilla Kubernetes paired with an agentic control plane.

Morgan Perry
Co-founder
AI
Product
3
 minutes
Qovery Skill for AI Agents: Deploy Apps in One Prompt

Use Qovery from Claude Code, OpenCode, Codex, and 20+ AI Coding agents

Romaric Philogène
CEO & Co-founder
Kubernetes
 minutes
Stopping Kubernetes cloud waste: agentic automation for enterprise fleets

Agentic Kubernetes resource reclamation is the practice of using an autonomous control plane to continuously identify, suspend, and delete idle infrastructure across a multi-cloud Kubernetes fleet. It replaces manual cleanup and reactive autoscaling with intent-based policies that act on business state, eliminating the configuration drift and cloud waste typical of unmanaged fleets.

Mélanie Dallé
Senior Marketing Manager
Platform Engineering
Kubernetes
DevOps
10
 minutes
What is Kubernetes? The reality of Day-2 enterprise fleet orchestration

Kubernetes focuses on container orchestration, but the reality on the ground is far less forgiving. Provisioning a single cluster is a trivial Day-1 exercise. The true operational nightmare begins on Day 2. Teams that treat multi-cloud fleets like isolated pets inevitably face crushing YAML configuration drift, runaway AWS bills, and severe scaling bottlenecks.

Morgan Perry
Co-founder

It’s time to change
the way you manage K8s

Turn Kubernetes into your strategic advantage with Qovery, automating the heavy lifting while you stay in control.