Hi folks!
This is an article I've wanted to write for quite some time now. I've seen my fair share of Kotlin and also used it in production. Kotlin is a JVM-based language developed by Jetbrains, known for their IDEs and developer tools. Kotlin is very interesting from a language perspective, it has many merits, but also some pitfalls and awkward design choices. Let's get right to it!
The Good
I could fill entire books about the things I like about Kotlin. Here is a run-down of higlights.
Semicolons are optional
Not "optional" as in "well most of the time it works without them, just sometimes it makes a difference" but as in "use them or drop them, the program won't change". Less clutter in code, and my wrist is really happy about this.
NULL is a separate type
String
(non-null
String) and String?
(nullable String) are two distinct types in Kotlin. If you stick to Kotlin methodology, your programs are unable to throw the dreaded NullPointerException
s at you.
Extension functions
Did you ever want a null
-safe variant of myStringVariable.isNullOrEmpty()
in Java? Well, Kotlin has you covered! You can write static methods (with certain signatures) and call them as if they were regular member methods. This also marks the death of the dreaded StringUtils
class full of static methods. All of these can be extension methods now!
Flow Typing & Type Inference
For all local variables, you just use var
and the compiler will figure out the type on its own. Java 10 has that too, but with Kotlin's stronger type system it's even more valuable. Kotlin also does not require a cast operator. If you check that an object is instanceOf
some class, then inside that if
block, the variable will be treated as such, without you having to downcast it manually. Kotlin calls it "smart casts".
Final made easy
In Java, the final
modifier is required to make a local-variable non-assignable. That's a very useful property, because it eliminates a large group of potential errors. However, final String myString
is a mouth full. Hardly anybody wants to deal with this. In Kotlin, the character count for final and non-final variables is exactly the same: var myString
for mutable and val myString
for immutable variables.
Smart standard library
The Kotlin standard library is a treasure trove full of pleasant surprises. Did it ever bother you that there is no elegant way to use a lock in Java with try-with-resources
? Well, in Kotlin you have lock.withLock{ doWork() }
. It's a small thing, but it really helps. How often did you forget to free a lock in a finally
clause? Or unlocked the read lock instead of the write lock? The Kotlin standard library is full of such little helpers.
Here's another one: mutableListOf<T>(T... elments)
. The Kotlin compiler is smart enough to figure out the T
if you provide the elements. So the calls to this method will have one of two shapes:
// empty list, need to specify the type argument explicitly
val myList = mutableListOf<String>()
// pre-filled list, no need for type argument
val yourList = mutableListOf("Hello", "World"!)
Can we stop here for a moment and appreciate how this code reads like prose in both cases? Also, Kotlin distinguishes between mutable and read-only collections, while still remaining compatible with Java. That's quite a feat in and on itself and makes Kotlin code a lot more robust against subtle bugs (hey, who modified this collection!?).
Streams, streamlined
If you want to filter a List
in Java, you need to do this:
List<String> newList = oldList.stream()
.filter( s -> s.startsWith("a") )
.collect(Collectors.toList());
That's quite a lot of text if all we really wanted to do was:
List<String> newList = oldList.filter(s -> s.startsWith("a"));
Then where's the point of using stream()
? By surrounding your filters (and map operations and...) with stream()
and collect(...)
, Java streams can be lazy. However, you might argue that there is no point in lazyness if all you want to do is one filter. And you are correct, for a single filter operation, a stream is overkill, both syntactically as well as conceptionally. But in Java, List#filter(...)
simply does not exist (you can use a static utility class, but come on, that's not the same thing).
Kotlin solves this problem in a very elegant way:
// single filter, quick and easy
val newList = oldList.filter { it.startsWith("a") }
// streams for multiple filters (same syntax!)
val newList = oldList.asSequence().filter { it.startsWith("a") }.toList()
So, Kotlin gives you both stream-based lazy chains (called Sequences) as well as one-shot operations. The best thing about it is that the syntax for filter
on a list is exactly the same as for a filter
on a sequence. If you work with existing code and realize that you need another filter, simply insert asSequence()
before and toList()
after to make it lazy, and leave the existing filter in place.
Long live the Iterator
Iterators are a very basic and powerful concept. They exist in Java since version 1.0 (which says a lot). However, Iterators are treated in a rather stepmotherly fashion in Java. You can't do a for
loop over an iterator in the same way as you can over a Collection
. Collection#addAll
accepts only other Collection
s as argument, not Iterable
s. Hell, you can't even do iterator.stream()
. I never understood why.
Kotlin fixes all of the aforementioned problems. Dealing with iterators in Kotlin is just as pleasant as dealing with collections, without losing the benefits of iterators:
for(element in iterator) { ... } /* works! */
iterator.asSequence().filter { ... } /* works! */
myList.addAll(iterable) /* works! */
Java Interop
... is really good. As in "Really REALLY good". You can mix Kotlin and Java sources (and binaries!) in the same project without issues. There are some pitfalls though; we will cover more on that later.
Many Platforms, one Language
Kotlin translates to JVM byte code, as well as JavaScript. Native implementations are also in the works. I think it goes without saying that this is a huge deal.
The Bad & The Ugly
For all its merits, Kotlin also have some downsides. They are not really breaking, but noteworthy nontheless.
No static
modifier
In Kotlin, there is no static
keyword. This causes some annoyance in case you just want to define that one utility method. What you have to do instead is:
class MyClass {
companion object {
@JvmStatic
fun myStaticMethod(/*...*/) { /*...*/ }
}
}
This is ugly. In Kotlin's defense, there's const
for constants (which is roughly the same as public static final
members in Java), and you can declare top-level functions outside any class (which effectively makes them static
). But if you really want a static method, the way outlined above is the only option (as far as I know).
The companion object
was originally introduced to ease the construction of singletons. A companion object is a singleton instance of the class it resides in. I feel that this was a bad decision. Firstly because static methods are useful and should not require that much syntax, and secondly because the Singleton pattern isn't so verbose as to warrant this shorthand, and also isn't that useful to deserve such a prominent langauge feature.
The open
keyword
All classes in Kotlin by default are final
. Let that sink in for a bit.
class MyClass {
}
class SubClass : MyClass { /* NOPE: compile error! MyClass is final! */
}
This is only the tip of the iceberg. Imagine frameworks such as Spring or Hibernate, which generate bytecode at runtime that extends your class. Nope, can't do that - the class is final
. Within the bink of an eye, all of your Kotlin classes have suddenly become incompatible with pretty much any commonly used framework. There are two ways to circumvent the issue:
- Either declare your class as
open class MyClass
or - Add a compiler plug-in to the Kotlin compiler.
Neither option is very pretty. The reasoning of the Kotlin language developers is that extensibility should be an opt-in thing. Only if your class is intended to be extended, it should be able to be extended. In my opinion, this reasoning is flawed: defensive programming best-practices will tell you that you should prepare your code to work in the most unexpected of environments. If you write a class that potentially breaks when it is subclassed, you wrote a bad class. Kotlin gives the utterly wrong incentive here, by simply sealing off everything.
Constructor Galore
Kotlin as a langauge has a really odd fetish for constructors. Have a look:
// primary constructor
class PersonA(val firstName: String, val lastName: String) { }
// regular constructor
class PersonB {
val firstName: String
val lastName: String
constructor(firstName: String, lastName: String){
this.firstName = firstName
this.lastName = lastName
}
}
Both examples above do the exactly same thing. You may argue that the variant with the "primary constructor" is much shorter, thus better. I disagree, for the following reasons:
- The primary constructor must always list (and assign) all fields. This is super annoying when you require an additional transient field for local caching purposes.
- The class declaration line gets exceedingly long.
- If you extend from a base class which has a primary constructor, you...
- ... must declare a primary constructor yourself
- ... must call the primary constructor of the base class
- ... must do all of this in the class declaration
In summary, once you are locked into the primary constructor workflow, you can't break free anymore. Your class declarations quickly grow to enormous sizes. Consider this:
class Student(val firstName: String, val lastName: String, val studentId: String) : Person(firstName,lastName) {
}
Of course, you get a lot of mileage out of such a line. It declares a subclass, declares the fields, declares the constructor, states the base class and finally states how inherited fields should be filled. That's a very high information density right there. I still don't like it. It's too much information packed together, and can be really difficult to parse for a human. I do not recommend the usage of primary constructors (except for data classes where you have no choice), but sometimes you are forced to use them because of the classes in the standard library.
Data Classes - a brilliant concept with half baked execution
Data classes in and on themselves are a brilliant idea. Have a look:
data class Person(val firstName: String, val lastName: String)
You specify the fields of a class and their types, and you get:
- the specified fields
- getter/setters
- a constructor which does exactly what you would expect
- hashCode
- equals
- toString
- clone
- various utilities
... all for free, without writing them. That means your hashCode()
will never go out of sync with your equals()
. Your toString()
will never forget to print a field you just recently added to the class. All of this is possible because there is no text representation for these things; the compiler just generates the bytecode directly. It's a really cool concept and vastly superior to generating all of the aforementioned things via an IDE (because the generated code can get out of sync easily).
The problem is that it wasn't thought through until the end. The main issue is that you cannot use inheritance when writing data classes. Data classes cannot extend any base class, and are final
themselves (nope, you can't use open
here either). The reasoning behind the decision was that things might break (in particular in the clone utilities) when you try to introduce inheritance. In my opinion, this is total rubbish. UML and Ecore have shown how to correctly have data classes with multiple inheritance and cloning utilities.
The lack of inheritance options drastically limits the applicability of data classes. Even simple Data Transfer Objects (DTOs) or JPA entities (prime examples where data classes would be ever so useful!) often require inheritance.
Abundance of Keywords
Kotlin has a very large set of Keywords and Operators. Kotlin (like C#) has the notion of "Soft Keywords", meaning that a certain word is only considered a keyword in certain contexts, but can be used as an identifier (e.g. a variable name) in all other places in the code. Some of them are quite... arcane and awfully specific, such as crossinline
. Regardless, this makes the language quite difficult to learn, and even more difficult to master, as you have to know all of these keywords. In regular usage, you don't need more than you would need in Java. But if you want to read and understand the library functions, you will encounter them quite often. I personally think that a lot of these keywords would have been better expressed by annotations (e.g. @Actual
instead of the modifier actual
).
Verdict
I absolutely recommend any Java programmer to at least have a closer look at Kotlin. In very many areas it is a far superior language than Java. It carries over all of the Java infrastructure and libraries, and adds a lot of smart design choices on top. It has a better type system for enhanced safety and compile-time error checking. The fact alone that it is null
-safe by default should be enough to justify the use of Kotlin over Java. However, it isn't perfect. Kotlin makes it easier to write code that is short and concise, but it can also be used to produce a mess of seemingly random character combinations that nobody can decipher anymore after two days. However, in spite of its shortcomings, weaknesses and the occasional strange design choice, it is an excellent language.
Do you have any experiences to share with this language? Do you use it in production, and are you happy with it? Leave a comment below.
Top comments (46)
Nice post, thanks for that!
I somehow understand how you got to your "bad/ugly" points, but it feels like it's written from a Java devs point of view.
But Kotlin is a programming language on its own and not a simple java substitute!
Here's my take:
Static functions
I disagree. You can have top level functions (like you mentioned) or nicely scoped extension functions. Imho, you never really want a static function to begin with. It leads to procedural, rather than object-oriented, thinking.
open vs final
I really think that this is impossible. Not even the original Java Developers were able to achieve that. Consider e.g
java.util.Stack
, which should most definitely have beenfinal
.There's a reason that Effective Java Item 17. is "Design and document for inheritance or else prohibit it." and is not written the other way round. Also "Favor composition over inheritance" has been advice since forever for the same reasons.
By the way, I rarely run into problems with
final
vsopen
because I usually use interfaces and rarely even consider extending non-abstract classes.The fact that the Spring Framework chose to work with subclassing is the Framework's problem, not the language's. That's why spring boot takes care of that for you by automatically applying the
all-open
compiler plugin.constructors
I don't understand what your problem with constructors is. If the problem is that it's on the same line, then put it on a different line? Otherwise Java's got the same problem - the reason is too many parameters.
Data classes
I kinda agree with your data class opinion, but want to add that it might be a bad idea to use data classes for JPA since, JPA is... special. (vladmihalcea.com/how-to-implement-...)
Additionally, the keyword vs annotation thing is discussed here (blog.jetbrains.com/kotlin/2015/08/...)
Thanks for giving you thoughts! :-)
I could not agree more. When I picked up kotlin for the first time, I saw it as simply a replacement for Java, and did a lot of the things Lombok did. The more I use it, the more I've come to realize that I really do approach solving problems in Kotlin differently (and better) than I would in Java. In fact, new libraries I write in Kotlin, I'm trying to make sure they are multiplatform, so that I can't just treat it like a cleaner Java.
I disagree on this one. The Kotlin language has been advertised by its creators from the very beginning to have excellent interoperability with Java. Therefore it should also play nicely with existing libraries and frameworks from the Java universe. My point is: the Kotlin devs must have seen those issues coming from miles away, and chose to deliberately ignore them, for the sake of forcing a design choice down people's throats (
closed
keyword) which isn't even that good in my opinion. Feel free to disagree, but to me this a big disadvantage of Kotlin.Indeed it is. A lot more special than I would like it to be, honestly. There's so many things you can do wrong with JPA without even realizing that you made a mistake, until it is way too late to rectify it properly. It would have been nice to have a framework where you can model your data easily and have dedicated support to ease the mapping of those data models. Data classes would have been a good fit.
I've yet to read the entire article (thanks for linking it), but
@inline
doesn't seem so bad to me. A lot better (and less scary) than a magical "sometimes I'm a keyword"inline
, in fact. The ability to use annotations without a leading@
that distinguishes them from keywords makes my neck hair stand up in terror. Good thing that didn't happen.I like my code to be structured in blocks: declare the class name, declare the superclass, declare the fields, declare the constructor. This isn't possible with primary constructors. It's all mixed up into one Gordian Knot. Also, there's the lock-in effect when using primary constructors. You can't rely on a secondary constructor in a subclass. Overall, data classes aside, I simply don't see the need for primary constructors. They just increase the mental efforts required for parsing the source code of a class. Where are my fields? They are not listed... oh, right, they might be listed in the constructor... argh I like to have things organized, and find them always in the same place. Now I have to look at least in two places until I find all the fields of a class. It's mixing what doesn't belong together.
Then you are not using frameworks such as Spring or JPA. I don't say that I like dynamic bytecode generation at runtime (quite the opposite...) but it is a hard fact that those are the primary frameworks we work with on the server side. Apparently, Spring 5 got dedicated support for Kotlin, I've yet to look into that, maybe they fixed this particular issue somehow. As stated above, the Kotlin devs must have seen this coming.
I often do, because I like to have named constructors that take different arguments, so I create factory methods (e.g.
User.createFromDTO(userDTO)
instead ofnew UserImpl(userDTO)
). The nice thing about these is that they can be placed directly into the interface (Java 8+), the code which uses it doesn't even have to be aware ofUserImpl
. If I wanted to do the same thing in Kotlin, I need a companion object that holds the factory method. The companion object construction as such (as stated in the blog post) is something that I feel wasn't necessary to begin with, and it has become a syntactic hurdle for cases that actually matter.Just my 5 cents, as I said: feel free to disagree. If you use Kotlin in a different environment (e.g. Android), your experiences with Kotlin may vary drastically from mine.
Data classes are very useful when declaring POJOs for processing JSON, with some extensions. For example, I can have the below setup to fetch a JSON that contains a user's data and convert it into the App's user model to remember locally in my app:
data class UserResponse(id: String, email: String, name: String){
fun asAppUser() = AppUser(name, id, email) // Convert to App's model.
val isVerified: Boolean
get() = id.endsWith("-unverified") // for example, id="37823-unverified"
}
// Somewhere in code,
val jsonString = fetchUserDataFromServer()
val user = Gson().fromJson(jsonString, UserResponse::class)
if(user.isVerified){
rememberUser(user.asAppUser())
}
"Imagine frameworks such as Spring or Hibernate, which generate bytecode at runtime that extends your class."
Heavy use of code-generation is always a red flag to me.
I think we have to differentiate between source code generation and byte code generation here. Both are entirely different beasts with individual pros and cons. In this case we talk only about byte code generation. At the very least, this is code you will never see, which will never go out-of-sync with your source code (because it is generated at application startup).
Overall, I would strongly prefer to work entirely without code generation in any shape or form, byte or source. But in the Java server world, the hard truth is: pretty much any notable server-side framework (JPA, Spring, Spring Data...) has to generate bytecode to achieve its goals in one way or another. You can't avoid it. And usually it doesn't matter, because you never see it happening. As long as you don't declare your classes explicitly as
final
(which I hardly ever see people doing), you'll be fine. With Kotlin, it is just the other way around: declare your class asopen
, or face inexplicable exceptions at runtime. Like I said in another comment, the Kotlin language developers must have seen this coming, I can't imagine that this came as a surprise to them.I have no experiences with Kotlin but it interests me as a gateway into Android apps. Is there a short story to tell regarding Kotlin + Android specifically (and would you tell it)?
It seems understandable if the integration is butter smooth with very little story at all.
I've worked with Kotlin/Android a good bit, and would highly, highly recommend it. It is especially useful considering that Android has Java 8 support for syntax only, but standard libraries (like the Streaming API) are only available on much newer versions of Android and so cannot be used because of backward compatibility reasons. Kotlin lets you do all the functional
.map { }
,.filter { }
, etc. that you want, and it still compiles down to Java 6 bytecode to work perfectly on all Android versions.They also have the Anko, Kotlin Android Extensions, and Android KTX libraries, which are all a treasure trove of useful extensions to make the Android APIs much nicer to work with.
I agree with you. What's important, JetBrains is constantly improving Kotlin and working on extending its capabilities. New versions are being often released, and that's great.
As a mobile app engineer, I'm happy about the appearance of such an advanced language as Kotlin. In our company, we use it more than 50% of our Android development.
Null-safety, extension-functions, coroutines, JVM backend, JavaScript official support, and many other cool features make it an effective tool for writing apps. By the way, TechBeacon included Kotlin in the list of 5 emerging languages with a bright future.
Is it a fair assumption that the rest of the 50% is essentially HTML/CSS/JS? While I do webdev, the appdev side of things is still cloudy; I'm unsure if there'd be anything else required.
Your comment makes me better appreciate my PyCharm subscription. When the time comes, it's all a few clicks away.
Those links are all new to me; butter smooth Android integration indeed. Thank you, drive-by story-teller.
I've been developing for Android now for the last 18 months exclusively with Kotlin. It is much more productive and safer than Java and is fully supported by Android. When working on older projects I find it is usually better to convert whatever classes I'm working to Kotlin. In particular the explicit and concise handling of nullable types is a major quality improvement.
I'm experiencing similar things on the server side. As I wrote in the article: null-safety alone should justify looking into Kotlin for every Java developer out there. It is a game-changer.
I strongly recommend Kotlin+Android. I am amazed with the better, functional style syntax, Extension Functions, Coroutines, Null-safety, and many other features. Threading and background processing is much easy with Coroutines.
Kotlinx - Kotlin Extensions is a collection of functions. For example, if you have a button in a layout XML named
btnAccept
, you don't need to dofindViewById(R.id.btnAccept)
. Instead, you can directly use the namebtnAccept
inside your activity.Here's a nice list, #31DaysofKotlin, a full month of tips and tricks: twitter.com/i/moments/980488782406...
Sorry, I've got no experiences with android so far. But considering that Kotlin is an officially supported language on Android, you shouldn't face too many issues. I'm using kotlin on the server side, and it works like a charm alongside our existing Java code.
Heard that.
I'll come back to this top-down view of Kotlin to refresh my mind on its concepts. You should have seen my jaw when I read about inheritance (Python has spoiled me). It's also a great mini-syntax tutorial.
Thank you for writing this thing!
The kotlin drawbacks are not drawbacks if you want to use the functional programming paradigm. OO doesn't make sense in all domains, so classes and inheritance sometimes add to noise of the program. Kotlin data class is replacement for Python tuple. Since the JVM is not getting the investment that it used to get 20 years ago, I would really like to compile Kotlin to Native. In the age of docker container running a JVM makes little sense, it just adds another unnecessary dependency.
Sure, you can do that. But then I would argue that you should go for Haskell or F#, because even if Kotlin has a lot of functional aspects to it, if you really want to go for it, then you might as well go all the way. The merit of Kotlin IMHO is that it supports both paradigms. It just falls short a little on both. For example, on the functional side Kotlin doesn't support Currying or partial application (without additional syntactical tricks). On the OO side, it doesn't support inheritance on data classes.
I don't think of them as replacements for tuples, and honestly I've never heard th is argument before. They were called data classes by the Kotlin devs for a reason. They can have methods. They basically do everything a regular class does and more, except that they, for some weird reason which I really fail to see, can't use inheritance. Data classes would be a lot more useful if they could inherit at least from one other data class.
I think that this point of view is a little bit on th extreme side. The JVM still gives you a lot of benefits, such as a garbage collector, all the profiling tools (JVisualVM etc.) and in general just a great infrastructure. Languages like Go are catching up, but slowly (different discussion entirely). I would welcome the possibility to compile my kotlin code to a native executable, or even to Go code.
I would disagree about data classes and inheritance. There is absolutely no need in inheritance, especially with data classes.
This may depend on your domain. If you want to use data classes for Data Transfer Objects (which would make perfect sense because they are just data containers, or "structs" if you prefer this term), then inheritance is ordinary day-to-day business (at least in the domains I work with). The only alternative to the use of inheritance would be to duplicate fields into every leaf class, and remove the base classes. This makes the design brittle, because not only are you duplicating information (DRY principle), you also run into the issue that you might forget to add a field in one of your leaf classes in the future. I strongly believe that inheritance is necessary, and this is the reason why I don't use data classes for DTOs, as much as I would like to do so.
Kotlin allows you to define properties in interfaces, and you can still implement interfaces in a data class. This frees you from the brittle design of duplicated properties (at least, in remembering to put them all in), and also solves the issue of wanting to use a data class as multiple types. I've tried this pattern out in my Dokka-to-JSON formatter and it works quite well, and is very robust.
I do not think the choice of data classes being final was an oversight. I think it was made very intentionally, in order to get you truly using data classes as pure data composed of other data. They are not in the same "class" of objects as normal objects, they are useful primarily for serialization, and its generally a good design to not have your code interact directly with a data class any way. You should abstract your code away from the structure of the serialized data into a format that works better with the business logic.
Totally in agreement here. That's why a quick way for building serialization formats is a great benefit.
I must admit that I didn't think of this option yet. It would allow you to do all the inheritance you want on the interface level, and instantiate only the leaf classes. It also enforces the correct properties and types. Okay, granted, that seems reasonable. It does entail though that you need to write the interfaces and the data classes, and in a lot of cases there will be only one implementation for each interface (DTOs for example). Still, thanks for the heads-up. I'll consider this in the future.
You can, of course, implement interfaces in data classes, but, in my opinion, compisition will always be a better way to do it. Want to avoid property duplication in your DTOs/value objects/whatever? Move the duplicated part to a new data class and use it as a property in another class. I have yet to see a use of inheritence which can be justified. Normally it is just a mess. Steer clear of it :-)
About primary constructors, you definitely don't have to list all the properties in the class. Just the ones you want initialized in a constructor. I will agree that the syntax is a bit quirky, but overall I've found them to be quite helpful. I had adopted a pattern in java of always chaining overloaded constructors to a single, "root" constructor, passing the default values along the way, to ensure all objects are initialized the same way. Kotlin just enforces this pattern.
I also prefer a different syntax for declaring primary constructors, which makes it easier to read.
This format makes it very clear which annotations go where (and I use
@Inject
on nearly all of my classes, so is quite necessary for me to have theconstructor
keyword even in the primary one), and also keeps properties each on their own lines, just like when declared in the class body.Thanks for sharing your experience. This way of formatting improves the situation a little. However, for me it's still counter-intuitive. When I open up the source code of a class file, there are three things that I am primarily interested in:
While the "Kotlin way" satisfies 1 and 2, it fails on 3. Look where the base class is located. Somewhere way down there. The very concept of declaring fields (which belong to the class) within a constructor is appalling to me. A constructor is a member of a class. Why should it contribute to the structure of the class (in terms of fields in this case)? I realize that this is not really the case, but the syntax makes it look as if that was happening. Maybe it's just because I've written so much Java and C#.
From what you say Groovy might be a language you should have a look at: Kotlin takes many of its Java improvements from Groovy, but Groovy avoids some imho strange syntax and design decisions, staying as close as possible to Java syntax and never unecessarily restricting the programmer.
Many mistake Groovy for a "script language", but as someone who was written a large framework (Vaadin/Ebean/Oracle/Tomcat technology stack) in it over the last years, I can safely vouch this not to be true: Groovy can be used as a dynamic as well as statically typed language (@CompileStatic/@CompileDynamic annotation on class or method), and comes with a multitude of powerful annotations (@Immutable/@Canonical/@ToString/@AutoFinal/etc) that make for very concise but readable programming. It has flow typing, closures (in addition to Java lambdas), a large extension library that enhances existing Java classes, a huge ecosystem - and is probably the most Java compatible language besides Java itself G-)
(For more details see arscreat.com/wprs/goltrta/ and on newer features groovy-lang.org/releasenotes/groov...)
We use groovy at my company too - but not for application development, but as an embedded scripting environment that allows the user to extend the application at runtime. It does a very good job at that.
If you remove all the meta-programming from Groovy and restrict yourself to the @CompileStatic subset, Groovy is a fairly decent language. My pet peeve with Groovy is that the language itself is far larger than the "safe" subset. I imagine it would be difficult to enforce the usage of this subset across the entire project and across all developers. Also, people are tempted to bring in external libraries, and those might make use of language features which do not conform to @CompileStatic. Basically, @CompileStatic divides the groovy world in two halves; and the 50% that does not use @CompileStatic I'd rather not deal with, at all, ever.
I've seen groovy both at its best (scripting with @CompileStatic) and at its worst (ever seen the mess that is gradle?). From all JVM aliens, I would personally rank Groovy second (behind Kotlin) for general purpose use, but ranked first for runtime scripting.
Hi Martin, could you elaborate on the "the language itself is far larger than the safe subset" and "remove all the meta-programming from Groovy" ? I don't quite get what you mean: Do you write your own AST transformations in Groovy in your project, or do you mean the built in AST transformations that come with the language (e.g. @Immutable) ?
In what areas is Kotlin better than Groovy in your opinion (apart from always being @CompileStatic ;-) ) ?
I have not used much Gradle myself, so I cannot really comment on it, I only know it is in widespread use. But so is make, of course ;-)
You might have noticed that our technology stack (which I as lead picked) is very @CompileStatic (Vaadin/Ebean), and does not e.g. use Grails or GORM. Having said that, I started using Groovy when @CompileStatic did not exist (and someone had just released a static Groovy compiler (groovypp), and approach that was rejected by the rest of the Groovy devs in favor of the more flexible @CompileStatic approach, which led to same guy later designing Kotlin), but due to the excellent IntelliJ* Intellisense support for Groovy I never really missed static compilation (probably a different story if you use Groovy as an embedded scripting language without Intellisense support, though).
(Only in the last 2 years have I started to shift larger portions of our code to use @CompileStatic , since IntelliJ sometimes makes refactoring errors on dynamic Groovy code. Also static compilation is helpful for less experienced developers and/or in areas where there is no time to have 100% test coverage.)
*Ironically the company behind IntelliJ, Jetbrains, of course also makes Kotlin. Alas it looks like the conflict of interests has started to show recently, when Groovy features were only supported in IntelliJ a year after they had been introduced. Besides Java, Groovy as the #2 JVM language is the main competitor of Kotlin. I would rather see the two sides join forces tbh, but that's not likely to happen...
Sorry for the delayed answer - busy times.
Kotlin enforces its type system everywhere. Every kotlin library out there adheres to it. The same cannot be said about Groovys @CompileStatic. Things like the JSON Slurper simply won't work under static compilation. Basically all the metaprogramming features contradict @CompileStatic.
I know, there are valid use cases for metaprogramming. But for general application development, I'd rather forfeit metaprogramming and get meaningful compilation results. In a way, it's nice that Groovy offers the choice, but it backfires when you need to include libraries which made a different choice than you did for your code. I don't want to deep-dive into a feature comparison of Kotlin vs. Groovy, but this fact alone (at least for me) would justify choosing Kotlin over Groovy for general application development. If you want an example of the extensive problems an optional type system can cause, you don't have to look any further than the current JavaScript/TypeScript mess. It's metaprogramming (JavaScript with monkeypatching) vs. well-defined strict type system (TypeScript) all over again. I'd rather stick to a language which doesn't even ask that question.
I'm in a minority here, but for me Kotlin use is a huge red flag.
The weakest developers use it to work around understand basic SOLID code and without understanding what it's going to generate.
The advantages you list like NPE safety are only truly advantageous in the hands of seasoned engineers. The rest of the time they are the reason weak devs recommend Kotlin to eachother in stackoverflow comments - to avoid learning to produce safe, efficient, quality code and to avoid learning concepts like nullability, inheritance and encapsulation. (The way they would previously have recommended static everything as a terrible solution)
So if a bridge would collapse you would blame the newly invented materials instead of the faulty design conceived by inexperienced engineers, who did not fully understand said materials?
Slavish devotion to a few general principles and the worship of acronyms is pretty stupid as well.
Nicely written. I totally agree with your assessments.
I'm frustrated almost daily by Kotlin's EXTREMELY awkward constructor logic, and it gets worse when you factor in usage of the
init
block, which will be required if you actually need to do any work in the constructor. Primary, secondary, init...it's all a bit ridiculous. For the life of me, I cannot fathom what problem the Kotlin devs thought needed to be solved, but I feel confident stating this wasn't the right way to solve it. It feels like someone was showing off: "Look how much logic we can stuff into a single undecipherable line!" Brevity should follow clarity, not the other way around.Data classes can't participate in inheritance, yet no one can ever infer that restriction. Instead, every Kotlin dev will be forced to discover it once their code fails to compile. Perhaps naming the class
FinalSealedDataClass
would have been helpful, but allowing inheritance would be better.And possibly my least favorite language construct of all time (second only to Android's
AsyncTask
) is this:inline fun<reified T>
. Dear lord, I had to google what the wordreified
even means. It basically means to 'make an abstract thing real', and while technically accurate, is a mightily poor cue for understanding that you're instructing the compiler to translate the genericT
into a concrete type at every call site at runtime. And while we're at it, I think Kotlin shouldreify
its own damnT
's and inline generic functions as needed without me having to tell it to do so ;-)And finally,
coroutines
- designed to make working with threads easier - and all that's required to use them is a complete understanding of absolutely everything about concurrency. Here again, I think the Kotlin devs have conflated simplicity with brevity.These not-insignificant complaints aside, Kotlin is actually rather fabulous.
When I heard about Kotlin for the first time I heard that it was a language full of features that solved many of Java problems: verbosity, NPE, etc. People also said that it was a smooth learning curve for any Java developer, I thought it was nice and since the learning curve was smooth, maybe I could do what I liked to do more: jump into a language with examples and all that. It was not a very good experience...
It was some months ago, so some things may have changed and my memory can fail, but the first example I jump into was Spring Boot, since I use it every day in Java and it is already concise and easy to use, so even more concise would just make me love it more, right?
I created the project and started to write, reading the documentation when needed, but when I finished writing the Hello World example... Errors appeared...
More recent examples don't have this anymore, but at that time it was necessary to use a companion object to start the Spring Boot application, it was hard to find out that, and then there is lateinit for autowired properties, compatibility between Java libraries and Kotlin which made the use of my favorite libraries very troublesome. I remember a had a lot of trouble trying to figure out how to use Jackson. lol
Yeah, it was not smooth at all... I'm returning now since there are examples of projects in Kotlin in the book I'm reading. I think I will give it another chance.