Detekt – A static code analyzer for Kotlin
detekt.dev
detekt.dev
Personally, I quite like Kotlin, but I haven't been able to convince most of my greybeard colleagues to make the leap.
The language ends up being more complex, but I find it a joy to use; going "back" to Java projects always leaving me wishing I could use Kotlin instead :P
Yet it's true, Java has improved quite a lot in the last decade. It is ahead of Kotlin in a few things, like pattern matching (though not incredible, it's still better than the nigh nonexistent support in Kotlin).
It’s ‘advanced’ kotlin in there for sure, and takes some learning of the internal plumbing, but having everything not hidden behind annotations has been great.
Just a CMD+click on whatever Ktor DSL/plugin API your using and you can immediately start to follow along / debug what it’s actually doing.
Netty is also a struggle to inspect so perhaps I just struggle with deep abstraction in general? I find code to be far better documentation than actual documentation, particularly when so much of it is along these lines:
/**
* Gets the next string.
*
* @return Returns the next string, or null.
*/
public abstract @Nullable String getNextString();
This documentation is entirely unnecessary because everything it says is present within the method signature. But say you have an abstract method that users override to handle incoming data, often documentation will not contain things like: whether the ByteBuffer is a slice or the whole buffer at a particular offset; or whether the buffer is a copy or a view; etc. So I end up doing a lot of defensive copying which is possibly unnecessary, but because it's very difficult to figure out where that buffer is coming from without first trudging through a seemingly endless forest of abstractions first.But again, could be wrong about Ktor specifically.
I'm of two minds about it.
I started working with Kotlin back when Java was still a very ~~stagnant~~ stable language. I definitely find Kotlin's syntax to be much more comfortable, expressive, and in many ways much more simple than Java's.
But at this point, the only big technical features that still put Kotlin over Java for me is the handling of nulls by the type system (which is, admittedly, mitigated decently well in practice by Java tooling configurations and ugly annotations), and value types (zero heap-allocation wrapper classes).
Another thing to keep in mind is that now that Java is actually adding features again, the Kotlin developers will have to also play "catch-up" to Java in ways that it didn't have to when it first gained popularity. It puts Kotlin in an awkward spot when Java's implementation of sealed classes and interfaces was initially better and more flexible than Kotlin's, or when Java's `switch` is now more powerful than Kotlin's `when`, etc.
Kotlin is also betting heavily on their multi-platform stuff, which I'm skeptical about. It seems to me that it will further slow the ability to add useful features to the language if you have to cater to the lowest-common-denominator between Java and JavaScript (and Objective-C? -is that how it works for iOS?) runtimes. Instead of being the best language for a given platform, it'll just be a pretty good language for a few platforms. I wish them all the best in dethroning JavaScript from infecting every computing platform and domain, but I'm just skeptical.
So, honestly, I don't actually recommend people start new projects in Kotlin. I'd suggest going for Java, or something that's meaningfully different in semantics and philosophy like Clojure or Scala. I say this, but I'm not actually sure that I'd be able to follow my own advice, because I really don't want to have to stomach Java's syntax, idioms (way too many annotations), and stupid null handling.
Soon (tm): https://openjdk.org/jeps/8303099
The one feature that keeps me in Java, albeit not popular, is checked exceptions. I far prefer checked errors over checked nulls if I have to make a choice.
https://news.ycombinator.com/item?id=44432640
It’s actually my #1 issue. I hate not knowing about error conditions and no one in Java uses checked exceptions because the language syntax for dealing with them sucks. Brian had a proposal for handling exceptions in switch but it seems to have died in the water.
Part of me secretly hopes Swift takes over the world because they have a typed throws that works and handling errors is a breeze.
let i: Int?
do {
y = try someThrowingFunc()
} catch SomeError.err {
y = nil
} catch SomeError.youCareAbout {
//
}This has consequences for config parsing, for example, where a particular subsection (sub-object? keyed struct?) may be optional, so it being missing is perfectly legal. But if you use try?, then there's no way to distinguish between it simply being missing and it being present but malformed. It unfortunately seems like the only other options in Swift is to propagate the error, or revert back to verbose try-catch blocks.
Whereas, in Zig, you can do inline catching and care about the error only as much as you want to, for example:
// This is equivalent to Swift's try
const i: ?i32 = try someThrowingFunc();
// This is equivalent to Swift's try?
const i: ?i32 = someThrowingFunc() catch null;
// This is a yielding inline catch block that does not care about the error
const i: ?i32 = someThrowingFunc() catch brk: {
logger.warn("Something went wrong!");
break :brk null;
}
It's not perfect, I don't love the block-label-break thing, but I much prefer this if only because then the variable can be defensively const, which I do not believe is possible with the kind of code snippet you provided. Also, instead of breaking out, it could capture and return the error or return a new error. It's incredibly versatile.But, I'd still rather have safe/correct null type checking, because at the end of the day, I can always write MY code to return a Result/Try/Either type instead of throwing unchecked exceptions for expected business logic "failure" paths.
One could rightly debate over whether Kotlin's coroutines design and APIs are better than Java's virtual threads for writing asynchronous code. But, at the core, the story used to be that Kotlin had coroutines and lightweight structured concurrency "built-in" (with a blessed first-party helper library for the actual concurrency part) and Java did not have anything that accomplished the same goals. Now it does.
On the other hand, when I see Kotlin code that's not written by JetBrains (especially on the backend), it often does just look like Java code with cleaner syntax...
Many larger companies in The Netherlands have moved away from Scala and Java and use Kotlin now. The switching costs are neglegible and the benefits are big.
The problem with Kotlin is, you don’t want to go back to Java.
I like to avoid mixing Java/Kotlin within the same module when I can, but it still works, and parts of our codebase are mixed this way. (by module I mean e.g. the same Maven or Gradle module, i.e. try to avoid a situation where you have a `src/main/java` and `src/main/kotlin` next to each other)
I'd say it's nice and I would actually start every new (spring/java) project I do in kotlin now. But for existing projects, I actually see no value. It does not magically make code easier to understand and it doesn't reduce bugs, it's just a bit more fun and challenging to write and pads your resume...
We did have one issue where upgrading the kotlin version from 1.x to 2.x in a project broke all depending projects due to a classfile version issue though. I deferred it by just forcing the version back to 1.x. That's the kind of annoyance you can encounter.
There is almost no locking with Kotlin. You can stop writting Kotlin code any time and start writting Java code.
However I think it's not possible to call coroutine code from Java.
It's extremely close to Java in terms of most concepts. That makes it an easy language to switch into and out of. It's java with a nicer syntax and more sugar.
Write a Kotlin multiplatform client side business logic module in tandem with your Kotlin backend. The multiplatform module compiles for both your Android and Apple environments and for extra flexibility you are able to quickly port code from the client business logic module to the backend (or vice versa).
Java is getting better, so maybe in 10 years it'll be a less ergonomic Kotlin variant.
PS: Jetbrains - the company behind Kotlin - started a strategic partnership with the Spring team, to make the combo Kotlin-Spring even better: https://blog.jetbrains.com/kotlin/2025/05/strategic-partners...
PSS: Jetbrains is actually working on an official Kotlin-lsp server: https://github.com/Kotlin/kotlin-lsp So devs in the near future won't be locked into the IntelliJ ecosystem, if that is a concern of yours.
coroutines are the biggest downside imo. they're great for android and other environments, but now that we've got loom on the jvm they're needlessly complex (accidental blocking calls, coloring, headaches with libraries that use thread locals, reentrant lock, etc.)
Come in; the water is fine.
There are serious headaches to be had when you have to maintain software for ten years and up.
Porting systems to another language is often terribly expensive.
Working with younger people exposes that quite mercilessly and I can recommend doing that if you catch yourself doing a lot of repetitive projects with people well over 40. Try something new if you want to stay relevant. Move on if your colleagues can't deal with that. It's only going to get worse with them.
I've been using Kotlin with Spring boot for backend development since 2018. I did Java before that; since 1995. Java has indeed improved and was probably nudged along by what Kotlin and Scala and other languages have done. But it hasn't really caught up properly IMHO. Everything Kotlin does right out of the box (intentionally, to fix what Java did wrong here), Java continues to do wrong out of the box and this is actually a big deal because it does a lot of important stuff wrong mainly for compatibility reasons. That's the big biggest difference. These are things that Java cannot fix in a backwards compatible way. That alone is a good reason to switch.
Examples of this are that it makes code less null safe, non final, introduces unnecessary mutable state, etc. Kotlin allows you to do these things when you need to but it defaults to things being closed, final, val (instead of var), etc. And these are just things that Kotlin has been doing right since it's first releases. It has progressed a lot since then.
If you like verbosity and don't want access to the countless convenient extension functions, DSLs, co-routine support, etc. that e.g. Spring ships for Kotlin, go for Java. In they end you kind of do the same things. But IMHO in a needlessly verbose and clumsy way.
But you are missing out. IMHO anything to do with Spring Flux is just an order of magnitude easier via Kotlin and co-routines and IMHO even attempting to use that from Java is misguided. But in Kotlin, you can hardly tell the difference with non reactive code. With Java, this turns into a mess of leaky abstractions, function chaining, and lots of boiler plate. Our server is effectively using only a handful of threads with lots of websockets, connections, background processing etc. happening.
Reactive/async stuff across all of the mainstream Java server frameworks is well supported from Kotlin and has been for many years. E.g. the co-routines library ships with lots of extension functions for just about anything you can name. The green thread stuff Java does is often cited as a thing that closes the gap. But from a Kotlin point of view it's just something that makes using legacy Java code a bit less tedious.
Jetbrains and Spring did some joint announcements at the latest Kotlin conf. The upcoming major release of Spring Boot is going to be even more Kotlin heavy and centered than the current one is. And honestly, you had a superior experience with v1 way back even. They won't abandon Java support. But at this point, you are really missing out if you are sticking with Java. There are non technical reasons for doing that but very few (if any) technical reasons.
They are doing a big push with the new release on making nullability checks on all the Spring Java stuff stricter for Kotlin and making sure Kotlin does the right things. And all their documentation was already dual Kotlin/Java but looks like it might be leaning towards Kotlin first going forward.
Many reasons to like Kotlin. If somebody tells you it's Android only that was never true and there's a lot of quiet Kotlin usage with anything Java because there's not a single Java framework that you can't seamlessly use from Kotlin. And almost universally it's a better experience. The older the better actually. Because the old one tend to not use any of the new Java features. Where new is introduced in the last 15 or so years.
don't use ktor etc where some people wanna take their scala religion and put into kotlin.
kotlin is nice is you keep is super simple. use it the same way as you do Golang and you will find it pleasurable.
PROFIT!
And, JetBrains finally started working on an open language server. It's not perfect but it makes it bearable to at least edit Kotlin in Cursor/VS Code.
https://plugins.jetbrains.com/plugin/27310-claude-code-beta- https://github.com/Kotlin/kotlin-lsp
I would never voluntary do any editing in VS Code or forks of it. It is just so slugish on large files. Plus there is always subtle things that annoy me, I can't even describe it, it just feels off. I hope Zed takes off as it is a bit more tolerable though still not there yet.
Its great for DSLs though.
No trailing lambdas, no infix operators, no @DslMarkers, no top level functions, and an infinite list of examples of Java verbosity making even the smallest thing look like an ancient greek epic. Java is utterly terrible for DSLs.
Language {
oh {
yes {
Java {
isSoNice
}
}
}
You can contrive awfulness in any language. building(of(S("expressions"),everything())No template, as it's specific to my team's project, but one example is that we enforce that classes within our core domain don't import types from outside the project. You could accomplish this with separate projects, but that comes with its own complexities. This rule basically says "any domain type shall not import a non-domain type".
and here's the diff for a 'real world' rule I implemented: https://github.com/michaelbull/kotlin-result/compare/master....
Sometimes people post the same link to HN for weeks, and nothing happens. Suddenly after 10th try, trend catches up, and the same link is on the main page.