Guest(s): Eve Matthaey, Software Engineer; Jocelyn Luizzi, Software Engineer
How do you translate roughly ten million lines of Java to Kotlin—without breaking production, burning developer time, or relying on endless manual IDE clicks?
In this episode, Pascal Hartig sits down with Eve Matthaey and Jocelyn Luizzi, Meta software engineers on the Mobile Infra Codebases team, to unpack how they’re translating Java to Kotlin across the monorepo. They walk through the Kotlinator pipeline (headless J2K + layers of PSI-based pre/post processing), how Codemod Service ships changes safely like normal diffs, and why nullability is the hardest part of the migration.
Pascal: Hello and welcome to episode 71 of the Meta Tech Podcast, an interview podcast by Meta where we talk to engineers who work on our different technologies. My name is Pascal and even though it's nearly February by the time this goes out, I still want to wish you all a happy new year or happy Q2 of the 21st century if that's how you roll.
You may accuse me of some Android favoritism with another episode about Kotlin in our Android codebase, but I'm sure that there are some useful lessons in this interview, even if you've never opened Android Studio in your life. My guests, Eve and Jocelyn, are working on the truly monumental task of translating roughly 10 million lines of Java code over to Kotlin.
As so often, when you're working at this scale, many of the existing out-of-the-box solutions no longer work. So, Eve and Justin had to get creative. We'll talk about what it takes to run a successful code mod at this scale, the infrastructure we have to support this, and some of the surprising challenges that came up along the way. And now without further ado, here is my interview with Eve and Jocelyn.
Pascal: Can there be too much talk about code based health and modernization? Maybe we're about to find out because that's once again, the topic of the month.
I for one couldn't be more excited to talk to the mostly invisible forces that have been migrating our Android Java code base over to modern Kotlin. And I have two engineers from the mobile infra code basis team with me here today to discuss the challenges and opportunities of migrating millions of lines of code.
Eve and Jocelyn, welcome to the Meta Tech Podcast
Jocelyn: Hello.
Pascal: Before we dive into the topic, I want to give you a brief opportunity to introduce yourself. So Eve, can I maybe start with you? How long have you been at Meta? And did you do anything before you looked into modernizing our codebases?
Eve: so I've been at Meta a little over five years now, and, before I started working on the Kotlin migration, I had previously worked on a scheduling framework that aims to be more conservative about how we, you know, use resources for parallelized jobs in our various Android apps.
Pascal: Wow, I had no idea you worked on this. That's really cool. So Jocelyn, what about you?
Jocelyn: Yeah, I have been at Meta for a little over three years now, and before I moved on to this project, I was working on the mobile dead code team and just deleting dead code, cleaning up stuff in our code bases that shouldn't be there anymore.
Pascal: Ooh, that's also always really exciting. I like when dependencies just disappear from my graph because they've been unused and dead. Dead code. Codemods come up. But can you just generalize this a little bit for me? So what does your team actually do when you just kind of look across all these different projects?
What's the mission?
Eve: That has been a historically difficult question to answer because it's quite the grab bag. Maybe I'll take a stab at it now. Jocelyn's been on the team longer than I have. I kind of got shifted onto here a few years back. But whenever there's something to do that has to do with the health of the entire code base, especially if it requires some sort of large scale Codemodding, that is typically something that falls under our purview.
So things like Android SDK upgrades, sometimes aspects of compiler or version upgrades as well. Obviously language upgrades. Dead code but Jocelyn will know more about the dead code part.
Jocelyn: Yeah. I would say just to put it super concisely, our team in general is modernizing mobile code bases.
Pascal: Well, that's very concise, I like it. Can you give us a bit of a sense of the scale that you're dealing with when we're just kind of talking about the code base?
Eve: So when we're saying the codebase here, we are referring to our mobile codebases. That is of course iOS and Android, though Jocelyn and I only work on the Android side. Or actually no, Jocelyn's touched on iOS code too, right? I'm still an iOS virgin. But on the Android monorepo side, we have tens of millions of lines of code.
We've said before earlier this, well actually last year now, that we had over 20 million lines of Kotlin and that number has only grown. And of course we have not yet gotten rid of all of our Java. So, this is spread across hundreds of thousands of files, thousands, tens of thousands of, of modules.
Several hundred, if not over a thousand developers.
Pascal: Whenever we publish a blog post and these numbers show up in them, something like 20 million lines of Kotlin code, I feel like a lot of people have this kind of knee jerk reaction of like, what is wrong with you? Why do you have so much code? Is there an answer to this?
Eve: There are many varied answers. I think the, the biggest one that people perhaps forget is that we have a lot of apps, uh, people here, especially when the company was still called Facebook, they think just Facebook, but it's Facebook, it's Instagram, it's Messenger, it's Facebook Lite, Messenger for kids, Ads Manager.
Pascal: Threads
Eve: Threads. All these other, um, so many apps, and of course they do share some infrastructure, but not all, some of them, you know, when they were written, they weren't necessarily designed, like the Facebook app wasn't originally written with the thought of sharing a ton of code, though that has happened over time.
And, you know, there are, our team does dead code deletion too, there are certainly things in there that could and should be deleted, but it's not that easily done, and it takes a while.
Pascal: Right. I think that's a good enough answer and we can move on. There are also tons of internal apps that are in the same repository. It is a monorepository in the end, right? So let's move on to the conversion part. So we found on the podcast here before from other guests that we are actually in a position now where most of the new code that is written for Android is in Kotlin. So you're not talking about migrating existing code. Can you just put it bluntly, like, why do we care?
Why do we care about the old code being actually modernized into new stuff if the new stuff is already successfully written in Kotlin?
Eve: So the simple answer is that developers prefer Kotlin, and they're more productive in it. And although we would all love to write new code all the time, if we could, we are also reading and modifying a lot of code that already exists. So in order to just make developers happier and more productive, it makes sense to convert the code that is still being actively modified or even just read in many cases.
There are several other reasons as well. The, that is certainly the, the marquee reason. Amongst them, mixed codebases are just not that great. I mean, we'll always have a mixed codebase. There's no way we're going to get rid of all of our Java. But at least if we can get to a point where the vast majority of our code is in Kotlin, we will be less affected by some of the downsides of mixed codebases. Like having to support parallel toolchains, the sort of mental overhead of having to switch between languages while you're, you know, reading or debugging something.
Also of course the whole nullability, uh, chaos situation.
Pascal: Yeah, for sure. Before we get into the nullability, just let me quickly say, this definitely resonates with me. I'm on a team that doesn't really write a whole lot of just brand new code, which means I usually look at some sort of legacy aspect. And more often than not these days, I see it being Kotlin, but every now and then it is Java.
And if it wasn't for mostly you, I would only be touching Java. And it feels like there's a, there's kind of an aspect of fairness to all of this, because if I wanted to write Kotlin, I would have to join a product team that works on something brand new and working in this kind of infra space that I occupy, I would have no chance of ever actually interfacing with modern languages.
So yeah, thanks for what you're doing. And I think now we can probably move on, but actually let me throw in one more just kind of devil's advocate question here, and then I'm done with it, I promise. Isn't Kotlin slower to compile?
Eve: Yes it is. It's definitely slower to compile, but it's a, you know, a worthwhile trade off and it's gotten much faster, uh, over the years. Of course, the worst case scenario is what we have now, in many cases, which is a mixed code base, especially a module, which is a mix of languages. So, at the very least, we want to leave ourselves in a state where all of our modules are one language alone, because the only thing slower than compiling Kotlin is compiling Java and Kotlin together.
Pascal: Good answer. Okay, so what is it that makes this whole project so complicated? Because if I remember correctly, if I open up Android Studio and I open an old Java file, there is a nice little button that actually asks me if I want to convert this file to Kotlin, and I guess I'm just done, right? Why don't you just do that?
Actually, this is a bit devil's advocate-y again, isn't it? So I think I just broke my promise, sorry
Eve: That's fair. Ah, we get this question a lot as well. Um, well, we get it from both directions, actually. Why didn't we use the thing right out of the box? And also, why didn't we just start from scratch and create our own? I guess the reason we're not just working with it right out of the box, is that we tried that and it didn't work. We have so much code that, say it was just me, clicking away, I've been working on this since, since 2021, so I would still be going. In fact, I would have barely made a dent in this project if I were clicking. It would take me something like a hundred years in order to do all of these conversions.
Jocelyn: And on the flip side of that, if we had every single engineer click the button for their own code, they would all get very frustrated sitting there waiting the five minutes for every file to convert. And it would be a lot of lost time and a lot of rage quitting of Android studio.
Pascal: Yeah, we've talked about the inner and outer loop of development cycle said before, and this is kind of your inner loop again, doesn't get any more, uh, inner than this, because it's literally blocking your ID. You can't do anything else unless you have a separate laptop or whatever where you can have a separate checkout and do your work.
You can't do anything else. So just by moving this out of this inner cycle and allowing people to do something else in the meantime I think that is very helpful.
Before we get to some of the trickier challenges behind this. I would like to talk a little bit about nullability and our approach in Java because one of the major differences between these two languages is clearly the approach to what is and isn't allowed and is not a super type of object or not. So let's talk about this a little in the Java sense.
What was our approach to nullability there?
Jocelyn: Yeah, so as you probably know, Java does not require nullability annotations or any sort of typing around nullability in the code itself. We had a team create something called NullSafe, which is a static analysis tool that annotates our Java code with either nullable or non null. And you're allowed to mark the files as fully annotated at the class level.
So we have, previously to us starting this project, we had that team go through and make about 80 to 90 percent of our code base null safe with that. Of course that 80 to 90 percent was spread out. Randomly across the code base. So you would have far less percentage of modules completely null safe within themselves.
But basically our approach and the approach of a lot of other companies, organizations is to try and annotate that Java code and get it statically as close to correct as possible.
Pascal: Right. I also saw that we actually have something that even looks at the null safety aspects and I guess the violations at runtime. How does that work?
Jocelyn: Yeah, this is a more recent addition to our code base because Kotlin requires you to check the runtime nullability of code. It's much more strict than even our static analysis linters. And so we realized, even in cases where we thought our code was fully annotated, we would convert it to Kotlin and we'd see null pointer exceptions and crashes because we, quite simply, were not correct with our static analysis.
So, one of our teammates created an annotation processor which can go into the code and log at runtime anytime we see nulls. and we weren't expecting them. And then this allows us internally to mark them correctly while they're still in Java and make us more confident in our Java to Kotlin conversion later on.
Pascal: It's actually really cool. I just saw this while I was working on an application that in my lockcat output, I saw that, hey, this method here was not annotated to take an all but received one. Oh yeah, that's odd. I should probably fix that. And I guess this is a really strong signal then later on as you do your conversions. But can you talk a bit more about the differences between Java and Kotlin and how they handle nullability? Because there was at least one specific aspect that I did not anticipate and I'm sure you will point it out now.
Jocelyn: Yes. So Kotlin under the hood adds check not nulls to every single parameter input, return input, and field input if they are not annotated as nullable. And so if you pass a null to a parameter that's not annotated as null in the very first line of that method, it'll throw. a null pointer exception. Which means that even if you're not doing anything dangerous with that parameter, if it's null and you didn't explicitly tell the compiler that it will be null, it'll crash your code.
And in Java, if you pass in a null and you're not doing anything dangerous with it, it'll be like, okay, cool. This isn't dangerous. We'll continue onward. And so when you do go to Kotlin, you have to be extra careful, because regardless of the level of danger of what you're doing, even if you're not using that parameter input at all, it will crash.
Pascal: I've definitely seen this in my conversions, and it happens a lot when you're not careful and, like in this case that I just called out, where you have a method, it's not correctly annotated, the Kotlin conversion does what it is supposed to, just look at all the information it has available to it, probably at this point without the runtime data that we've just discussed, and we'll just leave something as not null, and null comes in, it blows up, and that is something you can't know if you just look at a diff of the conversion, because this is well, this is not within the context of the file conversion, but how it's being called.
Jocelyn: Yeah, the positive aspect is that if it's explicit, and you're going Kotlin to Kotlin, it will crash at build time, and so you won't even be able to build that file. The dangers come when you're going Java to Kotlin,
Pascal: Ooh, that's very interesting. So that actually means the risk is probably gonna decrease over time because we will have more Kotlin code calling into Kotlin.
Jocelyn: Yes, exactly. So that's another reason why we want our entire code base in Kotlin because hopefully we'll see a pretty dramatic decrease of NPEs as we hit that final 5 to 10%.
Pascal: Got it. Okay. So we've talked about Codemods and what we haven't really talked about too much on the podcast here is the CodemodService that we have. Can you talk a bit about how it works and specifically the kind of end user perspective?
If I receive a diff from the CodemodService, what does that mean for me?
Eve: Yeah, so the CodemodService is basically like a Cron job that runs typically once a day and you give it a transformation and some criteria for finding inputs. It takes say 200 of those inputs on the transformation, then puts up a diff, which, of course, is our version of a pull request. And so if you are just, say, a product engineer going about your, your daily life, you're checking your diff queue, you'll see, of course, diffs from your teammates.And then you will also see diffs from CodemodService for code that you are an owner of. Or that the system has somehow decided you seem like a relevant reviewer for this code. And so you would just review this step. It was just side by side, the old Java, the new Kotlin in this case. Or, you know, more frequently it's just like Kotlin to Kotlin or Java to Java. And if you like what you see, you can accept it. Hit the ship button. It ships and that's it. The CodemodService itself will take care of actually shipping it. If you only hit the accept button, it'll take care of rebasing, if it needs to.
In many cases, the detection of crashes is also automated. So even if it crashes, there's a good chance that you may get pinged about it, or just as like an FYI, but really the whole, the whole thing is, it's very, quite seamless from start to finish at this point. There have been a lot of improvements to CodemodService over the past couple of years.
Pascal: And I guess one of the core aspects is that it really just looks like a diff that anyone else would put up. So it has all the same guarantees, it runs all the tests. You can reject it if you don't like the result, or you can accept it as you've said, and it will land. But this kind of uniformity of it really helps. It's not some sort of automated job that just silently in the background changes everything. And then if it crashes, you have no way of actually figuring out who changed it. But it just behaves like any other default.
Can you tell us a bit about the architecture of your specific Codemods and the different stages that it runs through?
Eve: Yeah sure, so the, I guess the core of it is still the converter that we mentioned before. Which we all refer to as J2K. That's the same thing as what you're triggering when you click that button in Android Studio or IntelliJ. Um, that, that lies at the core of what we call the Kotlinator. Not very creative, but it gets the job done.
So, the Kotlinator is basically several layers wrapped around a slight fork of J2K. And it is intended to be headless. Of course, because CodemodService is running these transformations on some remote server somewhere. So, there are, say, like, six phases at this point, um, though it's constantly growing and the layers get thicker and thicker as we go.
So, initially, we will do a deep build, which is to say, like, we materialize all of the generated code, make sure basically everything that we need to reference, in order to do symbol resolution is present. That's the first phase. Then we do our pre processing, which is AST, Abstract Syntax Tree, uh, based transformations Java to Java.
A lot of this is geared towards our internal frameworks, like our custom dependency injection. A lot of it is also geared towards nullability. We add nullable, the nullable annotation to a lot of things, because we like to err on the side of more nullable, so as to avoid crashes. Then the third step is this headless J2K that we mentioned.
Of course, we worked with JetBrains this past year to make some improvements there. So we were actually just pulled the source code for the newest master batch of IntelliJ, basically built that, and we, we use that, that binary directly.
Of course, that puts out Kotlin code. We do some additional rounds of like, you know, when you're looking at your IDE and it's like, are you sure, do you want to extract this, if to a when, like a bunch of things like that, we do those transformations and we do post processing, which is very similar to our pre processing. Also AST based Kotlin to Kotlin transformations, geared towards correctness, stylistic changes, a bunch of things basically, that we have to do to make these conversions work because, our code base is so large, that just can't be handled by J2K directly. Like changing references to getters that have since been turned into properties.
Then we have our kind of final step ish, which is, linters, and then we finally attempt to build and if the build fails, we have an additional step which tries to act on those errors. So it'll actually parse the error locks and then make additional changes also AST based, to try and fix this. Like perhaps inserting a double bang, that is missing. Or updating a getter that we, uh, missed before. And we do this not just for the file that has been converted, but also its dependence.
Pascal: Well, there is a lot going on. I'm just going to pull on a few strings here and see where it leads us. I'm curious about the headless tool. You talked about this right at the beginning. But most of the time that I've interacted with any IntelliJ code, for instance, by working on plugins, I've noticed how incredibly monolithic this entire thing is. It's usually like one idea jar where, which contains literally anything that you touch. What was it like extracting this J2K processor into a headless tool?
Eve: It was quite the struggle. We knew it would be difficult. And really, it was a trade off because we could have, I guess, tried to create our own converter that was, you know, headless right off the bat, but we thought there are a lot of years of wisdom baked into J2K. So if we can use it, we should. Because reinventing the wheel here probably doesn't make sense.
And after talking to some JetBrains folks, especially Ilya Kirilov, he suggested that we might try and run a headless J2K, kind of like a headless inspection. Because there was this kind of not that frequently used, sort of backdoor, or at least to me, it seemed a backdoor though. I suppose it's quite standard for other people. To allow you to launch the IDE headlessly and just call specific actions. But it is tricky because, you know, we have a lot of customization built on top of IntelliJ and Android Studio to help with handling our ginormous code base and all of our custom tools and frameworks, so it was tricky to get this to actually not just like open and run, but to be able to resolve all the symbols that it needed to in order to produce a high quality conversion.
Pascal: That makes sense. I'm pretty sure they used their internal model, which I don't remember the exact architecture, but they have this PSI, which is kind of their own kind of way. of representing a code graph and all the different edges that exist in there. And for them it obviously makes sense to rely on their existing symbol resolution and dependency graph and all of this doing the conversions.
I'm just trying to kind of make a case for them because otherwise it might seem like why would you put all of this into the same code base? This is clearly something that should work as a standalone tool, but I think they're fairly good reasons for them to actually make it performant. But that's really cool that you had a chance of working with JetBrains on this.
Can I maybe poke a little bit more about the, uh, on the custom transformation that you added as additional steps?
Eve: Yeah, so well, like you mentioned, the PSI libraries that JetBrains created, those are, you know, used for AST, parsing and transformations. And that's what we rely on largely as well in our pre processing and post processing phases. They're, each phase consists of, I wanna say, maybe 50 ish steps in pre processing and probably over a hundred at this point in post-processing. Almost all of which are leveraging these PSI libraries, uh, to do these transformations.
Pascal: Is there a way for people to actually look at some of those transformations?
Eve: Indeed, we have open sourced a few of them. We go back and forth on perhaps open sourcing more at some point. But yeah, you can see them on our, uh, GitHub under Facebook samples.
Pascal: That's really cool. And I guess, like, open sourcing all of them has probably diminishing returns at some point, because if you have a transformation that is very specific to our internal dependency injection mode, then it's probably not particularly useful, apart from it maybe being a sample.
Jocelyn: Yeah, we have tried to open source everything or like most of the things that are very universally applicable and we'd also love more steps to be created by other people or by us to open source. Um, but like you've mentioned, like you mentioned, a lot of our steps are just simply because we have so much custom tooling here at Meta that we need to have special steps for that.
Pascal: Right. Are there any specific transformation that we've added for nullability?
Jocelyn: Yeah, we have a ton of transformation steps, especially in the pre processing before we run the JetBrains J2K for nullability. And a lot of those steps have actually turned into standalone Codemods. And so we try and run these steps across the whole code base to make sure that we are continually updating our Java code that still exists.
But then we become even more aggressive right before actually running the J2K. Adding Nullables, checking with interfaces, etc. to make sure it's in the best state possible before we convert.
Pascal: Got it. Are those mostly static analysis steps, or does it also rely on the runtime information that we talked about earlier?
Jocelyn: At actual Kotlination time, we are just relying on static analysis type steps and inferences. Our runtime data, we are leveraging whenever we collect it, we are using Codemods to read that data directly at the time we collect it and make changes to the code base. We're not waiting for actual Kotlinator time.
We're trying to make those changes ASAP.
Pascal: Makes sense, because even if you don't have Kotlin, you still benefit from having the right annotations on your methods.
Jocelyn: Right
Pascal: To know how to use them right.
Jocelyn: Yeah, right. And we've gotten a lot of people being like, Hey, that shouldn't be null. And then they have to go back through their code and change it so that it can't be null. And so it's nice to have people's input to get the nulls pushed back a little bit to where they should be ending.
Pascal: I've had that before, where I suddenly opened the code base that I was working on and saw some new annotations like, that doesn't seem right. It was definitely the right annotation because it was used this way, but it was never designed to have that particular interface. And then you just need to go through all the core sites and update them to do what you originally intended to do. And that's actually a really helpful exercise.
So, you also mentioned Code Linter. So, how do they come into play for these transformations?
Eve: So we run all of our dozens, hundreds of linters on the resulting Kotlin code. We leverage the autofixes too, so this is partly an exercise in just making the code higher quality. But we also rely on linters to catch potential bugs, even though the Kotlinator and J2K are pretty thoroughly tested at this point. There are still gotchas that can arise, especially when humans get involved and make additional changes.
I think one good example would be confusion about like the get syntax, where you, instead of using a getter, sometimes people get a little bit confused and they just do initialization instead. So we can say, actually, this is a dynamic value that you're setting it to. Are you sure you want this to be initialization and not a getter? Something like when we're renaming properties so that instead of mfoo, it's foo. Occasionally, someone will accidentally introduce self assignment, where they really did not mean to do that, or they meant to reference, say, the outer classes foo, so we can warn that and prevent a lot of a lot of foot guns.
Pascal: Got it. And I guess in the end, there's still a human actually reviewing the diff. So if you have linters that you can't act upon as part of a transformation step, somebody will still review it and can then decide whether or not this is actually the correct behavior.
Okay, so I think the last question I have about the steps is the build failures that you mentioned. So you mentioned that like a double bang could be introduced somewhere to make this go away, but that sounds fascinating to me. Because I kind of wonder if I could just apply this to my own code sometimes that doesn't compile. But like what, what different mechanisms do you have in there to pick up on build errors and fix them automatically?
Jocelyn: So we run the build, obviously, as the final step to check if it's correct. And then when it's not, we have a whole list of build errors coming out of there. And so what we actually realized is that a lot of these build errors are fairly common, and have common fix patterns. For example, adding that double bang.
And so what we do is we parse the build error. And if it's one of these common patterns, or patterns that we know we can fix, we input it into our CodemodService, type AST parsing, and we try and make those changes. Based on that, rerun the build, repeat cycle, quite a few times, potentially, until it actually finally builds.
And that is a step that can be standalone, and you could add to any of your own Codemods, or code, if you wanted to check it, if it's not building.
Pascal: I'm sure there will be cases where no matter how many times you run this, it still doesn't build. What do you do in that case?
Jocelyn: Yeah, at that point, if we're just running this through CodemodService, the CodemodService will know not to produce a diff. It tests these builds, it will crash, and it will disappear off into the ether. And next time we run through our inputs, we will try it again.
If we do have the possibility for human beings to run these Kotlinator on their own. And in those cases, we will still produce a diff with the crashes and allow the human being to do their best to fix the code on their own.
Pascal: Yeah, that makes sense. And I guess a bunch of these errors might again just kind of disappear over time, at least if they are in some way related to Java Kotlin interop, because you will have more Kotlin in the end calling into it. So hopefully they take care of themselves.
So in case there's anything that we haven't discussed yet, were there any massive challenges in this that you didn't expect as you got started in this whole project?
Eve: Well, I'll say, I mean, the knowability was certainly unexpected. I think it's worth calling out that we did not, we didn't look at the bytecode originally. We just kind of took for granted that if something wasn't crashing in Java and was, you know, null safe, then if we converted it to Kotlin it would be fine.
Then we broke a lot of things, and learned that that was not the case. And I mean there are a whole host of stories that I, like, broke marketplace search at one point by doing this. This was also over 4th of July weekend. So it didn't get fixed for a while. Anyway, that's a whole other story.
There, but like, one, one thing that kind of came out of that, which we sort of knew early on, but which we're now really feeling, now that we're, you know, much more than than halfway in was, like, disagreements between implementers and interfaces. It doesn't seem like that would be a huge problem. But our null safe checker, and I imagine many others, is not super harsh with people.
If you, say, have slightly different annotations on different implementers of the same interface, but the Kotlin compiler is much stricter. And so, at the beginning, we were too aggressive with not making things null. Then we were very aggressive with making things nullable. Now we have this kind of hodgepodge of different annotations.
So for, you know, we have many interfaces that are used very widely. They might have 100 implementers, and now 50 of them say one thing, the other 50 say something else for the nullability of a return type of a commonly used method. And now we have to go and figure out which one it really is and resolve that issue. Which is a problem we created for ourselves, at least to some extent. And now we are reaping the, uh, fruits of our earlier labors.
Jocelyn: Yeah, I just got through with the help of some of our contractors, but a giant conversion work of an interface that was used like over 400 times across the code base. We had to change pretty much every single file multiple times for multiple methods. And then right when we finished that, most of them went through conversion completely cleanly.
So had we just done that upfront, they would have been converted a year ago.
Pascal: I think this is a good answer to somebody asking the hypothetical question of like, what do you do all day? You just, you know, set up a bunch of robots that do all the work. What's left for you to do? But it turns out, still quite a lot because you have all these edge cases, right, where you actually still need to get involved and try and figure out what is the right outcome of this. That's not entirely obvious if you just look at one diff individually.
Eve: I would also add type parameters. We've been a little cowboy ish with our use of type parameters. And, uh, sometimes they're not used where they should be, or they are used where they shouldn't be, or they're used in inconsistent ways. And, uh, we are definitely feeling the pain on that because Java lets you have raw pointers, Kotlin does not, so we are just flat out missing type information in places where we should have it.
That is its own struggle. Often related to nullability, because nullability on type arguments was not that strictly enforced as well. We have to kind of, we're basically like taking on all this tech debt, tech debt that has accrued over years of people just kind of being left to do whatever they want a little bit. And now in order to get this into Kotlin and kind of resolve this ambiguity once and for all. Someone has to resolve it, and often that is us.
Pascal: I mean, a lot of it might come down to engineers just kind of writing code as quickly as possible, not really thinking about the future consequences of a potential Codemod coming up trying to convert that code. But I would also say a bit of the blame probably lies on Google, who've not always created the best interfaces in the Android SDK to work with.
I'm thinking like view tags and other beautiful constructs. They come up with, where you can just shove any old object in. And now suddenly in 2025, you need to figure out what type it's supposed to be.
Eve: It's one of our favorites,
Pascal: Oh.
Eve: Blind View.
Pascal: Great to hear. Okay. So as we are getting close to time, can you maybe give us a quick overview of the state of the monorepository in terms of Kotlin conversions today, and what's next for you this year?
Eve: So I'll say we are more than 70 percent of the way to our, um, goal of being, well, being nearly 100%. Uh, obviously, Threads is done, because they wrote the app in Kotlin to start with. Um, Instagram is probably the farthest along at this point. Of course, also, we did all the easy parts. So, if it were already doable, we would have already done it.
So, we are still churning out fixes, improvements to the Kotlinator, so that it can recognize and handle more patterns, both just based on static analysis, based on runtime data, based on build errors. But we're also trying to really finish things off in a handful of apps so we can finally get that benefit that Jocelyn mentioned earlier of actually seeing a significant reduction in PEs once, once and for all.
Um, so we'd really like to see that in some of the smaller apps this year.
Pascal: Fantastic! Jocelyn, do you have anything to add? Anything that's on your mind that you want to focus on this year?
Jocelyn: Yeah for me, as you probably have figured out by now, my focus is nullability, so I always like to see the percentage of our code that's completely null safe increasing. I like to see it when that code then gets converted to Kotlin. So, I mean, my goal is pretty much the same as Eve's, closing that last percentage, creating more steps to our Codemods and things to make that happen.
Pascal: I'm definitely with you on this one. I do remember that it was once called the Billion Dollar Mistake. Not specifically extrapolating just from this company, but just kind of seeing how prevalent it still is if you're clicking around in literally any Android app, that it will just crash for this particular reason.
I think they might want to revisit that particular number because It might be undercounting a little at this point, but well, I want to thank you both for coming onto the Meta Tech podcast to talk about the conversion journey to a 100% Kotlin or close to it and making us all more productive in the company.
Thank you so much.
Jocelyn: Yeah, thank you for having us.
Eve: Thank you.
Pascal: And that was my interview with Eve and Jocelyn. If you want to hear more stories of this kind, you may enjoy the Mobile @Scale conference which happens virtually every year and it's actually how I learned about this amazing effort. You have speakers from Meta and other companies that perhaps unsurprisingly operate at scale. Eve gave a great talk about this very topic. So, if you're more of a visual learner or just like to see some code samples, then check out the link to the video in the show notes. If you want to hear more stories about code health at scale or really just want me to stop, then send me a message. The podcast is @MetaTechPod on Threads and Instagram. And that's it for another episode of the Meta Tech Podcast. Until next time, stay null safe and toodle-loo.
Pascal: You were also saying that you didn't expect the whole kind of nullability aspect. I'm glad it wasn't just me because I saw all these SEVs and people saying like ‘oh yeah Kotlin conversion we have another NPE SEV’ and it's like I didn't quite understand at the beginning. it's like I thought Kotlin is supposed to be more null safe not less. Why are we having all these these crashes now in production?
Eve: Yeah I guess I do like or one thing which I've kind of struggled to articulate uh which I guess we didn't exactly say here is that you you don't get this improvement in null safety for free. Someone has to pay that price upfront. And so it's basically us with our little Kotlinator and all of our little null safety bots. We are the ones paying it.
And hopefully because we're doing it in a systematic way and we have a lot of um practice at doing this now and a lot of data. We can do it in a way which makes sense and is better than just like a thousand engineers over time trying to do this. Um somewhat more hazardly and organically.
Pascal: Yeah, that's actually really well put. I still have my backup recording running, so I might just add this to the end of the episode.
RELATED JOBS
Show me related jobs.
See similar job postings that fit your skills and career goals.
See all jobs