When adopting a new language at scale, there are many different factors to consider because things can change dramatically. For many, choosing a language can arguably rely on personal preference, but at LinkedIn, we have a Foundation team tasked with evaluating the impact of such fundamental technical decisions. Recently, we underwent the process of evaluating and assessing languages for Android development. From the perspective of the mobile infrastructure team, I would like to share the steps we took in assessing languages that eventually led us to land on Kotlin.
Mobile language adoption at scale brings many different considerations. Such a decision can have a huge impact on developer productivity, hiring, training costs, and ultimately, the product. For example, you might increase developer productivity and be able to deliver more features, but have to pay the price in increasing app size, which can eventually affect user downloads. To better understand both the investment and return on a specific language adoption, we believe an unbiased technical analysis is fundamental to our decision. Once an objective assessment is made, we can then develop a roadmap best fit for mobile development across the company.
Android development at Linkedin spans teams of different shapes and sizes—from the large team working on a mega-size application, smaller teams working on multiple small and midsize applications, to the team focusing solely on building tools and infrastructure. In order to perform the assessment as comprehensively as possible, we established a committee with senior developers from each application team, representation from the infrastructure team, and build tools team. Under the context of LinkedIn Android development, these are the areas we looked through.
We were all thrilled when Google announced Kotlin as an officially supported programming language for Android development. Kotlin, as a language, comes with many new features not found in the Java world, such as null-safety, extensions, data classes, coroutines, and many more. While we enjoy all the benefits that these new features bring, Kotlin also drops certain Java features by design, such as direct primitive types, static members, checked exceptions, and so on. In our analysis, we took a deep dive to understand the sentiment behind the language design decisions and looked into the decompiled code to understand each feature. This helps us to establish guidelines for the future and be confident in the code we ship.
At LinkedIn’s scale, if we were to decide to move to a new language, the migration would be a long process, especially for codebases with millions of lines of code. Just as it’s important to understand the features of each language, it’s equally crucial to consider interoperability. Fortunately, the integration between Kotlin and Android Studio is such that it often provides suggestions and helps you refactor your code. However, at the end of the day, they are two different languages and making it work is just a start. There will certainly be enhancements you want to build along the way. For example, some we came across were adding JVM annotations for static members and ensuring the Java API has the Single Abstract Method (SAM) conversions parameters placed last so that Kotlin compiler can allow the use of trailing lambda syntax.
Build time is one of the biggest priorities among mobile engineers. With Kotlin, we expect build time to increase. LinkedIn mobile development varies from thousands to millions of lines of code; therefore, the increase in build time may vary significantly. It might not be immediately noticeable, but the increase over time in converting to Kotlin will be something we’ll keep a close eye on, especially at LinkedIn’s scale and size. To trial what the build time could look like, we used Android Studio Poet to create Android projects with different number of modules, lines of Kotlin, and Java code as a simulation, while keeping the project size and the dependency graph similar. We measured both clean and incremental build with Gradle Profiler to understand where the build time is spent.
Given the sheer scale of the project, it has the potential to influence many different aspects, from development to production. Runtime performance was another key area of this analysis. We implemented several algorithms in both Kotlin and Java as instrumentation tests and ran them on physical devices. To make the experiment more realistic, we also enabled ProGuard in these tests. The result correlated with the findings in Performance Evaluation of Kotlin and Java on Android Runtime. Interestingly, most of the overhead in Kotlin comes from the null-safety feature, which makes every call to a method with non-optional parameter and cannot be mapped to primitive types by compiler. This leads to a call to Intrinsics.checkParameterIsNotNull, which is responsible for most of the overhead in our experiments.
App size is a result of many factors. According to Google, the initial Kotlin standard library import will increase application size by ~1 MB. As time goes by and as more Java code is converted to Kotlin, the app will be bigger due to method increases from Kotlin byte code. Fortunately, ProGuard is able to trim and greatly reduce unused methods from Kotlin generated code. We created sample applications and converted them into pure Kotlin from Java to understand the overhead for method counts and app sizes.
The landscape for mobile development is constantly changing and it’s important to understand the upgrade process. In the past, we’ve seen language upgrade come with incompatible changes, build failures, and many unexpected errors that eventually forced us to lock the trunk to even perform a language upgrade. JetBrains promised two forms of backwards compatibility for Kotlin:
A newer compiler will work with older binaries (but older compilers may not understand newer binaries, like javac 1.6, which can’t read classes compiled by javac 1.8).
Older binaries will keep working with newer binaries at runtime while newer code may require newer dependencies.
In terms of source compatibility, Kotlin follows a pattern of API deprecation WARNING, which gradually moves to ERROR once you upgrade to a new compiler version. In the past, we’ve seen the upgrade usually give the community enough time to adopt these changes. For example, the language behavior change in KT-21515 was marked as warning and stayed for the remaining 1.2.x patch versions until Kotlin 1.3. However, it is worth noting that JetBrains makes no statement about what types of compatibility will be provided across major version changes.
It’s not surprising that Kotlin works naturally with Android Studio since JetBrains is the major contributor behind both products. Most, if not all, of the Android Studio tools work seamlessly with Kotlin, including debugger, memory profiler, android profiler, and etc. Some Kotlin features worth mentioning include Kotlin auto-conversion and Kotlin decompiler. The latter gives you insight into what your Kotlin code is doing under the hood.
LinkedIn uses Gradle as the de-facto build system for Android and Java products. Kotlin works smoothly in Gradle with the Kotlin-Android gradle plugin. Gradle enterprise provides remote caching solution. Kotlin 1.2.21 allows Kotlin projects to make use of build caching. Specifically, for the Gradle Build Cache, it also requires the use of Kotlin Gradle Plugin 1.2.20 and the use of Gradle 4.3 or above. Overall, changes in the build tools for Kotlin adoption were minimal. However, our analysis primarily focuses on Gradle, and may not apply to other build systems such as Buck.
As much as we focus on developer productivity, quality is another significant consideration to look at. Writing tests are essential to ensuring code quality. We spent time to understand the limitations in our testing ecosystem and its compatibilities with other languages. This includes not only the testing framework, but also the code coverage analysis, CI pipeline and most importantly, the steps to be taken before we start writing Kotlin tests without compromising our testing standards. Also included in this analysis is the primary static analysis tool in Android, Android Lint, which now uses UAST to support both Java and Kotlin analysis and style check tool.
Since the announcement of Kotlin as a first-class language for Android development at Google I/O in 2017, an increasing number of engineers have embraced Kotlin. The Stack Overflow 2018 survey reported Kotlin as the second most loved programming language. Kotlin is also cited as the #1 fastest grower of upcoming languages, alongside Swift, Go, Haskell, and Rust. According to Google, in 2017 when Kotlin was announced as a first class language for Android development, 9% of apps in Google Play were already using Kotlin. One year later, in May 2018, Google announced that this number had increased to 35%, an almost sixfold increase year over year.
We analyzed the top 300 free apps from the Google Play store and filtered out applications based on their DEX file size to understand how many applications are using Kotlin at scale. While this number includes the code for all third-party libraries, it gives a good impression of application code size. As a preliminary step, we excluded applications without a special Kotlin folder in their APKs, which would contain some Kotlin builtins used by the Kotlin runtime. To calculate the percentage of Kotlin in their code is tricky due to obfuscation. However, we converted all DEX files to SMALI code by using apktool and found two different footprints that identify a class as written in Kotlin. For apps that do not strip out the original file name from .source attribute we could extract an extension: *.kt. For other apps we looked for kotlin.Metadata class. Overall, we were able to establish a chart showing applications and their class counts, code sizes, and Kotlin percentages. This provided us insight into how mature the community is and how much support we can expect from our partners.
Initially, most library owners felt that using Kotlin as an implementation detail in their own code was premature and not something they should force upon their consumers. As the Kotlin community has become more active, the opinion has started to shift. Square, the owner of many of the most popular Android open source libraries, announced that their core Okio library had been rewritten in Kotlin and that they will be using Kotlin in all future releases. Some other library projects have chosen to follow the example Google set with Android KTX and released a base library written in Java with additions of Kotlin-specific extension points in separate artifacts. Examples include FasterXML’s Jackson project, which added a Kotlin module for serializing and deserializing Kotlin classes, and Mockito, which is investigating adding a Kotlin-optimized mocking syntax.
A holistic review
In addition to the points we listed above, we also invested time looking into the integration with LinkedIn internal tool systems, including crash report, review process, and CI pipeline. We have continuously talked to developers from the community and other companies, including Google and JetBrains, to not only understand their directions, but also to ensure our decision would align with future Android development. Talent is always one of our top priorities; thus, costs associated with hiring and training were also important parts of our analysis.
Overall, we’ve heard all sorts of benefits and testimonies from the community. With this comprehensive analysis, we are able to build a roadmap that takes LinkedIn to the next generation of Android development in Kotlin with confidence that we minimize the technical risk, understand the impact on hiring and training, maintain the standard of craftsmanship, and continuously deliver value to our members.
This language analysis could not have been completed without the relentless efforts from our Kotlin evaluation committee members: Alexey Dubovkin, Drew Hannay, Tony Lin, Malcolm Nguyen, Saurabh Patel, Siddarth Sodhani, Sajjad Tabib, Prince Valluri, YJ Yang and Michael Zeng.