A language that doesn’t affect the way you think about programming is not worth knowing.
Alan J. Perlis
Migrating to a new programming language can be daunting. This chapter gives practices that have helped other companies successfully migrate. But because a migration is not just a technical matter, this chapter also covers the nontechnical aspects involved, such as communication, getting buy-in, and risks to consider.
Changing any tool, technology, or technique involved in the software development process is not just a technical decision because it also affects business concerns, such as deployment cycles, user satisfaction, and estimated project effort. For these reasons, a migration requires buy-in from the whole team as well as the responsible manager(s).
Also, as a technical person, it is important to keep in mind that, from a business perspective, many more aspects come together in a project that affect its chance to succeed. Therefore, when pitching the adoption of a new tool like a programming language to your manager, you should have a realistic view of what impact this tool can have, taking into consideration all that makes up the project.
Every migration or tool change has inherent risks. This means that there is always a certain barrier to introducing change, and a certain “activation energy” necessary to trigger change. Risks exist on the technical and the business level. On the technical level, risks include problems integrating with the toolchain or the existing technology stack, unexpected roadblocks, leaving behind experience with the previous tool or technology, and transferring knowledge. On a business level, risks include negative effects on productivity, team and user satisfaction, and keeping deadlines even during the transition phase.
Each of these risks can also be turned into a benefit if the effects of the migration are predominantly positive. Namely, increasing satisfaction and motivation, fewer roadblocks and smoother development, better interoperability with the current technology stack, and so forth. As a rule of thumb, migrating to a new tool or technology will likely slow you down in the short term, but this initial slowdown should repay in the medium or long term. Of course, this can make it hard to struggle through the migration and justify its necessity—which is why buy-in is crucial before attempting to migrate to any new technology or tool.
Note
Think about each of these general points in terms of migrating from Java to Kotlin to contemplate benefits and risks it may have in your opinion.
Generally, a migration must have a defined purpose—and adopting a new technology because it is becoming popular is not a purpose. This is not to imply that adopting Kotlin doesn’t have a purpose—it can have many benefits as you will see. Good questions to ask before any migration include the following.
Is the tool, technology, or technique mature and proven to be effective in practice?
Are other companies successful with it?
Does it have a supportive community?
Do you have experts in the team who can facilitate the adoption?
Is the team interested in using (and learning) the new tool or technology?
Does it integrate well with the current technology stack and tooling?
In the case of Kotlin, it has been proven mature enough to be incorporated into the technology stack; many companies have done so successfully. These also contribute to Kotlin’s very active community. The other questions depend on your company and team, but can also be affected by you—you can be the expert facilitating the change, and convince your team of the benefits Kotlin can have to spark interest in the language and the motivation to learn it.
In terms of tooling for Kotlin, there are certainly obstacles to overcome. For instance, compiler plugins and static analysis tools are not as abundant for this young language as they are for Java. Additionally, while Gradle is pushing forward with its excellent Kotlin support, other build tools do not offer special Kotlin support and may not play well with language features that don’t exist in Java. The same can be said for several libraries such as Gson (a JSON mapper), which cannot handle concepts like primary constructors. While Gson can be replaced by Moshi when using Kotlin, not all libraries may have such a direct counterpart. Thus, the best way to evaluate Kotlin is to test it with your tech and tool stack to explore possible issues.
Bringing about a tool change is a task that requires leadership, communication, and ultimately convincing. Especially when it comes to programming languages, developers tend to have strong opinions and preferences because it’s one of the most immediate tools they constantly work with—just like the IDE and the version control system. Thus, adopting a new language may be one of the hardest changes if your team doesn’t like the new language—or one of the easiest if you can get them excited about the new language. This section provides guidance and actionable tips to lead the change.
Adopting a new programming language requires buy-in from everyone involved—but how can you get that buy-in? This depends heavily on whom you want to convince (a technical versus business person) and at what kind of company you work (a startup versus a large corporation). Ultimately, with buy-in, developers are more likely to push through the obstacles that will occur during migration.
Here, I’ll loosely differentiate between “technical people” and “business people” to give examples for arguments addressing both perspectives. Mostly, it is about framing the same underlying benefit in the correct context. Arguments you can use to entice the use of Kotlin include the following.
Fewer null-pointer exceptions: Even after full migration to Kotlin, unfortunately you cannot guarantee complete null safety because you’re still interoperating with Java in the Android SDK and likely in several dependencies. Still, Kotlin helps reduce null-pointer exceptions which, from a business perspective, means fewer app crashes, more ads shown, better user retention, and so forth.
Smaller pull request and code reviews: With fewer lines of (readable) code, developers can review code faster. Also, you need less time to think about null cases in reviews if you use mostly non-nullable types in Kotlin and can focus on more interesting aspects instead. From a business perspective, faster reviews mean increased productivity and higher code quality.
Map language features to issues: Be perceptive about common issues in your code base and link them to Kotlin features that can help mitigate such issues so that you have concrete examples for how Kotlin would improve the code base. Christina Lee used this approach, among others, to introduce Kotlin at Pinterest in 2016.1
Excited and motivated developers: Refer to surveys2,3 that indicate that Kotlin users love the language4,5 and therefore tend to be more motivated and more productive.
2. https://pusher.com/state-of-kotlin
3. https://www.jetbrains.com/research/devecosystem-2018/kotlin/
4. https://insights.stackoverflow.com/survey/2018/#technology-most-loved-dreaded-and-wanted-languages
5. https://zeroturnaround.com/rebellabs/developer-productivity-report-2017-why-do-you-use-java-tools-you-use/
Removing third-party dependencies: If migrating to Kotlin fully, libraries like Retrolambda,6 Butter Knife,7 Lombok, and AutoValue8 are no longer necessary in the long term, also reducing the method count and APK size.
6. https://github.com/luontola/retrolambda
Officially supported: Last but not least, don’t forget to mention Kotlin is an official language on Android backed by Google. This and the fact that JetBrains has a large team working on the language means that it’s not going away anytime soon. For the business, this means it is a stable tool that is unlikely to become legacy in a few years.
These are by no means all the benefits. You can pour out all that you’ve learned in this book to give an accurate picture of Kotlin’s advantages and potential drawbacks, then team up with others excited about Kotlin to lead the change. With enough buy-in from your team members, you can pitch the idea to everyone involved, for instance, by composing a brief document that pitches the language—like Jake Wharton did to lead the adoption at Square.9
9. https://docs.google.com/document/d/1ReS3ep-hjxWA8kZi0YqDbEhCqTt29hG8P44aA9W0DM8/
Although you want to focus on the benefits here, you must manage expectations. Obviously, you should paint an accurate picture of the language and communicate clearly that adoption comes with a learning curve that will initially slow down the team but should be amortized later. Also, the decision to migrate must be evaluated thoroughly; keep in mind that not migrating may be the better choice for you or your company.
Sharing knowledge is particularly essential before and when starting the migration in order to inform people about the technology and what it can do. This should be planned for ahead of time as it will take time and effort across the team. Approaches to share knowledge successfully include:
Pair programming: This allows instant feedback between colleagues and sparks discussions about best practices, idiomatic code, and so forth. Consider pairing a more experienced Kotlin developer with a language learner to speed up knowledge transfer.
Organize talks, presentations, and workshops: These can be internal, in user groups, or one of the big Kotlin conferences like KotlinConf.10
User groups: If you do not have a user group in your area yet, consider founding one to bounce ideas off likeminded developers, learn from them, and let them learn from you. If there is one already, it’s an ideal way to expose yourself and your team to the language and community.
Promote collective discussions: Especially while people familiarize themselves with Kotlin, it’s important to address their doubts, ideas, questions, and opinions. Discussions with the whole team allow carving out agreed-upon practices, conventions, and action plans for the migration.
Document knowledge: An internal wiki or another easily accessible resource is a great place to document best practices, benefits, risks, and all aspects affecting the migration.
Tap into the community: The best way to succeed with migration is to learn from people who have done it. Luckily, the Kotlin community is extremely active and supportive, so remember to use it. Personally, I consider the Slack channel to be the primary place to tap into the community.11
Let’s assume you have successfully pitched Kotlin adoption, or maybe you just decided to migrate a pet project of yours. You need a migration plan. This section discusses advantages and disadvantages of the two main types of migration—partial migration versus full migration. As you will see, these have quite different consequences.
Partial migration means that you mix Kotlin and Java in your project. Even so, you get several benefits:
Reduced lines of code: This affects overall code base size, code reviews, and pull requests.
The feeling of having Kotlin deployed: This shows feasibility and builds trust that Kotlin can be used in production and deployed to clients without problems.
Experience gain: Any exposure to Kotlin, especially writing code yourself, increases knowledge and experience. The more you migrate, the more proficient you become.
These are on top of all of Kotlin’s benefits that you already know of. Unfortunately, partial migration and a polyglot code base with mixed languages comes at a cost. The most important drawbacks to keep in mind include the following:
Harder to maintain: Constantly switching between two languages in your code base means a lot of context switching and thus cognitive overhead. Also, in the case of Kotlin and Java, you will likely find yourself writing constructs of one language in the other from time to time because they look so similar.
Harder to hire people: There are far fewer Kotlin developers than Java developers so, from a business perspective, it is important to be aware that it is harder to find proficient developers, and you may have increased onboarding time on the project.
Increased build times: Mixing Kotlin and Java in your project will increase compilation and build times. Assuming you’re using Gradle, incremental build times don’t increase as much as clean build times, and Kotlin build times have also improved significantly. The first converted file has most impact, and each following Kotlin file does not significantly affect build time anymore. Figure 10.1 demonstrates why this effect occurs. Basically, introducing just one Kotlin file requires the Kotlin compiler to compile all Java files that it depends on, which takes a noticeable amount of time.
Migrating module by module reduces this increase in build time and reduces the number of integration points and thus context switches. As mentioned, build tools other than Gradle may show more significant increases in build time because Gradle specifically works on its Kotlin integration.
Interoperability issues: Although Kotlin interoperates well with Java, there are still several things to consider—which is why this book has a whole chapter dedicated to interoperability. If at least your own code is entirely in Kotlin, many interoperability issues disappear.
Hard to reverse: Migrating and converting files from Java to Kotlin is easy but can be hard to reverse; in fact, it would require rewriting the file from scratch if too many changes have been made so that version control becomes useless.
In short, a polyglot code base introduces several drawbacks that may outweigh the benefits of introducing Kotlin in your project. Therefore, it is important to realistically evaluate the pros and cons before doing a partial migration (which may be the only feasible option of migration). In large projects, even if your goal is to migrate entirely, you may have to live with these issues for a considerable amount of time. In smaller projects, where it can be done in a reasonable amount of time, I would recommend focusing on migrating the entire code base quickly (or not migrating at all).
All of the benefits of partial migration above also apply to full migration, as well as some of the drawbacks.
It is still harder to hire people but at least developers must be proficient only in Kotlin.
The migration is hard to reverse once you’ve introduced enough changes in Kotlin so that the last Java version in version control is useless.
Interoperability issues are not entirely mitigated because at least the generated R
file and BuildConfig
continue to use Java, as well as likely some of the libraries you use.
However, dedicating to a full migration brings great benefits. On top of all the benefits listed for partial migration, these include:
Better build times because all your own modules use exclusively Kotlin, taking full benefit of the reduction in build time this creates.
A system that is easier to maintain because single-language production and test code requires fewer context switches, introduces fewer interoperability issues, and allows faster onboarding.
In summary, aim for a full migration if you do decide to adopt Kotlin at your company. For small projects, this can be done in a relatively short time. In larger projects, it may well be that a full migration is not feasible due to third-party dependencies, internal restrictions, or simply the effort involved. In these cases, you can still follow a migration plan as outlined below.
In any case, you should have an agreed-upon migration plan that everyone involved follows, and you should introduce Kotlin ideally module by module and for each module package by package to minimize build time, the number of integration points, and context switches.
If you decide to adopt Kotlin, whether partially or fully, the next question becomes: “Where do I start?” This section covers three ways to start integrating Kotlin into your code base, along with the respective advantages and disadvantages.
The first possibility is to start writing or migrating test cases in Kotlin. At the time of writing, this is proposed on the Android Developers website12 and many companies have successfully used this approach. Test code is rather simple, so this is an easy place to start trying out Kotlin. It’s also relatively easy to migrate back to Java.
12. https://developer.android.com/kotlin/get-started#kotlin
However, there are many arguments against this approach. There are two basic scenarios: You are adding new test cases or migrating existing ones. In the first scenario, you’re adding new tests in Kotlin for existing functionality—so you’re testing after the fact and not in a test-first manner. Also, this only works if you had gaps in your tests in the first place. In the other scenario, you’re migrating existing Java test cases to Kotlin. This incurs several risks.
Bugs that you unknowingly introduce into the test code can lead to production bugs in the corresponding functionality—and introducing such bugs can happen especially while you don’t have much experience with Kotlin yet. For instance, consider the subtle difference given in Listing 10.1 that may cause a bug without your knowing.
You don’t refactor test cases as often as your production code. Thus, an intricate bug in the test code is likely to remain for a long time.
val person: Person? = getPersonOrNull()
if (person != null) {
person.getSpouseOrNull() // Let's say this returns null
} else {
println("No person found (if/else)") // Not printed
}
person?.let {
person.getSpouseOrNull() // Let's say this returns null
} ?: println("No person found (let)") // Printed (because left-hand side is null)!
Test cases are not tested themselves, so you are basically operating in the dark. Most test code is rather simple, but if you do make a mistake, you are less likely to notice.
In test code, you cannot usually make full use of Kotlin’s powerful language features that provide actual benefits. In fact, if you only rewrite your JUnit13 test cases with Kotlin using JUnit, you won’t gain much at all—you would mostly be writing Java-like code in Kotlin, not improving your code quality much at all.
All in all, although test code has become a popular starting point to introduce Kotlin, I personally consider the following two approaches preferable.
I’ll assume here that your production code is thoroughly tested so that changes can be made with confidence, and if a bug is introduced during migration, one of the test cases should fail. This greatly supports migration because you know if you did something wrong. Other benefits include the following.
You’re implementing actual product features, which is not only a lot more motivating but also gives you the confidence to know that you can deploy Kotlin to your users.
You can make full use of Kotlin’s language features, even those that are rarely used in test code, such as sealed classes, coroutines, and delegated properties. You can compare your implementation directly with the previous one in Java to see the benefits.
You can start with very simple features or functionality so that you are unlikely to make mistakes, then work your way up to migrating the more complex parts of your app.
Your production code is refactored regularly so that even if you did introduce a bug or wrote unclean or unidiomatic code, it is more likely to be fixed soon after.
In summary, don’t assume that migrating test code is the safest way to start migrating. If you have a strong test suite, making changes to your production code is a lot safer because you get direct feedback in case you introduce a bug.
If you do not yet have a (good) test suite, then adding new tests in Kotlin first is a reasonable alternative. Adding them after the fact is still better than not testing at all, and you can combine this with migrating the corresponding production code.
Pet projects are probably the best way to gain experience with Kotlin once you’re familiar with the language—which you certainly are after working through this book. You should work on pet projects by yourself to further familiarize yourself with the language before pitching it to your company. If other team members are interested in evaluating Kotlin, it is the perfect chance to work on a pet project together. If you’re considering adopting Kotlin at your company, work on the pet project with the team that would be affected by the adoption—ideally also using the same technology stack to encounter possible problems ahead of time. For instance, Kotlin does not play well with Lombok. So if you have a large project using Lombok where you cannot easily migrate all Lombok uses, you’ll have to think about how to deal with this incompatibility beforehand.
On the downside, this approach costs effort and time without direct progress on company products. But there are many benefits to pet projects that can make them well worth the investment.
Pet projects provide a safe environment to compare different solutions and conventions, such as finding an adequate balance between conciseness and expressiveness, how much to use functional programming concepts, or what can be solved easily using the standard library.
Each team member has the chance to evaluate Kotlin’s drawbacks and benefits for himself or herself and discuss them with teammates.
You can use pair programming in pet projects to accelerate knowledge transfer from people already familiar with Kotlin and to spark discussions.
You can collect data (ideally with the same technology stack) to discover potential problems ahead of time. To measure build times, you can use the Gradle Profiler.14
You will come across issues early on—and before using Kotlin in production. Whether this is not being able to run individual test cases with Spek15 (a testing framework for Kotlin), Mockito16 not always playing smoothly with Kotlin (MockK17 is a good alternative), or the tendency of tooling to be behind a bit.
Pet projects are incredibly effective to evaluate Kotlin before even considering a migration. It not only gives you the opportunity to research build tool integrations, third-party libraries, and other integration points; it also allows you to start developing internal libraries that encapsulate common use cases in well-defined APIs and would be useful in future projects. What’s more, it gives you a chance to evaluate testing best practices and test infrastructure.
Generally, I’d recommend starting off with pet projects in the team and, if Kotlin should be adopted, start with simple and well-tested functionality in a non-business-critical app.
The previous sections already outlined general practices that can all be part of a migration plan. Here, we summarize and extend upon them again as an overview.
Start with simple and thoroughly tested functionality where you are unlikely to introduce bugs without noticing.
Migrate module by module, and within that, package by package to improve build times and reduce integration points.
Plan when to migrate test code and evaluate testing frameworks and infrastructure for Kotlin in pet projects.
Isolate Kotlin’s API for higher-level Java consumers to avoid interoperability issues from using Kotlin’s standard library or own APIs from Java.
Write all new features in Kotlin and enforce this in pull requests.
Consider migrating every file that you touch to fix a bug or to refactor it.
Block dedicated time to focus on migration in larger projects, for instance, in every sprint if you are using Scrum.
These general rules help guide the process. SoundCloud18 and Udacity,19 for example, both followed the last three points when adopting Kotlin.20,21 Agree on a clear set of rules with your team, and work out a concrete migration plan that follows the above ideas.
20. https://fernandocejas.com/2017/10/20/smooth-your-migration-to-kotlin/
21. https://engineering.udacity.com/adopting-kotlin-c12f10fd85d1
The Java-to-Kotlin converter is a useful tool to speed up migration. This section covers how to use it, what to do after using it, what to take heed of, and general tips to facilitate migration.
The converter is bundled into the Kotlin plugin so it’s accessible in Android Studio and IntelliJ by default. It is useful not only to make quick progress when integrating Kotlin but also to learn the ropes for beginners by comparing the generated code to the original Java code.
You can trigger the converter in different ways. First, you can invoke it under Code and then Convert Java File to Kotlin File in Android Studio’s menu to convert the current file. Second, whenever you paste code from a Java file into a Kotlin file, Android Studio will automatically prompt you to convert the code. Third, although this action is currently named Convert Java File to Kotlin File, it can convert whole packages, modules, or even projects. So you can right-click on any directory in the project view and trigger the action from there to recursively convert all its Java files.
Note
Don’t autoconvert large parts of your code base without a plan and the time to go through and refactor all converted files. Even then, I’d still recommend doing the conversion file by file to migrate a package or module to have better control over the process.
Irrespective of how you decide to use the converter, you will have to adjust most converted code to follow best practices, to use idiomatic patterns, and to improve readability. After all, there is only so much an automated tool can do. Here, we provide a checklist of common changes you’ll have to make.
Avoid the unsafe call operator (!!
) wherever possible—and do not hesitate to restructure the code to avoid nullability in the first place.
Move helper methods to file level where appropriate.
Avoid overuse of companion objects for all that was static in Java; consider using top-level declarations instead, and consider using const
on top-level variables.
Join property declaration and initialization—Android Studio will suggest this as well.
Decide whether to keep @Throws
annotations for methods. Recall that this is useful if they are also called from Java or for documentation purposes.
Use function shorthand syntax where it is possible and improves readability.
More high-level questions to ask include:
Which methods would better be extension functions? Especially when converting a utility class, you will likely want to turn its helper methods into extension functions. But other methods may also benefit from the transformation.
Can I use delegated properties? For instance, if you’re building a complex object that is only used under certain conditions, put it into a lazy property.
Can more declarations be immutable? Rethink any use of mutable data and var
to abide by Kotlin’s mentality. This of course should be done in Java code as well.
Can I use read-only collections? Although they’re not strictly immutable, prefer Kotlin’s read-only collections to mutable ones. As Java only has mutable collections in its standard library, the converter will keep them and use types like ArrayList
.
Could an infix function or operator improve readability? You should use these judiciously (especially operators), but if they do fit a use case, they can increase readability at the call site.
Would part of the system benefit from a custom DSL? For instance, if there is a complex class (whether your own or third-party) of which you frequently build objects, a type-safe builder DSL may be a good idea.
Would my asynchronous code benefit from coroutines? For example, if you’re using many AsyncTask
s, using coroutines can greatly reduce complexity.
Not all these changes are trivial; some can require substantial refactoring. But all are important considerations to make on the way to a high-quality code base—after all, this is why you would want to migrate to Kotlin in the first place. My hope is that this checklist helps guide you to a code base that all developers agree was worth the work for the migration.
Note
Converting any file will delete the original .java
file and add a new .kt
file. Thus, version control history for the Java file quickly becomes useless when modifying the Kotlin code.
This chapter covered the technical and nontechnical aspects of migrating to Kotlin (or a new tool in general), from implications on build time and code base quality, to getting buy-in and pitching adoption at your company. This summary recaps the primary steps involved, roughly in a chronological order.
Get a good understanding of Kotlin—which you have done with this book.
Implement a pet project in Kotlin by yourself—you have already created two with guidance in this book, so try one without step-by-step guidance. Also, if you have an existing Java pet project, try migrating it to Kotlin.
Watch for common issues in your company’s code base and map them to Kotlin features that would help solve those issues.
Talk to your colleagues about Kotlin and let them know what it can and cannot do. Paint an accurate picture about its benefits and drawbacks. Be open to discussions and critique to establish a culture of learning and to get buy-in from other team members.
Pitch an evaluation of Kotlin at your company, for instance, with a document highlighting features, benefits, and compatibility with the company’s technology stack.
If your company wants to adopt Kotlin, decide in advance whether to aim for a full migration and agree on a migration plan.
Work on a pet project with your team, ideally evaluating the same technology stack as the product you want to eventually migrate.
Migrate a simple feature that is well tested to Kotlin and celebrate the fact that you can deploy Kotlin to production.
Use the converter, but expect to adjust the autoconverted code.
Don’t stop at 90% if you aimed for a full migration, even if the last packages and modules are harder to migrate, require bigger restructuring, and you could be working on new features instead. Remember the benefits of full migration.
This is the bird’s-eye view of the main steps involved in the adoption of Kotlin. Keep in mind that not every developer will be eager to switch to a new programming language, that it introduces risks on the technical and business level, and that it may in fact not be the best choice for your company. However, Kotlin can substantially improve developer experience, productivity, code quality, and eventually product quality. The recommendations in this chapter aim to help you evaluate which are true in your case.