Translating 10M lines of Java to Kotlin
engineering.fb.com
engineering.fb.com
I think Kotlin helped push Java in the right direction, but at this point it seems like a weaker choice. Spending years to migrate a massive Java code base like this feels like wasted time.
I like:
* data class Foo(val theImplementationAndStyleOfDataClasses: String)
* elvis?.operator?.chaining
* the order of variable declaration (type afterward)
* how function pointers at the end of a method call can be auto-inserted as the next curly bracket
* how you can have fun foo() = returnValue; in one line.
* fun `method names that can have spaces - and dashes - in them`()
The preceding 3, combined, allow for: @Test
fun `much easier testing`() = commonTestSetupWrapper {
// the code under test
}
* val immutable by default
While I agree that Kotlin has definitely helped push Java in the right direction; and I agree that it's probably not especially necessary to migrate 10MM lines of Java code to Kotlin, especially since they're fully interoperable, I definitely would prefer writing _new_ code in Kotlin over Java for the for-the-lazy devex improvements, if nothing else.fwiw, my "good at it" programming history follows the lineage in historical order of:
* Java (8?? years of it including competition programming)
* C# (5+ years)
* Python (2016ish to current)
* Ruby (3-ish years, lots of Rails)
* Kotlin (2-3 years, through current - written over 40k lines of Kotlin in the last year, alone) `method names that can have spaces - and dashes - in them`
Eugh, that is a turnoff. Maybe I have programmer Stockholm’s, but at least with a single connected word, I can always double click to select the token. Maybe, I might have wanted dashes at some point, but spaces seem like a step way too far. class MyTestableClass {
fun `methodName - when input does not parse to valid regex throw exception`() {
}
}
It's pretty clear what is under test in a situation like that. That's basically the only situation I ever see it used in (and would code-smell the heck out of it if I saw it in other circumstances).People who are familiar with RSpec-style testing are very used to this sort of thing.
describe MyTestableClass do
context 'input parsing issues' do
context 'when not valid regex' do
it 'throws exception' do
...
end
end
end
end
Anecdotally, I've also found that such style naming for tests allows me to write out the desired names for all the tests ahead of time more easily and then implement them. That happens to be my flow.They did a study and it isn't hard to read camelCase. https://ieeexplore.ieee.org/document/5090039
It's important to note that the researchers only tested short phrases with 2 and 3 words. "getNextPath" is a different beast compared to "methodName_whenInputDoesNotParseToValidRegexThrowException".
On the other hand, there is a good body of research that generally shows that proper sentences (not just 2-3 words) without spaces are harder to read, at least for English speakers[1]. Early medieval Latin manuscripts did not use space between words[2]. The fact that spaces were seen as an improvement when they were introduced, is probably telling.
[1] https://scholar.google.com/scholar?hl=en&as_sdt=0%2C5&q=unsp...
[2] Roman-era inscriptions did use an interpunct (a small middle dot) to separate words, but this practice fell out of fashion in Late Antiquity and Early Middle Ages and I'm not sure if it was ever the norm in handwriting.
That study compared camel case to underscore spaces. Well underscore space is still not as readable as methods with spaces and dashes.
The study does not say that camelCase is unequivocally as easy, or easier, to read.
Selecting the correct identifier from a list of 2-3 words (expandAliasTable, expandAliasTitle, etc.) is a wholly different exercise.
@ParameterizedTest
@ValueSource(strings = {"first", "second"})
void fooGoesBar(String param) {
...
}
Also, once you get used to assertj, you'll never want single-assert again. It's fantastically useful to know (for example) not just that two lists aren't equal, but specifically what elements differ.Pytest shows the diff as well when using assert:
> assert y == x
E assert [1, 2, 3] == [1, 4, 3, 6]
E
E At index 1 diff: 2 != 4
E Right contains one more item: 6
E Use -v to get more diff assertThat(someCollection).map(Thing::getValue).containsExactlyInAnyOrder(3, 4, 5); assert sorted(x) == sorted(y)
If you want to add map, then you have to write assert list(sorted(i.getValue() for i in someCollection)) == [3, 4, 5]
If the list contains non-sortable values, but they are hashable and unique, you can use sets: assert set(x) == set(y)
If the values are not unique, but hashable, you can use a counter (like a set but with count for repeating values): assert Counter(x) == Counter(y)
(by the way I learned about this trick from a LLM)And if the values are neither sortable, nor hashable, you'll have to write a helper function.
But still, pytest tests are less wordy and they don't require you to create a class.
This situation is familiar to me, I had to write such helper function when writing tests in PHP, for some reason it is not included in PHPUnit assertions.
In fact I've occasionally found myself writing reflection code to parse the test method name as input just to avoid that problem altogether (for name and data, not for documentation). And that was in plain Java even, the pattern could be far more useful with that kotlin naming freedom.
When a test fails, the method name describes what it was supposed to do written out like a sentence enclosed in double quotes which seems like a win but not much of one.
When you need to add a new test or analyze if an existing test needs to be changed, you have to eyeball all the code in the test class because even with methods named with spaces, it's not always indicative of what it does.
With Java, I can sort a list of method names and have a better idea immediately what needs to be updated or added.
Refactoring Spock tests sucks due to how the context is different based on the clause you’re in.
expectInternalServerErrorOnBadInput
Vs
expect internal server error on bad input
I'd expect an IDE for Kotlin to detect `stuff like this` and select the entire name. Considering Kotlin came from JetBrains, IDEs are most likely already in the Kotlin community's DNA.
(i never wrote or anything related to Kotlin myself but i do appreciate IDEs helping with stuff like this)
Idea understands and performs dramatically better with Java.
It also really doesn't like Java with generics for some reason. Still beats any other Java IDE I've seen.
val isBar = foo.`is`(bar)
rather than needing to use reflection like you would to accomplish the same in Java.The Kotlin style guide basically says "never use this outside of test names" and some targets, such as old Android, don't even support the feature in test cases.
In general you would not use spaces for method names of anything that’s a shared/common use facility. They are best used to give test methods readable names. E.g. “test parallel writes are thread-safe”. And not “write with timeout.”
If you're using anything that can do syntax coloring correctly, you can still do that.
That's because you're too used to C/Algol style languages
In 2020 I started a new project in Kotlin.
I like Kotlin, but what I hate is that in the meantime I migrated all my programming to Neovim and Helix. All programming? No, not Kotlin, because the LSP isn't there and JetBrains clearly has no interest, they want to sell their IDEs.
Now don't get me wrong, I have an IntelliJ license since 10 years, even back when I was employed and paid it out of my own pocket.
It's not about the price. I would gladly pay for the LSP implementation. But I don't want to use IntelliJ anymore.
So a new project on the JVM where I have a say in the stack? Java or maybe Scala. No more Kotlin.
As for Kotlin in general, I agree with your list. I really enjoy the way Kotlin improves the dev experience not so much with audacious new (or "new to a java-like environment", of course I'm looking at you, Scala) concepts but with small trivialities like being able to assign the outcome of an expression chain to a name without polluting that name's scope with intermediates that really don't have any purpose after the result of that chain is assgned.
And I don't hold it against Java that it does not follow that path (it focuses on more impactful changes), I would consider it almost out of character if it introduces e.g. .let. The only thing I'm a bit torn about when it comes to Kotlin is wether the "this" variants of scope functions were a good idea. They are certainly part of kotlin, and some "DSLy" styles would really not be the same without them, but if I were to pull what I like about kotlin into other languages I'd probably introduce let and apply (and the this-less run) and skip run/with/apply (assuming that the target language even has a this to run/with/apply on)
For the purists there are records now in Java (which can be combined with destructuring / pattern matching - there were significant improvements in this area in the last few JDK releases, and more are coming!). In this regard Java had surpassed Kotlin by some margin.
For the enterprise coders, there is the Immutables project (https://immutables.org) which you can say is Lombok on steroids (but implemented correctly, that is, by generating classes which you can inspect as regular source code, instead of relying on hacking the compiler...).
> * elvis?.operator?.chaining
This will hopefully be finally resolved by one of the subprojects of project Valhalla, as a prerequisite for value class support (which cannot be null, so...).
The others on your list are small and subjective stylistic differences, of course your preference may vary, but should not weigh heavily in any Java vs Kotlin discussion.
This makes Java records extremely painful to use for any data has more than 3 fields or so. You could use them together with an annotation processor like RecordBuilder (or Immutables or Lombok, but I don't think they support records?), but that's just not pure Java. Annotation processors that generate byte code always come with their own complexity.
Kotlin data classes have their own warts (order-based destructuring can be dangerous and the copy() method is a bit hacky) but the overall ergonomics for large value objects are better than Java records at this point.
Lombok is so ubiquitous today that I effectively consider it part of the language. And even with Lombok, the Java toolchain is so much faster, easier to use, and more stable than Kotlin.
Kotlin is a great language, marginally better than Java, but I'm not sure it's worth enduring the toolchain. And Java keeps improving.
Java will never get close to Kotlin because even though Java can, and thankfully does, still improve, it's too late to revise many of it design decisions, and large language features often need to be considered in a language's design from the start.
On Android, Kotlin is an even bigger win because Android uses an old Java version and has become Kotlin-first.
In my opinion, Java will remain the best choice for libraries that target multiple JVM languages. For everything else, I use Kotlin without thinking twice. Kotlin 2.0 laid the groundwork for toolchain improvements, which have indeed been a long-standing pain point.
Having worked professionally in both, I find that good programmers can write excellent code in both languages; bad programmers can write terrible code in both languages. The average cultural practice of the Kotlin community is perhaps better than the average cultural practices of the Java community (which huge and defies averaging anyway). But Java code which emphasizes immutability, uses Optional instead of nulls, uses streams, etc is pretty indistinguishable from Kotlin.
Here are some of my favorite Kotlin improvements over Java that I leverage all the time:
* Much improved type system (nullable types, function types, declaration site variance, type aliases, contracts, better type inference, reified function type arguments)
* Local variables are final by default ("val")
* Type-level distinction between read-only and mutable collections (but compiled to Java collections under the hood, so no conversion required when interacting with Java)
* Much improved collection API
* Much improved lambdas (e.g., no pain points w/ mutating variables and checked exceptions)
* Extension functions (incredibly useful in practice)
* Much better DSL capabilities (great for UIs, generating HTML, etc.)
* Lazy properties (more generally: delegated properties)
* Coroutines (looking forward to Java's Loom; by then coroutines will have dramatically improved my async code for 5+ years)
* Great serialization support (kotlinx.serialization)
* Pragmatic macro-like capabilities via inline functions and compiler plugins (removes lots of boilerplate)
* Multiplatform support (JVM/JS/WASM/native; Graal native image is a good alternative to Kotlin/native and also works for Java)
* Yes, the Kotlin type system is better, no question. And yet it doesn't really matter for 90% of code, Java's type system is "good enough". And in the places where it tends to suffer, IntelliJ ends up covering the ground anyway (validating various annotation values, spring properties, sql, etc).
* The difference between nullable types and Optional<?> types is not material. TBH I quite like Optional<?> and how it meshes with streams.
* "final by default" is indeed nice. But putting `final` everywhere (which we do) is at most a tiny annoyance. This is not material.
* Clear type distinctions between mutable and readonly collections is better, but not material, because we treat all collections as immutable.
* Java streams hit 90% good enough, and StreamEx tends to fill in another 9%.
* We don't have pain points with mutating variables in closures because we don't use mutable variables.
* We don't have problems with checked exceptions because we don't use checked exceptions (and when interfacing with foreign code that throws checked exceptions, we use @SneakyThrows or wrap).
* Coroutines are a major net negative for Kotlin, and Loom is already here. After many years of async programming in JS/TS, Ruby, and (going back far enough) C++, I've concluded that async programming (and function coloring) is absolutely toxic and should be avoided unless it's absolutely required for performance (or the platform, ie GUIs).
* Multiplatform support has great future potential, but it doesn't seem to be ready yet. I just started a greenfield project and _really_ wanted to use KMP. Decided against it in favor of a React frontend. I don't want to live on the bleeding edge, I have work to do. And I've been bitten by deadended technologies enough times in my life to know the risk isn't worth it.
I could go on but I think you get the point - my Java looks like Kotlin, and the parts of Kotlin that would really diverge from my code aren't things I want. You could tell me that I should use coroutines everywhere and I just simply disagree.
I think we'll see them come in through https://openjdk.org/jeps/8303099 although I'm not a huge fan of the approach chosen (tri state nullability: explicitly nullable, explicitly non-nullable, legacy code that should just be treated as nullable).
Except for native support for reification, almost all of Kotlin can be reimplemented by Java if you wait long enough and stack enough class annotations. It's all stylistic up to a certain point, but the classic Java style combined with the glacial improvement process is one reason why some people want to use a different language.
I think I'll have an easier time convincing someone to consider Kotlin than I'll have convincing someone to add Immutables+Manifold to the dependency chain. You end up writing Java that's not really Java like, but also not something that there's a good standard style for, and that usually ends up in a mess.
I'm glad with pattern matching, Java is now pushing Kotlin to improve rather than the other way around. I'll hope to use those features one day, when companies finally upgrade from Java 11 to Java 17 in five or ten years.
I did just glance at Java's pattern matching; and yeah, it does look like it is a bit more powerful than Kotlin's pattern matching in that it can, at the same time, look for both type and a conditional. That's relatively neat - not something I've personally needed / wanted; but neat just the same :)
The JVM is a really nice virtual machine in how it lets us use both of these languages fully interchangeably to pick the one we like more.
I'm glad Java has been investing in some of these areas, too. Everyone improves when paradigms and new learnings are distilled.
What would be compelling: Array types that don't hate generics, or generic collection types that don't hate primitive types. Does Kotlin improve on Java at all here? It's such a pain having to remember this bonus complexity when all I want is a consistent way to manage a bunch of things. (I suspect not, as I think the limitations are at the JVM level, but I don't know.)
Bottom line is, you could have your cake and eat it too - have an int[] that looks like a List<Int>, and vice versa, while retaining the performance characteristics of an array.
interface Box<T> {
T get();
void set(T val);
}
interface IntBox extends Box<int> {
int get();
void set(int val);
}
https://openjdk.org/projects/valhalla/[this] https://inside.java/2024/12/16/devoxxbelgium-valhalla/
So it's possible.
You just swap them out with fastutils or trove and call it a day.
That said, I tend to get bitten by the fact that == on 2 boxed numeric values is roughly as misleading as possible -- it does reference comparison, but interns small integer values only, so (Integer)42 == (Integer)42, but (Integer)420 != (Integer)420. (So the small values you probably used in your unit tests will appear to work just fine with your broken, ==-using code.)
ETA: Deleted a misplaced "new". I also don't love that, e.g., ++x does a heap allocation + deallocation if x is boxed (since boxed types are immutable). But usually I care more about the cycles I spend coming up with correct code than cycles the CPU spends executing it.
record Foo(String theImplementationAndStyleOfDataClasses) {}
The string is final. While sadly there is no elvis operator, the Optional type helps: evlis.map(e->e.operator).map(e->e.chaining)
I still strongly dislike working in Java but Java 23 is a long way ahead of the Java 6 I first learnt Java with.https://docs.oracle.com/en/java/javase/17/language/switch-ex...
I'd personally prefer modern C# as it's been outpacing advancements in other major languages for DX, IMO.
It is sad that its adoption seems to have boundaries defined either by large ecosystems dependent on the JVM or ideological.
If I had to switch and use C# as my main work language for a company, I would be perfectly happy to.
This is one feature I wouldn't mind making it into other languages. It makes so much sense for doing UI stuff.
What makes Kotlin such a strong choice for many orgs today is its batteries-included multiplatform capability. We are able to write data models, validation logic, business logic, etc just once and compile to JVM, WASM, and native targets. For orgs with very large codebases and frontend applications (web + iOS + Android) this is an attractive capability because we can have a single codebase for a ton of core functionality, and have each frontend import a library which is native to its own platform.
Of course many technologies with this promise have come and gone over the years, but this is the first one with a strong backing that has allowed us to _natively_ interoperate with each target platform.
I believe these are all driving factors that have been pushing well known companies, that were previously Java shops, toward Kotlin. If you speak to a broad range of people in the industry you'll find many more orgs moving from Java to Kotlin than from Kotlin back to Java. We can simply get more work done with less code and ship to all our frontend platforms, and unless Java can do the same, I don't see the industry moving in that direction.
The question of optional things is always laid out as if the choices of my coworkers do not exist. Are you expecting me to work solo, or to be a bloody tyrant who pushes through all of my own ideas over the objections of an entire team? These are some of the most antisocial people in any debate on software. No I don’t get to just chose to use or ignore a tool in an ecosystem. That’s why picking a system with batteries included is a simpler transaction. I can go to a new company and have expectations, not arguments.
Not familiar with Kotlin but how does that work? Does it come included with a PAL? Because it you want to be platform agnostic, you can't for instance use a Java RegularExpression in your platform agnostic code.
In shared code you can define interfaces that have to be implemented by any platform you use.
I think on Android the choices are either to start from scratch or to migrate to Kotlin for Facebook. Sticking with Java on Android just makes everything harder. All the new frameworks are Kotlin centric. Most Android developers they'd be hiring would have been using Kotlin for years. Etc. So, I can see why Facebook is making the effort to keep their code base alive and future proof. Android is more Kotlin focused and has been for years. And that's only going to be more the case now that compose multi platform is a thing.
Btw, most of the stuff you mention is probably more applicable to server side Java and less to Android. IMHO, Spring + Kotlin is a very nice combo on servers. Very well supported by Spring and they bundle a lot of convenient things for Kotlin. And I don't need any of the tools you mention because it does the right thing by default.
I mostly agree with you, but Spring+Kotlin does require the allopen plugin, so it's kind of hacking Kotlin to do the wrong thing (all classes open) in order to support an arguably bad design choice by Spring.
It is wild but such is the consequence of the culture that punishes explicit failure, and ignores failure to be able to ship fixes for the unpredicted situation and ignores failure to have a productive code base. I cry for the mounds of copy-pasted code that must result from this policy that treats its engineers code like radioactive garbage.
Exception: final parameters. They should never be assignable.
One of the huge problems with a language migration is that you're left with old and new and all the parallel tooling, context switching, and impedance mismatches. There's often a claim that the existing code will be migrated eventually, but IME that doesn't actually happen with large code bases. Then if there's ever a third migration, you're left with three languages in play.
It's much better to aim for 100% migration, as close to 100% automated as possible. Then when you're done, you're really done. Maintenance, training, and the next migration will be easier.
Contrary to the popular notion of dismissing certain syntax differences as sugar, I consider it one of the most important factors simply because we spend more time reading than writing code. To me Java has always been verbose and dreadful to read, there's something fundamental wrong if you need your IDE generate so much then force to train your eyes to skip over most of them while reading.
I find Kotlin to be more elegant and fluent especially with native syntax support of the above features. I can read it at least 25% faster than Java. Perhaps which one is better is personal taste, but I'd maintain syntax is very important, just like natural languages.
Nothing on Kotlin will ever matter on the decisions taken by Oracle, IBM, Amazon, Azul, PTC, Aicas, microEJ, Microsoft, Red-Hat,.... on their JVM/JDK implementations and related tooling.
What I saw happen was Kotlin taking over the mantle of "better java" from Scala, and aiming for an 80/20 type language compared to Scala. And for the most part, it's those 80% that are finding their way into Java.
Its Meta. They have enough money to spare not to need to worry too much about whether there is a good RoI on rewriting some mobile apps.
I very much doubt FB's mobile apps are written for any sort of efficiency, (either in engineering or financial terms) - 18,000 classes in the apple one! https://quellish.tumblr.com/post/126712999812/how-on-earth-t...
So super inefficient binaries, but I guess more efficient to develop (or I assume that was the idea)?
By the Dalvik Virtual Machine (DVM). 65k method limit is what Facebook hit. tbf, DVM was engineered to run on embedded systems with 64MiB RAM (as a replacement for J2ME).
Nobody wants to use a mountain of tools just to achieve basic feature parity with other languages. Also java syntax is awful.
Identifier-first can also be a lot more readable when gnarly (and thus lengthy) types are involved, since you don't have to scan the line to find it. It makes for neater-looking code with multiple declarations one after another, too.
As far as commonality, both syntaxes are very common, and have been so historically as well (roughly speaking, C vs Pascal syntax families). It should also be noted that most new languages go for identifier-first, so if anything, it's more future-proof in this sense.
And again, the point is that Kotlin brings along extra characters AND variation. What's next, `fnctn myFunction(x : Int)~ Int { /* code */ }`?
Why? Just to be quirky? To avoid some hypothetical lawsuit?
And I already explained the reasoning. No, it's not to avoid the lawsuit, it's because many people genuinely find it more readable for various reasons. Which is why it keeps showing up in all kinds of languages that are otherwise wildly different syntactically.
And again, in the latter part I'm talking about Kotlin's use of 'fun' and ':'.
Using val instead of let is similarly off-putting.
Maybe it has a more PHP-like feel to it. No accounting for taste at that company.
What's dubious about it. Kotlin has a more comprehensive dx, and has features like extension functions that aren't on the java road map.
Does a Kotlin codebase have more safety from a legal perspective?
IMO, Android is moving away from modern versions of Java. Java and its underlying standard library will always play a big role in Android development.
The way I see it, Kotlin makes a lot of sense for Android development because Kotlin can introduce new things in its standard library that make older versions of Java look ancient. It's not like you can use new Java features in Android, so using Kotlin makes people not care as much about new features of Java that are only available in modern versions of Java.
Hence ART is updatable via Play Store since Android 12, and is currently on Java 17 LTS.
It is good enough Java for relevant libraries to work on Android.
Including the main sales pitch of Kotlin, its Interoperability with Java.
It's not "the JVM"; JVM is a spec that has many implementations, you are probably referring to Oracle JRE/JDK.
But if built-in null safety and lower verbosity was all that Kotlin had to offer I doubt it would have won. Kotlin offers a lot more features that Java does and most probably will not offer in the next 10 years:
- Extension methods: No more terrible FooUtil or BarHelper classes.
- Context parameters (preview feature)
- Coroutines (they are not just about concurrency - you can use them to write generators as well[1]).
- Keyword arguments: This makes using constructing data classes possible without generating builders, and in general lets you deal with multi-argument methods in a safer and more readable way. It also cuts down the boilerplate of defining 20 different overloads that call each other.
- Interface Implementation by delegation: This feature makes it easy and painless to follow the motto "composition over inheritance". Java makes implementation inheritance 50 orders of magnitude easier than composition with manual delegation, so it's pretty natural that the vast majority of Java code- bases overuse inheritance in very bad ways.
- Properties: This reduces the need to worry about direct field access causing API breakage in the future, and removes the need for the absolutely massive boilerplate associated with getters and setters in Java. It's true that records remove the need for that, but records only work for purely immutable data classes (and right now they are only practically usable with a small number of fields).
- Delegated Properties: Not something I use every day, but it's very useful when I do use it.
- Unsigned integers (specifically bytes): Writing any byte-wrangling code in Java is a downright nightmare.
- Reified Generics: Java may get something equivalent to that with Project Valhalla, but I'm not sure how comprehensive it would be.
- If expression: Java made switch an expression with JEP 361, but if is still a statement. This leads to a lot of bloated and somewhat unsafe code where you have to define a variable before the if and then assign to he variable in each branch.
- Expression Bodies for function: This is a very small feature, but it's pretty nice to cut down boilerplate and make functions more readable when you need it.
DSL Features ------------
Kotlin is perfect for DSLs thanks to features like:
- Closure blocks: Kotlin lets you pass the last closure argument to a function as a block of code inside curly braces that follows the function call. This features lends itself very well to readable DSL. In Java you would need to embed the closure inside the function argument list and add an extra parentheses. This gives you some of (see the next couple of points) the DSL capabilities of Kotlin, but the DSL becomes very hard to read and use when a lot of blocks are involved. There is a good reason why Kotlin DSLs are based on blocks, while Java DSLs are based on the less-flexible fluent interface pattern: block-based DSLs are just too hard to read and write in Java.
- Inline functions: Inline functions allow the compiler to perform some optimizations in advance that may be harder for the JIT to do (namely inlining the provided block closures), but the real kicker is that you can use flow control statements (break, continue and return) that affect the calling scope.
- Multiple "this" in context. You can have multiple values for this inside a nested scope. The compiler manages to find the right reference for the "this" alias based on your usage, but in case of ambiguity (where the deepest "this" wins), you can disambiguate your choice. This feature sounds overly complex and unnecessary at first, but it allows a lot of power for the Kotlin DSL system.
- Closures with this receivers: Using the multiple this values from above, you can have closures that receive their own "this" to introduce DSL methods. Being able to introduce these methods without shadowing the "this" from the parent context is crucial for powerful DSLs.
- Infix functions and operator overloading: Needless to say, these features make DSLs even nicer.
I've probably skipped a couple of features I can't recall right now, but I hope that it shows that Kotlin is a lot more than just "Java with null-safety and data classes" as some people think.
While we're on this subject, I'd like to rant for a bit. Why is this not a standard feature in every OO language today? The adage that composition should be preferred to (implementation) inheritance is itself older than Java, and was already fairly common wisdom 20 years ago - yet PL design has seemingly not reflected this.
I learned that social whims (developer fun, preferences , dopamin) are weighted as much as technical rationales (performance, maintenance)
Long-term, the folks who remain are stuck and unhappy. The business got what it needed and moved on, leaving a system half in maintenance-mode, half feature-complete. Any Kotlin-only changes are mostly just bug-fixes or glue work with other team's SDKs. Any new features carry a lot of risk because the few people motivated to drum up new business (and write up requirements) left or quiet-quit.
In a weird way, the project naturally selected for people who would get stuck maintaining the artefacts of the project. It's a paradox that I can't fully comprehend.
Personally I find that it's an interesting indicator of the capability of the programming languages. Moving from language A to B can be extremely easy if B is as powerful or more powerful in terms of expressiveness than A. It can be an absolute horror if it is less powerful.
Being not null-safe in fact brings additional expressiveness. I guess most would argue that it's not a good type expressiveness. Nonetheless it is a type of expressiveness that can cause trouble during such a transition.
In general it feels like Java is making faster progress than Kotlin nowadays. I wonder where Kotlin would be if it weren't for Android. I hope those folks don't have to migrate back before they even finished.
Why not? The current application is working, so I'd start with that and then gradually move away from it. But it seems they chose to go the opposite way.
We have: public and internal apps, tests for those, tons of wrappers around Android APIs, autogenerated infra code, did I mention the API wrappers?, custom layout engines, duplicate code that people wrote because they couldn't be bothered to search the monorepo, code that was copied without much thought because people searched the monorepo and found something, app architecture tools like dependency injectors, annotation processors, custom build tooling, etc
If you can think it, someone has probably committed Java/Kotlin or native code for it already.
This is also a very common way in Object-Oriented code bases to ensure you're not messing anything up by re-using classes. Concatenating yet another class to the code base is the safest and laziest way to ensure you're not possibly changing the behavior of anything else when you inevitably need to add/change something in the class.
The former is not how OO is supposed to really work, but it is a direct result of adopting OO (non-obvious results of inheritance and other features) and in the latter we get exactly what we already were doing: Lots of functions operating on data, it's just more of the same.
It seems to me that one of these highlights the failure of its respective paradigm much more than the other.
I should note that I don't think functional programming as a paradigm is a particularly fruitful one. What I'm arguing for in the large, if anything, is bog standard procedural/imperative code with procedures/functions + data. Sprinkle in the few useful ideas functional programming does offer and you have something that has a fighting chance.
This sees to give an unreasonable leeway to copy pasted code to imperative functional languages. What if the copied function also has side effects, mutates a global variable etc. All these problems still exists.
I have no clue what's in their code, but I would expect a lot of "almost redundant" stuff around specific Android versions, manufacturers, etc, which can really pile up.
* Facebook Messenger came out on Android in 2011 [0]
* React Native came out in 2015 is written in C++, Java, JavaScript, Objective-C, Kotlin [1]
[0] https://en.wikipedia.org/wiki/Messenger_(software)[1] https://en.wikipedia.org/wiki/React_Native
So, even if they did use React Native, they still have 4+ years of code in the original language; and, React Native doesn't stop the use of Java
Mostly, I find it far less verbose with a couple huge convenience features that I'm constantly using and would feel very lost without; and, they all come as part of the language, rather than bytecode hacks / pre-processors, like lombak
I know they added the Result type later on, but there's zero convenience to it, so you have to manually handle every error, even if you just want to bubble it.
Checked Exceptions were better, though Rust still has the best error handling in any non-functional language.
In my experience I don't hesitate to recommend Lombok. It will generate getters/setter/builders/toString in a transparent way, without visible changes in the code. This keeps Java classes much more concise to read.
It also provides withers on records, while waiting for native support (it's coming)
Do note that Java has quite a few features now that it didn’t at the time of writing.
This must be a bad attempt at a joke, right? Darcula is a nice theme (I personally prefer high contrast), but surely IntelliJ’s code inspection and automatic refactoring have always been its claim to fame.
Java is to too verbose. Kotlin have features like null safety, better functional programming, better coroutines, advanced sealed classe. Java virtual threads is still not ready and development was very slow. Blame oracle for being too complacent.
still seems too verbose though even though it is my preferred language
For example, you can always `git mv` the Java file to the Kotlin file and only then transform it, which keeps your history.
The only reason fb is able to do this, is the billions of $ behind it... For everyone else this is just pure idiocy
Sure if you like Kotlin, use it for new software, but rewriting milliona loc for some marginal gains... that how businesses fail more often than not
The language's "declarative style lambda syntax" makes code overly-complex. Every library is basically it's own DSL and every lambda context has it's own set of specialized keywords. If I write `test { somthing(value) }`, depending on the context, `somthing` could be invoking a higher-order function, calling a method on an object that the lambda is extending, or calling a static method. The muddling of data/declarations and procedures, makes jumping into new codebases confusing and time-consuming. Rich Hickey has pointed out in numerous talks that syntax itself is complex because rules and order are complex, but in languages we generally trade a little complexity for terseness. Kotlin encourages this trade-off far too much in the direction of complexity, and the language would be almost unusable if not for it's IDE support.
Getting to the root of the previous problem is that method extensions in general feel like an anti-pattern. Without introspecting a method call, it's not possible to tell where that functionality is coming from. It's a huge amount of added cognitive strain to have a classes functionality split into various files, possibly across various code bases.
Another problem is coroutines (oh joy, another new DSL to learn). By now, it should be known, from languages like Go and Erlang, that preemptive lightweight threading is almost always going to be better than cooperative. It avoids function coloring, keeps throughput of applications more consistent, and is generally easier to reason about. Java also now has preemptive lightweight threading via virtual threads, but because much of the Kotlin ecosystem is reliant on the coroutine API, it don't get any of the ergonomic benefits.
Could someone expand on this please.
Younger developers are far less likely to know Java today than Kotlin, since Kotlin has been the lingua franca for Android development for quite some time. Mobile developers skew younger, and younger developers skew cheaper.
With Java out of the code base they can hire "Kotlin developers" wanting to work in a "modern code base".
I'm not suggesting there's something malevolent about this, but I'd be surprised if it wasn't a goal.
If I had someone call me about Kotlin, I would assume the people hiring are more interested in the future than being stuck in the past.
To quote from another comment I made:
> I don't have any numbers, but we know that the Meta family of apps has ~3B users, and that most of them are on mobile. Let's assume half of them are on Android, and you're easily looking at ~1B users on Android. If you have a nullpointer exception in a core framework that somehow made it through testing, and it takes 5-6 hours to push an emergency update, then Meta stands to lose millions of dollars in ad revenue. Arguably even one of these makes it worth to move to a null-safe language!
It's even worse with Kotlin, without the NPE to provide the warning something is wrong, the bug could persist in PROD much longer potentially impacting the lives of 1 Billion users much longer than it would have if the code remained in the sane Java world.
Even if I am charitable with my interpretation, I'm not sure I get your point. If you refuse to handle the case where something is nullable and you convert it to non-null via .unwrap() (Rust perspective, I haven't used Kotlin), then you will get your NullPointerException in that location, so Kotlin is just as powerful as Java in terms of producing NPEs, but here is the thing. The locations where you can get NPEs are limited to the places where you have done .unwrap(), which is much easier to search through, than the entire codebase, which is what you'd have to do in Java, where every single line could produce an NPE. So in reality if you push incomplete code to production, you will have strong markers in the code that indicate that it is unfinished.
There is likely a mix of reasons -- of which NPE avoidance is almost certainly one. And hiring/talent management is almost always another, when making technology choices. Particularly when choices are coupled with a blog post on the company's tech blog.
> The short answer is that any remaining Java code can be an agent of nullability chaos, especially if it’s not null safe and even more so if it’s central to the dependency graph. (For a more detailed explanation, see the section below on null safety.)
And that, right there, is all the reason they needed back then. Sun wanted C++ developers (and C developers, to some extent) to switch to Java.
Record types are 3 years old and they are already obsolete with regards to compile time null checking. This is a big problem in Java. A lot of new features have become legacy code and are now preventing future features to be included out of the box.
This is why the incremental approach to language updates doesn't work. You can't change the foundation and the foundation grows with every release.
I am awaiting the day Oracle releases class2 and record2 keywords for Java with sane defaults.
Now, this wasn't some thing that just dropped out of the blue - the problems were known for some time before. However, it was considered manageable, treated similarly to other cases where some operations are invalid on valid values, such as e.g. division by zero triggering a runtime error.
The other reason why there was some resistance to dropping nulls is because it makes a bunch of other PL design a lot easier. Consider this simple case: in Java, you can create an array of object references like so:
Foo[] a = new Foo[n]; // n is a variable so we don't know size in advance
The elements are all initialized to their default values, which for object references is null. If Foo isn't implicitly nullable, what should the elements be in this case? Modern PLs generally provide some kind of factory function or equivalent syntax that lets you write initialization code for each element based on index; e.g. in Kotlin, arrays have a constructor that takes an element initializer lambda: a = Array(n) { i -> new Foo(...) }
But this requires lambdas, which were not a common feature in mainstream PLs back in the 90s. Speaking more generally, it makes initialization more complicated to reason about, so when you're trying to keep the language semantics simple, this is a can of worms that makes it that much harder.Note that this isn't specific to arrays, either. For objects themselves, the same question arises wrt not-yet-initialized fields, e.g. supposing:
class Foo {
Foo other;
Foo() { ... }
}
What value does `this.other` have inside the constructor, before it gets a chance to assign anything there? In this simple case the compiler can look at control flow and forbid accessing `other` before it's assigned, but what if instead the constructor does a method call on `this` that is dynamically dispatched to some unknown method in a derived class that might or might not access `other`? (Coincidentally, this is exactly why in C++, classes during initialization "change" their type as their constructors run, so that virtual calls always dispatch to the implementation that will only see the initialized base class subobject, even in cases like using dynamic_cast to try to get a derived class pointer.)Again, you can ultimately resolve this with a bunch of restrictions and checks and additional syntax to work around some of that, but, again, it complicates the language significantly, and back then this amount of complexity was deemed rather extreme for a mainstream PL, and so hard to justify for nulls.
So we had to learn that lesson from experience first. And, arguably, we still haven't fully done that, when you consider that e.g. Go today makes this same exact tradeoff that Java did, and largely for the same reasons.
I mean, with the ressources meta has I’m sure they could have rewritten the tool, made a fork or done any other thing to incorporate their changes (they talk about overrides) or transformed the tool into something better fitting their approach. Maybe it has been done, just not clear from the article
Why? Bit of substance here would be nice. Otherwise it's just another "we migrated to $coolLanguage" post.
>With this in mind, we set out to automate the conversion process and minimize interference with our developers’ daily work. The result was a tool we call the Kotlinator that we built around J2K. It’s now comprised of six phases:
followed by a list of descriptions of the "phases" which only sort of make sense for the name given to them, and are utterly incoherent as actual phases in a process (and grammatically inconsistent). For example, one of the cited "phases" is... "headless J2K". In other words: they have one piece of software that wraps another, and it - gasp - doesn't use the wrapped software's GUI. Aside from being entirely unremarkable, that's neither a phase in a process nor a component of a tool. It's a fact about the component.
LLMs write like this all the time - and it's clear evidence that they do not, in fact, do anything like reasoning, even if they can sometimes be manipulated into generating a second piece of text that resembles an analysis of the first one. The resulting description is so weird that I question whether the authors actually checked the LLM's output for accuracy.
Any human writer who gives a damn about good writing and has any skill, would not allow "it" to refer to "the conversion process" two sentences back when "a tool called the Kotlinator" has been introduced in the interim (or, if that were the intended referent, would notice that tools are not "comprised of phases"). Such a writer would also not come up with abominations like "the conversion process is now comprised of six phases" where "we now use a six-phase conversion process" would be much clearer. Certainly, a six-point bullet list produced by a competent writer would label them in a grammatically consistent way (https://en.wikipedia.org/wiki/Parallelism_(grammar)) - not with an abstract noun describing an action, two participles, two concrete (well, as concrete as software ever is) nouns and a command (who, exactly, is being told to "build" the "error-based fixes" - whatever that means - here?).
I'm starting to feel like Mark Twain.
----
On the other hand, I was cynically expecting some mention of using AI for the actual task, and that doesn't seem to be the case.
(Also, the "reactive" web design is broken. The page overflows horizontally for some range of window widths, without causing a horizontal scrollbar to be added.)
Well, you could combine AI with correctness preserving transformations, so you get the best of both worlds (i.e., correctness AND a translation that keeps the resulting code close to what a human would write).
More importantly, why would you even do that? Feature-wise Java is pretty much a subset of Kotlin, so it's mostly a syntactic transformation, which we know very well how to do reliably with straightforward code.
Not one link to an opinion piece or two regarding: "kotlin vs java". The nearest thing I found was "What makes Kotlin different".
This sounds somewhat like a debate about which Germanic language is best. German, Dutch and English are all "Germanic" but which is "best"?
Obviously: That's the wrong question to ask and so an answer is doomed to failure.
In the end, does your generated machine code implore the CPU and associated hardware to do what you want it to more efficiently in some way that is not an abstraction?
Pissing contests rarely excite me. Why did you do this?
You literally quoted the "why": their developers prefer writing Kotlin over Java for Android development. That's it. They don't need further justification. They didn't need a "Kotlin vs. Java" comparison, and they're not really evangelizing Kotlin all that much. They're simply stating a fact for their organization: Kotlin is a better fit for their developers than Java is.
> In the end, does your generated machine code implore the CPU and associated hardware to do what you want it to more efficiently in some way that is not an abstraction?
Most shops don't care about this too much. The most important thing is developer productivity.
(And yes, this is why we have bloated garbage like Electron these days; sometimes some people value developer productivity to unhealthy extremes.)
And abstractions are useful. If they weren't, we'd still be writing in assembly.