Duncan McGregor and Nat Pryce, co-authors of Java to Kotlin: Refactoring Guidebook, speak with host Giovanni Asproni about their hands-on experiences migrating Java codebases. The episode starts by highlighting Kotlin’s seamless interoperability with Java, allowing teams to incrementally adopt Kotlin without disrupting existing Java code. Duncan and Nat then describe some of the benefits of using Kotlin — including stronger type safety, non-nullable types, and better support for immutability — and some of the gotchas when refactoring from Java to Kotlin due to the different idioms supported by the two languages. Finally, they discuss the importance of testing and tooling, and the evolving role of AI-assisted tools in complex and large-scale refactorings — in the context of work done by teams, as opposed to individuals.
This episode is sponsored by Monday Dev
Show Notes
Related Episodes
- SE Radio 666: Eran Yahav on the Tabnine AI Coding Assistant
- SE Radio 656: Ivett Ördög on Rewrite versus Refactor
- SE Radio 628: Hans Dockter on Developer Productivity
- SE Radio 625: Jonathan Schneider on Automated Refactoring with OpenRewrite
- SE Radio 615: Kent Beck on “Tidy First?”
- SE Radio 465: Kevlin Henney and Trisha Gee on 97 Things Every Java Programmer Should Know
- SE Radio 418: Vladimir Khorikov on Functional Programming in Enterprise Applications
- SE Radio 326: Dmitry Jemerov and Svetlana Isakova on the Kotlin Programming Language
- SE Radio 266: Charles Nutter on the JVM as a Language Platform
- SE Radio 108: Simon Peyton Jones on Functional Programming and Haskell
Other Resources
- Java to Kotlin Book Website
- Pairing with Duncan Youtube Channel
- Martin Fowler’s Refactoring Website
- C2 Wiki: Grain of a Programming Language
- Kotlin Language Website
- Java Language Website
- Jetbrains IntelliJ IDEA Community Edition Java / Kotlin IDE download page
Transcript
Transcript brought to you by IEEE Software magazine.
This transcript was automatically generated. To suggest improvements in the text, please contact [email protected] and include the episode number and URL.
Giovanni Asproni 00:00:18 Welcome to Software Engineering Radio. I’m your host Giovanni Asproni and today I will be discussing Refactoring from Java to Kotlin with Duncan McGregor and Nat Pryce. Duncan has been a professional software developer for over 30 years. He was an early adopter of object-oriented programming, extreme programming, and more recently functional programming in Kotlin. He co-authors Java to Kotlina, a Refactoring Guidebook , and publishes a weekly YouTube video on Kotlin. Nat has been developing software for many years in various languages, platforms, application domains and industries, and he’s a co-author of Java to Kotlin with Duncan and also co-author of Growing Object-Oriented Software Guided by Tests with Steve Freeman. Duncan and Nat, welcome to Software Engineering Radio. Is there anything I missed that you’d like to add?
Nat Pryce 00:01:05 No, I think that was very good.
Duncan McGregor 00:01:07 Hello, lovely to meet you.
Giovanni Asproni 00:01:09 And before we start the questions, there are a few episodes related to this one. There is Episode 666, Eran Yahav on TabNine, the AI Coding Assistant, Episode 656, Ivett Ordog on Rewrite versus Refactor and Episode 326 Dmitry Jeremov and Svetlana Isakova on the Kotlin programming language. Okay, then let’s start with the questions. Now what considerations should the teams make to decide if they want to refactor from Java to Kotlin? I mean from a technical perspective.
Duncan McGregor 00:01:41 Well, I think technically just do it. I mean there’s nothing, if you’re certainly in the Java space, there is nothing stopping you using Kotlin, if you are developing Java. It is as lightweight as Java is, which is say not terribly lightweight, but it doesn’t add any weight to a Java program really. And so if you like the look of the thing, go ahead, try it out.
Nat Pryce 00:02:05 One of the benefits I think compared to a lot of other alternative languages on the JVM is that JetBrains who wrote it really put a lot of effort into how you can gradually adopt it into an existing Java code base. So you can add it to the build and that doesn’t change, your Java will continue to compile just fine. You can then write little bits of Kotlin and you have almost seamless interop between Kotlin and Java. So there is no interop layer that you have to write. The sort of code looks exactly the same on either side of the language barrier. Kotlin has more type safety around things like nullability and Java doesn’t. So in that interop layer, the compiler will put in null check so that you get, if there is a null going from Java to some Kotlin code where you said this will never be null, it gets caught as it passes through the interop layer.
Nat Pryce 00:03:06 So you can gradually increase the type safety of your code base bit by bit and slowly spread that outwards increasing the area of your code that is now Kotlin until you have no Java left at all. If that’s as far as you want to go in small steps, which is why we called the book, Refactoring to Kotlin. It’s not about a big bang conversion. It’s about gradually and safely transforming the code, improving it by introducing Kotlin into existing code. Kotlin is terser than Java, I think. And if you like the veracity of Java, you may not like Kotlin because of that. You can leave types out of Kotlin, you can put them in, but by default it tends to be less cluttered should we say, it has type inference. So you don’t need to declare types as much as you do in Java, but they’re still there.
Nat Pryce 00:03:55 They’re still being type checked, the type of checker is, it works better than that of Java. It has better ergonomics. And so yes, it’s not like the types aren’t there. In fact, you lean much more on types and you can do in Kotlin because the types are so much more convenient and get out of your way. So you can do, you can apply much more type level modeling and you use reflection much less to the degree that, that the Kotlin reflection library is an optional library. It’s not just part of the standard library. You have to choose to bring it in. And so the default sort of style of Kotlin is more compositional, more statically bound, more statically type checked. And you can use that to your advantage. That’s the way that you like to design and it takes a while to adapt to that if you’re a Java team, because it is different than the typical way that enterprise Java is written.
Giovanni Asproni 00:04:52 And you’ve done, just to clarify with our audience, you’ve done this refactoring from Java to Kotlin in reality as well. It’s not simply talking about something that would be nice or?
Nat Pryce 00:05:05 No, no. We’ve done it a few times and so have other teams we work with. So we adopted Kotlin when we were both working in a scientific publisher. We struggled with doing functional programming in Java. We looked around for alternative languages and Kotlin seemed to be the most commercially viable. This was before Google adopted it for Android. So that happened about the time that we took our system to production, which was we were writing experimental systems and then one of them needed to go to production and bing, Google adopted Kotlin as the official language for Android. And we were like, whew, that was lucky. We didn’t have to rewrite the whole thing in Java. And so we just upped the version number in our build file to 1.0 and into production it went. But then we used it, I think then I was on a different project.
Nat Pryce 00:05:53 We had a strong steer to go Java, but eventually the team just put Kotlin into the build and started changing some of the internal domain model classes into Kotlin. On that project we had, a strong inclination towards functional style, immutable domain models and things. So we were able to remove hundreds of lines of code per domain model class by adopting Kotlin because it has natural support in the language for immutable data classes, easy transformations and things like that. Not as powerful as, something like Scala, right? But the organization didn’t like Scala, been burned by it in a couple of projects. And so this was like a nice happy medium between Java. It’s very approachable for Java. There’s no sort of new ways of thinking about design really. You can use immutable data and transformation and data-oriented programming, but you’re not writing monad transformer stacks or anything like that. And so it’s very approachable for Java programmers and provide that smooth path where you can gradually introduce it into a code base and introduce stronger type safety and push that further and further out through the code bases as you work.
Giovanni Asproni 00:07:06 Okay. And so now that you mentioned that, can you give us a brief overview of the similarities and differences with Java? But the, you already mentioned some of them. I think maybe something else.
Duncan McGregor 00:07:19 I think there’s the language has a different feel. It feels tighter, it feels better designed, it feels more modern in terms of the actual capabilities. I don’t think there’s anything that you can do in Kotlin that you couldn’t do in Java. And Java, in fact, I think is caught up really quite well. Java had record types and things that Kotlin introduced and simply weren’t available in Java at the time. I think Kotlin programming is probably a little more functional than Java developers. Kotlin’s type system, as Nat said, I think is better designed, especially with respect to generics. So you do a lot less declaring that this is question mark super T and whatever the other one is. And another couple of features. One, Kotlin has the idea of inline functions where you can ask the compiler for the type that this is actually being invoked with — rarified types, yes, rarified types — and those in particular, I think they’re sort of poor man’s macros in some sort of way, but they lead to an awful lot less reflection in Kotlin and effectively allow you to write your own control structures and things like that.
Duncan McGregor 00:08:26 So you can write functions that take lambdas that you can return out of and those sort of things.
Nat Pryce 00:08:32 I think that’s the one difference really between Java, right? That’s the one thing that you can do in Kotlin that you cannot do in Java is the inline functions because it allows you to write your own control structures.
Duncan McGregor 00:08:40 That also plays quite nicely into generics because inline functions don’t end up boxing primitives. And so that probably one other thing that Kotlin can do that Java canít.
Nat Pryce 00:08:53 Kotlin doesn’t distinguish primitive and reference types like Java does hit automatically and at the interop layer will automatically. Now if you, you have a Kotlin INT, it’ll pass it in as a Java in or a Java integer as necessary and does its best to hide that from you entirely. It has, non- nullable types in the type system. So every type you can market is by default non-nullable, and you have to explicitly say whether you want something to be nullable. So that is something that Java type system doesn’t do and has been on the cards for a long time. Can’t remember what is Project Bower is it? But it’s going to be a long time before we get that. But it is to the Java developers that were adopting Kotlin and refactoring their code to Kotlin, that was a massive selling point. They could just lean on the compiler to track nullability. And even the annotations that Java has for stating whether something null or non-null, they can’t apply in all the places. You can’t apply them to a generic parameter, for example. So you can’t say I’ve got a non-null list of nullable values. It’s just not something you can express in Java, but it naturally falls out that Kotlin type system.
Giovanni Asproni 00:10:10 Okay. Yeah, that is quite an interesting one because removing nullability in many situations actually removes an entire class of errors and checks as well.
Duncan McGregor 00:10:20 It’s almost like I compare it to the first time I used a language with a garbage collector, there’s a sort of freedom, you get straight away from not managing your own memory and you go, ha, that’s nice. And I’m instantly X percent more productive, where I think maybe with a garbage collector the answer is a hundred. Nullable and non-nullable types is again that sort of freeing thing. So all of a sudden you go, oh, I really wish I’d had this for the last 10 years of my career.
Giovanni Asproni 00:10:47 Yeah, yeah. But also you said, you mentioned before that Kotlin tends to be a bit, well, programmers that work in Kotlin tend to be a bit more functional in style. So maybe explain a little bit to some of our audience. There’s main differences of similarities between functional and object-oriented programming.
Duncan McGregor 00:11:08 I would say, I’m going to start because Natís the computer scientist. So from the non-computer scientist side, it is largely about how we manage state. And so state in particular, changing state is the problem we have with software. Something changes some other thing. Which pieces of our program can see that change? Which piece of our program can’t see that change? Is that a valid state change, those sort of questions. Object 20 programming said we will encapsulate state inside objects and those objects, those objects will manage state change within them. And functional programming says, let’s manage state change by having immutable data and then copying that mutable data, making changes in copies, and then managing the visibility of which version of a particular state we’re looking at. How’s that, Nat?
Nat Pryce 00:12:01 I think that you get more explicit data flow, right? Because you don’t have mutable state causing changes in other parts of the system through aliasing, you end up having to feed that state through function parameters and through expressions. And as a result, what I find is it’s easier to come to a code base, understand what’s going on, understand how one piece of that code base affects another piece of the code base, which can be a real challenge in large frameworks, especially when they’re designed with reflection and AOP and things like that, which make the composition very difficult to immediately understand. So you have explicit composition which can reason about behavior of your application through manipulating the syntax of your source code and not by having to simulate the running of the program in your head in order to understand what effect any change to that source code will have. See what I mean? So that makes, makes it much easier to be, to make changes more rapidly and more competent.
Giovanni Asproni 00:13:12 You mentioned also there some terminology around this. When you, when you talk about functional programming, you mention also effects, actions, calculations. Can you give us a bit of definition for what this means?
Duncan McGregor 00:13:25 Actions and calculations is a thing that I do bang on about. And the idea that we can divide our code into two types of things. If it doesn’t matter whether or when we run a piece of code, if the result of calling a function is only ever determined by the values of the parameters we pass into it, then it’s a calculation. And in functional programming terms, it has no side effect. It is referentially transparent, but I like the term calculation. Everything that isn’t a calculation is an action and that means that it matters when we call it. So anything that involves IO is an action because if we read, we may be reading different things at different times. If we write, it matters that we actually write random numbers, time, those sort of things are actions. But it does help a great deal if we divide our programs up into the bits that are actions and the bits that are calculation.
Giovanni Asproni 00:14:22 And a concept that I actually quite like that I find really interesting that you mention in the book is you talk about the grain of a language.
Nat Pryce 00:14:29 Yeah, it’s something that somebody wrote on the C2 wiki way, way back. That’s where got the term from. And the metaphor is like wood, wood has a grain and a carpenter will know that certain things are easier along the grain and certain things are easier across the grain, but the grain of the word, if you’re not aware of it, you’ll be working too hard when you’re trying to do something or damaging the word or damaging your tools. So, languages are the same. The designing of the language has made some deliberate choices to make some things easy and some things deliberately not easy to sort of nudge the people using the language to write code in a certain towards a certain style of design. And then around that people build libraries and tools and they bed in that grain and affect it.
Nat Pryce 00:15:24 And there’s a feedback loop that the more libraries you get that work a certain way, the more code you write that matches that and eventually there’s a certain way of writing code, right? So Java, most people write Java now using the Spring framework. That’s lots of annotation sorts of reflection. Java doesn’t have to be written that way, but it is written that way because of the amount of code that has built up over time. And because of Spring was a really good approach to working with the JTBD frameworks back in the early 2000s. Solved a lot of problems in those frameworks. Those frameworks sort of gone now. But we still have Spring and we still have that way of coding. Kotlin has made deliberate decisions to be different. It pushes you towards strongly typed and immutable data. It makes it easy to work with immutable data.
Nat Pryce 00:16:16 You have to choose whether your data is mutable or immutable and it’s as easy to make it immutable as not, because the standard library also comes with a very rich collection of high order functions over collections for transformation, fold zips, maps, all of that stuff is at your fingertips, are much more easy to use than Java Stream. So you reach for it more naturally. The default is to have data that’s not knowable because it’s more effort to make it knowable. So you tend to like be modeling null ability very deliberately and in small places and not all over the place. Things like that.
Duncan McGregor 00:16:55 I think maybe collections are the places it’s easiest to see. First of all, the difference in the grain of the language. Java collections from the outset were mutable and that was acceptable in the nineties. These days immutable collections are more the fashion and Kotlin collections, the default Kotlin collections are immutable. Now they’re actually implemented with the Java mutable collections. And so there’s no difference like implementation and because of that they’re very interoperable. But if you start from a Kotlin collection, you can’t add items to it. And that gets you in the frame of, okay, well if data is immutable, what do I do? Well I have to work with a collection that is a copy of this one plus these other three items or a copy of this one with these items transformed in this sort of way.
Giovanni Asproni 00:17:42 So let’s see. So something like immutability in terms of immutability in collections, for example, goals, well with a grain of Kotlin but in Java is a bit more problematic. But is that an example of the other way around? Like something that is actually easier in Java that goes with a grain of the language in Java, but with Kotlin is a bit more problematic? I don’t know. Doesn’t have to be, but I’m asking if thatís the case.
Nat Pryce 00:18:09 In Kotlin language and very minimal support in the standard library for that amount of reflection. So you can get a class, you can get a function. So if I’ve got a function, I can turn that function into an object that happens to have a function type. I can refer to a property and get back a property object where I can ask for its name, but it’s all strongly typed at this point. I’ve never escaped. I’m not doing reflection as in Java where I just I can ask for a class and say, give me the thing with that name and then I have to use untyped data from that point on to interact with it. Kotlin gives you enough reflection to have things with names or, or wrap them in laziness or whatever, but not enough that you are out of the type safety. So in that respect, full blown, reflection is easier in Java, but I don’t miss. Itís because of the grain of the language, it’s not something you really turn to very often.
Giovanni Asproni 00:19:06 Yeah, and I guess using reflection also makes understandability of the code a bit more difficult in many situations because you can, it’s not simply reading the code. It’s like what is doing behind the scenes that you don’t fully see there. Okay. Let’s now go maybe to a bit more into the mechanics of the refactoring from Java to Kotlin. Now the first question is, in the context of moving from Java to Kotlin, how did do you distinguish between what is a refactoring and the variety?
Nat Pryce 00:19:35 We had quite a discussion about that with our editor when we wrote the book. So we called it a refactoring guidebook. Originally, we wanted to call the book Refactoring to Kotlin because Kotlinís big benefit compared to most other non-Java languages on the JVM is how seamlessly it interoperates with Java, how gradually you can introduce it into the code base and how little work. There is no interop layer really the compiler does all the work for you. If you add Kotlin to your build, you’ve still got a Java program, but you’ve got the option to write some of it in Kotlin, you can go and add a little bit of Kotlin and Java can call it as if it’s Java. Kotlin can call back to the Java. It’s just another class in your compiled program. The compiler works out based on the dependencies between the Kotlin and Java code, how to compile it so that it compiles correctly.
Nat Pryce 00:20:36 It’s not like you can, oh well I must compile my Java and now I can compile my Kotlin, depending on the Java. It just works it out for you. And then one of the greatest features is written by JetBrains ID vendor, is the magic key press to take that Java and turn it into Kotlin, right? Just one key press transforms a file into Kotlin, gives you a few options like do you want me to make it so that I change all the Java to sort of match your Kotlin or do you want to annotate your Kotlin so that you don’t have a very big impact on your Java code? And that kind of thing is really great. They really thought about, as you are transforming a large code base, you’re not going to rewrite it. You can’t rewrite it. No one’s going to say you can’t do any more business development while you are converting into Kotlin and you can introduce it and other people are working in this code base.
Nat Pryce 00:21:24 It might be calling into the code that you are currently turning into Kotlin with a single keeper as you can do that and not impact their work. And then as that code that’s depending on what you’ve changed becomes Kotlin, you can remove these compatibility annotations and then everything becomes much more natively Kotlin. So I’ve never really used, done a lot of multi-language development and the introp layer has always been painful, right? We’ve written J and I, between Java and C or between Python and C or TCL and C, I show my age. But yeah, it’s always been a painful experience. And with Kotlin and Java is incredibly easy.
Duncan McGregor 00:22:02 I think the grain here is maybe pertinent as well because the introp is completely seamless. You literally can just like take one file from the middle of your Java program, convert it to Kotlin and it’ll continue to work. Everything will be fine, but the grain of Java and Kotlin are different. And so if you are a Java programmer who thinks a little bit more functionally, then your Java code will naturally convert to Kotlin, more idiomatic Kotlin. So I think the danger is not so much that we end up with that the introp is hard, but that the functional thinking, the change across the boundary is difficult. So that we end up with Java that’s busy new taking collections and Kotlin, that’s assuming those collections won’t being mutated.
Giovanni Asproni 00:22:47 So also, I would imagine that then this step is like okay, initially you can start help by the tool, yeah? To say from Java to Kotlin, but then at some point as you move on, probably there will be some level of redesign of the application as well. Because when you go with a grain, if you change from one language to the other, you go to the, with a grain of the new language, this may direct you towards a different shape of your system to some levels. Am I correct?
Nat Pryce 00:23:14 As that little bubble of Kotlin sort of expands outwards and yeah, in my experience it was null safety that really drove that. So people using the optional class in Java because they didn’t want to use null pointers, but that has awkwardness of its own. And so once the, the area of Kotlin had spread far enough and we could replace optionals inside it with just nullable types, then inside that code we could start thinking more about how we make better use of the Kotlin standard libraries. Again, tooling is fantastic and the IDE will give you little hints saying, oh you could use this standard library functions here instead of doing it like this. If you’ve got a four loopers to do a map, it can say this would be better done with the map function. Right? Or if you do, a map and a filter, it’d be like, oh, well I can do that in one, I can collapse these parts of your pipeline down into one.
Nat Pryce 00:24:08 So when you’re working in Kotlin, you learn a lot just by seeing these little brown highlights in the IDE and seeing how to improve the style to match the grain of Kotlin as you go. And it’s also useful as they evolve the language and standard library you come to, open up some of your old Kotlin code and the language has moved on and it’ll be, oh yeah, there’s new little brown things telling me that I can now use new standard library features or whatever. So yeah, that aspect of tooling is great for getting people to think in a more Kotlin library.
Giovanni Asproni 00:24:39 When we’re talking about tooling here, we are talking specifically about the JetBrains?
Nat Pryce 00:24:44 Yes. Yeah, yeah. So, JetBrains is an IDE vendor. This is a language that was written with IDEs in mind and the tooling was very good for brand new language.
Giovanni Asproni 00:24:56 Yeah. Also, as we said, I think the JetBrains the, IntelliJ is actually there is the free version that everybody can use.
Nat Pryce 00:25:04 Yes, yes.
Giovanni Asproni 00:25:05 If use for commercial purposes the company has to pay for. But, everybody can use the free version, I guess for their own projects.
Nat Pryce 00:25:12 It’s open source, I think. I think that even companies can use the free version, commercial software development. But the enterprise additional ultimate edition, whatever they call it, has a lot of enterprise software support. So it supports databases, Spring and all of these other sort of enterprise.
Giovanni Asproni 00:25:29 Yeah, I mean doesn’t add much to the support of Kotlin itself.
Nat Pryce 00:25:33 It doesn’t add anything to the support of Kotlin. All the support for Kotlin is in the free version.
Giovanni Asproni 00:25:38 Yeah, so basically if people want to use Kotlin, they don’t have to buy anything they just download and I was asking this because I know, well, I was asking this because there are many developers, for example, that seem to use nowadays Visual Studio Code, and I don’t think Visual Studio Code does anywhere, the support for Kotlin that IntelliJ has.
Nat Pryce 00:26:01 No, I mean, Visual Studio Code is much less capable. It’s a text editor with some additional support for navigation and a little bit of support for refactoring, but it doesn’t have the whole integrated development environment sort of feeling that, let’s say JetBrains IntelliJ does.
Giovanni Asproni 00:26:20 Yeah, yeah. And talking about tools now, the tools you just mentioned, support from JetBrains, those are, that support was available well before Copilot or any AI coding editor was there?
Duncan McGregor 00:26:35 So when we started, AI tooling wasn’t a thing, right? We sort of describe it as traditional refactoring tools, although even now people, a lot of people don’t really use them, but there is a language ware editor. The editing actions are things like extract method, move a class, move that instance method from that class onto that class, those kind of things. What JetBrains brings is very good at, but they do have AI agents and we do use them and they have AI auto complete built in which doesn’t have to talk to an external server. It’s just completely local. So that can do really pretty good auto complete, actually, I’ve been very surprised by how capable even the local auto complete in JetBrains is. For example, I was writing some Kotlin code to generate some Lua code and as I was writing the lure(?) code inside the Kotlin code to generate it auto completed the Lua but was also auto completing the Kotlin code to generate the auto completed Lua for me all in one go. Wow. Right. These things have come on to almost like incredibly capable now and that’s without having to call out to a massive data center somewhere.
Giovanni Asproni 00:27:50 So these are kind of local models.
Duncan McGregor 00:27:52 Yes, yes.
Duncan McGregor 00:27:55 So it should be said that the conversion from Java to Kotlin, the one built into the ID, so you can just basically say, I’ve got this Java file, please replace it with a Kotlin file that predates AI, predates LMS and is just a JetBrains written plain old, plain old code, basically. I think it’s certainly possible to use AI to convert larger swats of a code base. And I think, and they certainly seem to be very good at that, but the particular tools built into IntelliJ are just, I guess just templates and templates and strings the old-fashioned way.
Nat Pryce 00:28:28 And graph transformation the IDs got a graph model or know object model of the language underneath it, and then the refractory tools work on that.
Giovanni Asproni 00:28:37 Are there any things that maybe, if you have an example, would be good that maybe when you did the first Java to Kotlin move, if there was, if there had been AI and AI coding assistant at the time available would’ve been much simpler to do?
Duncan McGregor 00:28:55 I think we might, I think we might attempt these days to throw more of a code base at once at an AI tool and see.
Nat Pryce 00:29:02 I donít know, see I think that maybe the success was because we didn’t have it, we had to go in small steps. If we’d have just done, hey LLM convert that code base to Kotlin for me and I’ll go for a cup of tea and come back and I’ve now got a Kotlin code base A we would’ve had a lot of reviewing to do and reading code is harder than writing code and B, we were smashed over all the work of all the other people in the team who were working away, right? So what we would’ve committed that and just wiped out forced small, small, small steps, we were able to gradually introduce it into a code base additional type safety, no safety and confidence and clarity that came with the code when that we actually saw that while we’re doing the conversion and continuing to write business features and evolve the code, our delivery accelerated.
Nat Pryce 00:30:02 So we definitely saw that we were delivering faster as we converted more and more things into Kotlin. Now that was because I’d say we were writing in a largely functional style and that takes a lot of effort in Java. So adopting a language which is nowhere that is naturally the grain of the language means that it’s much easier to do. So we were getting the benefits of it. I can’t say whether if you have a very sort of object oriented JARA program, whether you would see the same benefits straight away, but I think null safety certainly gives you a lot of benefits and you probably would worth doing it just for that really. So that to me is the worry of LLMs. You, you can do massive coaching as a solo coder. They make me very productive, in a team where teams work well together when they make small safe changes and continually integrate them and the smallest steps as possible, the better we can integrate together where an LLM can make huge changes, sweeping changes across a code base with very little effort from the point of view of the programmer. How that will change the way that we work in teams I think is still a no.
Giovanni Asproni 00:31:49 Are there any things at which the AI tools actually are good at in terms of in addition to everything else we do actually improve the process of what we do make it smoother, in your experience?
Nat Pryce 00:32:02 They are good at big refactoring, which are very tedious and difficult to do with the refractory tools. Kotlin has been around a while, but if refractory tools are still behind those of Java and even so there are some things that will never be able to be done by a refactory tool. For example, I was converting Spring’s annotation based transaction management serializable database transactions, which involves having to use the transactional annotation and a retry annotation with some code like logic in a string in the annotation about how you’re meant to do retries, and the SQL layer tells you that you’ve got a serializability failure, right? It’s not something that comes out of the box in Spring. Requires a little bit of effort to do and I wanted to turn that into programmatic transaction management using transaction template retry template, have them composed properly, have the rules put in properly with the right models of retried and everything prompted Juni, which is JetBrainís AI coding assistant and it just did. Just did it right now, but I don’t think you could do that transformation from annotations and AOP reflection to programmatic composition using traditional factoring tool.
Nat Pryce 00:33:21 It would require manual rewriting and even then it’d be very slow process of like where you do baby steps and graduate transformations in some bits and then we’d have to manually rewrite others. So thereís focus, it wouldn’t have had a massive impact across the code base with other people and it was a solo project and it was very impressive about how well it did it.
Giovanni Asproni 00:33:41 Okay that’s interesting. So basically there is definitely a place for these tools and these actually they can be useful to great advantage.
Nat Pryce 00:33:49 I think so, yeah. And also still refactoring tools are going to be useful because LMS are trained on code they find on the internet. It’s not the way I like code and I think every programmer in the world has the same opinion, right? I mean different from my opinion, but the code on the internet is never good enough, right? So you use an LLM, it generates code and then you have to refactor it so that the next time you use it, it generates code that’s more in your style, more consistent with the code base and over time the LLMs become much more efficient to use but you’ve got to keep giving it that guideline. So those traditional refractory tools, I think will never go away because they’re required to nudge the LLMs in the right direction all the time.
Duncan McGregor 00:34:29 I think you’re wrong personally.
Nat Pryce 00:34:32 What do you think Duncan?
Duncan McGregor 00:34:33 When we say the traditional refactoring tools will never go away? I think they will, the traditional refactoring tools will become part of the arsenal of AI assisted refactorings. So there are still things that we want to be digital, maybe digital’s the word, right? When we rename, if we ask an AI to rename a function, it’s a best effort thing, right? It is just like it’s guessing the next word, still when we ask IntelliJ to rename a function, it is finding all the places that function is being called literally. And it is, you can pretty much guarantee that it will do the right thing. But combining those certain things, the human designed refactorings into bigger sets of refactorings and using those tools as tools, allowing the AI to use them as tools, I think that will really become a thing. Yes, yes, yes.
Duncan McGregor 00:35:22 I’m not sure it’s there yet, but I think that will become a thing. And then we’re looking, I’m interested in how we, how we describe our larger refactoring in a language that allows an LLM to take the language about how we want to transform the software and then translate that into a language of, well I need to then pull this method up, rename it to something and then move it into this other place. And I don’t think we had that language. There are a few books on this sort of advanced refactoring ours amongst them I think. But at the moment AIs are not making use of tools in order to chain a bunch of refactoring together in order to achieve a goal. And in fact, I’m not sure they even know what the goal is, but I think maybe they can get there?
Nat Pryce 00:36:11 Yeah, LLMs work on text, right? And so they work on the outcome, they just change the code and see what happens and then so if I say make this programmatic not annotation based, generate some programmatic stuff, tests will start failing compilers starts failing from those failures in the rest of the code. It kind of autocompletes the next step to do and the next step to do, amazing that this process is so effective, right? But it’s still focusing on outcome and not the steps. It’s not focusing on what is the right way to do this. If I’m a member of a team, well you say that but, I’d say thatÖ
Duncan MGregor 00:36:51 The thing about agents that in fact we have, we’ve gained the system, right? Agents gain the system by saying yes, you can only guess the next word, but what you can do is you can guess the next word in the plan to do a thing and then you can execute that plan and when the plan goes wrong, you can guess the next word in this went wrong with this error message, what should I do? Yeah. And then you can reform the plan and then you can execute that plan again. Yeah. I think that is a really clever gaming of all I’m doing is predicting the next word. And I think we might do the same thing with, with languages, and we might do the same thing with systems as well. It may be that we’re able to tell a system with enough memory or enough context that this isn’t working because you are not taking into account your team membersÖ
Nat Pryce 00:37:33 Right, I think that one effect of LLMs and their ability to refactor in the ways that they can right now is that it, it makes a wider variety of languages practical for professional use or industrial use. So if I have a language that has no refactor tools because it’s Essure, because Lua is a perfect example, there’s no good refactor tools for Lua. I could refactor Lua just by prompting an LLM though, right? That suddenly makes it much more practical for use
Nat Pryce 00:38:08 in day-to-day development. Where currently I just use it for hobby development. My hope is we’ll see a sort of renaissance of languages. It won’t just be Java, it won’t just be Kotlin. We’ll see LLMs allowing people to adopt a wide variety of languages, thatís why Duncan was saying one which are more practical particular parts of the system would now be much better to work with and if they would.
Giovanni Asproni 00:38:31 Okay, now let’s change direction a little bit. How important are tests for refractoring purposes, especially from Java to Kotlin, how important is the presence of tests, let’s say in the regional, in this case Java system. So you know that whatever youíre doing, work.
Duncan McGregor 00:38:49 I mean you’re talking to TDD zealots, right? But I think both of us would probably say that tests are an economic argument. I mean then they’re not the reason that we are writing code, they make writing code more enjoyable for us because we spend less time debugging and I think we enjoy maybe expressing ourselves in tests. We enjoy the little challenges around, we enjoy the little challenges around making the TDD cycle work, driving the system in the direction that we want to drive it with our little tests.
Duncan McGregor 00:39:20 But, if we don’t need tests, we don’t need tests.
Nat Pryce 00:39:22 But for refactoring and it’s all about risk, isn’t it? And like you say, economics, there are certain areas where I know I make mistakes, so I write tests for them. That’s why I cover almost all my code with tests and without tests, how can you do it right? You need to know that you’ve not broken something. So it was one of the things that we were debating when we were starting to adopt Kotlin. Do we adopt it in our tests? Do we convert all our tests to Kotlin? First, it’s low risk, it lets people learn Kotlin on something that isn’t going to go into production. But in the end, we said actually no, we’ll we will keep the test in Java and we’ll convert them much later and we’ll start by converting the domain models because that’s where we’ll get the most value with the least effort from Kotlin. You won’t have to integrate with Java infrastructure APIs, which are not very Kotlin friendly, and we won’t have to interact with things that do a lot of reflection like J unit, whatever. This is where we are in full control of the model. And the model for us happens to be very close to the grain of Kotlin. So we’ll get a big bang for our buck and then as it spreads out, we’ll be able to lean on it and start converting the edges of the system and the tests around those things as well.
Duncan McGregor 00:40:40 With respect to migrating from one language to another, it’s often recommended that you just try writings of tests and new language is like that will let whether you like them. And you can always throw away the tests. I think that’s often really bad advice because the tests are involved to a project and maybe they’re the last things you want to throw away, in some ways they, they do sort of represent your spec.
Giovanni Asproni 00:41:02 Another question is, are there any cultures that developers should be aware of when they go from Java to Kotlin?
Duncan McGregor 00:41:08 Absolutely not. It’s perfect.
Nat Pryce 00:41:12 Interop layer, right? Like the interop layer is the main one. There are a few little places because Java and a lot of Java libraries play fast and loose with null. You have to know what the nullability is when you call into those libraries. And there’s a number of times where even where they’re annotated with like nullability annotations, they’re incorrect and Java doesn’t check that at runtime or, and it only gives the IDE gives warnings at compile time or editing time. But at runtime, nulls flow through mere seems fine, right? One Kotlin as that null passes the introp layer Kotlin will throw a null pointer exception and tell you that you’ve got it wrong. So that’s where testing becomes very important, right? Especially if you’ve got null that’s going out of Java through your Kotlin back into Java again, you are not touching it, you’re just carrying it through. That might be perfectly okay, but if you’ve like taken that value which you think is non null through Kotlin and out, it will be checked for null and it will fail. So you that that’s really the main one. It’s quite rare, right? And as you push your boundary further and further, you are more and more Kotlin and you’re sort of pushing it to through to the edge. And now especially there are much more Kotlin native libraries that you can lean on. That issue becomes less and less of a worry.
Duncan McGregor 00:42:30 I think this idea, this thing I said earlier about default immutable collections is a place that can trip up. So if we get a collection from Java inside our Kotlin code, we see it as an immutable collection. We may actually treat it that way, but Java, if Java’s hung onto a reference to that, it can be busy mutating that reference somewhere else.
Nat Pryce 00:42:51 Worse we pass that what we think is an immutable collection into Java and we’ve created that collection. Java mutates it because it sees it as an array list for example. It adds an element to it. Boom. Right now you are seemingly immutable collection that you created in Kotlin has magically changed underneath you. Luckily that there are very few libraries in Java that do that kind.
Giovanni Asproni 00:43:12 Okay, but this basically anything at the border between the two languages?
Nat Pryce 00:43:16 Yes, yeah, yeah. But it seems seamless. You just have to be aware that it is a seam, and there are different conventions on either side, different grain on either side. And just be aware of what the risks are.
Giovanni Asproni 00:43:26 Yeah. Okay.
Duncan McGregor 00:43:28 It’s neither seamless nor seamy. It’s seamy–like “truthy. ”
Giovanni Asproni 00:43:37 Let’s see from a business consideration. So when you decide that the first time you said to go from Java to Kotlin, and she said this is a good idea to do that, but were there any business considerations here to take into account maybe convincing your product managers or anybody else that this was a good idea?
Duncan McGregor 00:43:56 I think the original project we were allowed to use any JVM language except Kotlin, weren’t we? That was, that was the spec.
Nat Pryce 00:44:03 No, no, I think it was the first time we used it. We were able to use anything we wanted. because it was, they were all proof of concepts. Then the big project we started on there, we were told to use Java and not Scala because history in that organization, but it was so much nicer that people just did it. And once it was afraid to complete every other team pretty much in the, the building picked it up and started using it because they could see the benefit as well. Just not having like random failures. We did have old systems that would have regular null pointer exceptions in production, right? So just, that’s an easy thing to talk about. You can just share the logs.
Giovanni Asproni 00:44:45 Let’s say, if I understand correctly, the fact that the language was so easy to introduce in a Java project allowed you to introduce that in a kind of, in without making too much noise about it. That and then people started to see the benefit of this move, especially because of this knowability and the immutability that gave
Nat Pryce 00:45:07 And the concision yeah.
Giovanni Asproni 00:45:07 And the concision that nobody had anything against it at the end.
Nat Pryce 00:45:11 Yeah, yeah. And the fact that we were already using, we were already writing Java in a very similar style to the grain that Kotlin made it an easy win, right? Because you would replace hundreds of lines of code to do sort of functional transformations in Java with like a single data class declaration of one line.
Giovanni Asproni 00:45:33 You managed to introduce it because it was easy. So the language itself, the interoperability with Java, didn’t add any overhead on the work you are doing.
Nat Pryce 00:45:43 Yeah, I think also the organization itself was quite open to development teams trying new technologies, right? It wasn’t that there was a complicated bureaucratic process to adopt a new programming language and you had to get signed off from a whole bunch of different groups and what have you. Teams were quite autonomous, right? And as long as they could, as engineers argue the case, if anyone ever asked, no one was really going into teams and telling them don’t use this or don’t use that.
Duncan McGregor 00:46:14 I think there’s another important factor here though that that is that nobody, nobody wants to down tools while they learn another programming language. And so deciding that we are going to introduce programming language X, where in order to become proficient enough in programming language X to be productive in this code base, you are going to have to go on this two-week course and we’re going to cycle people through that two week course. But until everyone’s been through that two-week course, we’re not going to introduce this programming language or we’re going to introduce this programming language. Once four people have been on the course, but then the other six people on the team can’t read the code, that’s a problem. In practice I found the Java programmers basically read Kotlin from day one and they can write Kotlin from day two.
Giovanni Asproni t 00:46:58 Okay, so in terms of skills to get started, you don’t really need a lot.
Duncan McGregor 00:47:03 No, very little.
Nat Pryce 00:47:05 I think that, like you were saying earlier about collections, that was the largest mind shift that I saw developers have to make. So I would see developer writing code to do a transformation, but they would have a list and I’d go, well I’m going to write some code to take these ones out of that list and then I’m going to write some code to put these one into the list and now I’ve got the list and I’d be, well that’s a filter followed by an addition, right? You see what I mean? And like, whoa, all right, now the code is like two lines long. So actually the benefits of moving into that direction in terms of how short and readable the code is also are very easy to see.
Giovanni Asproni 00:47:41 The language comes also with the libraries and functions that exactly actually going that direction, it’s I guess at the beginning you write without the knowledge of those particular functions, then you get to know them and your code changes becomes more .
Na Pryce 00:47:54 Yes, yes.
Duncan McGregor 00:47:55 And as Nat says, the idea actually helped you there because there’s a series of inspections where it says you are writing it this way, but you could write it that way and in the end you, you get toÖ
Giovanni Asproni 00:48:06 You kind of learn as you go. Absolutely. So somehow thing is done in such a way that to learn as you go.
Nat Pryce 00:48:11 And adopting the hint is also very easy because it will, like you say, oh you are using a four loop and mutating, but you could replace it with a map. Okay. And you just all enter on that I underline and boom, it’s a map. So it’s not like you have to sit there and go, oh gosh, how am I going to have to look up how to do a map? It would do it for you. And so the friction of moving towards that more concise and expressive way of programming is very low.
Giovanni Asproni 00:48:36 Okay. Yeah. How should the team that decide where to start from? So for example, in one of your projects, you can give us an example you say we want to go from Java to Kotlin. How do the decide where to start? I think you hinted at that before talking about some parts of the application with ready or models or something. But in general, what do you think the considerations there from the starting point should be?
Duncan McGregor 00:49:01 That’s right. I think we would start with data. Kotlin is very good at representing data. Perhaps if you’re in a Java 21 project where we have records and those sort of things may be less important. And maybe if you are in a very modern version of Java, you should maybe not consider Kotlin or at least if you moved on that far then you, you must be very happy with the language you have. But Kotlin is very good at describing data.
Nat Pryce 00:49:27 I think it’s still better at transforming data than Java is. Yes.
Giovanni Asproni 00:49:31 So now you just mentioned more than Java, you said that if teams have gone what virtual now is what, 2021? What is, I donít know what is the latest.
Nat Pryce 00:49:40 ë23 is it? Yeah, I mean I just stick to the OTSS so it’s not an LTS, I just wait for that.
Giovanni Asproni 00:49:48 But, so in this case you seem to hint at the fact that if you’re working with a Java that is let’s say one of the most recent ones, you might not have such a good reason to refactor to Kotlin. Did I understand correctly? Because they added some nice features in the language.
Duncan McGregor 00:50:04 It’s certainly true to say that I think Java was, what should we say moribund when we took up Kotlin in 2017?
Nat Pryce 00:50:11 Yeah, I think there’d been no changes to it for five years or something.
Duncan McGregor 00:50:15 Well no we just gained Lambdas I think and Streams and so on, but it had been a long time coming. If you are, if you’re stuck on an old version of Java and that largely also means an old version of the Java Virtual Machine, then Kotlin is quite an easy win. And certainly I think that’s true very much in Android, which is why it’s so popular on Android that the modern version of Java are not available in that sort of small form factor.
Duncan McGregor 00:50:40 I suppose I consider people who are keeping up to date with Java are probably keen on the language in order to keep up to date with it. And if you’re keen on the language, don’t stop doing it. Java is fine but for us Kotlin is more fun.
Nat Pryce 00:50:52 Yes. There are just things that are nicer, right? No worrying about primitive types. I got an Int I can put it in a list. Yeah sure I have to worry about performance but I have a list of Int, I don’t end up with a list of integer which is a different type, right? Things like that, right? I can have like null checks in my compiler so I don’t have to think about it. I can model, algebraic data types, which is now I think in sealed classes are now in Java. The inline functions means I can write my own control structures and that doesn’t happen a lot but, it’s useful for making domain specific languages when you want to do that kind of thing. So Java is moving in the same direction, but it’s got, I think their ambitions are much greater when it comes to things like getting rid of primitive types. Kotlin wanted to get rid of them in the language and then as a result it will do some boxing for you. And you have to be aware of that. And if you’re worrying about performance then that’s something you have to know how to deal with. While Java are looking to remove them from the Java virtual machine. That’s a much larger engineering project. No wonder it’s taking so long.
Giovanni Asproni 00:52:01 Yeah. Actually that’s interesting what you said now because are, there any performance implications in moving from Java to Kotlin?
Nat Pryce 00:52:08 It depends. It’s performance, right? But yeah, so you do have to be aware of boxing. So if you’ve got an array of Ints and then you start mapping over it, you’re going to end up with an array list of box stints that’s going to hit your performance. So you do have to be aware, of course the things that Java have are still there because you’re running on the JVM. Kotlin targets a number of different platforms. But let’s assume that we’re talking about Java to Kotlin running on the JVM. Everything you use in Java is still there. So if you want to create an array of Ints you can. So you can always drop down to writing something specific to the JVM to manage performance. That’s kind of the case you do in Java anyway when you really worry about performance you have to sort of make your code a certain style to avoid a lot of garbage or a lot of boxing or what have you and work in terms of arrays of primitives if that’s what matters.
Duncan McGregor 00:53:04 The inline functions I mentioned earlier are a place where we can actually gain quite a bit of performance I think, because it allows us to express algorithms and things on Ints as well as objects. And so we have would box in those cases. Kotlin has feature called value classes which are effectively types that are remain as primitives, remain as just an Int for example in the Java byte code but the Kotlin compiler treats as if it is actually a type. And so we can do things like non-negative Int and those sort of things and the Kotlin compiler will track the type around but actually only ever pass around the Int that is inside one of these value classes.
Nat Pryce 00:53:46 Because you have to worry about the introp boundary there more and, but that’s the case. So yeah, I mean I think since what Java 7.0, maybe Java the language and the JVM, Java is no longer just an assembly language for the JVM, right? It compiles into sometimes quite surprising structures in the JVM, which can have performance impacts, especially on embedded systems. One project we were on, we had to rummage through the, what was being generated on the heat by the compiler in order to squeeze more and more performance out of embedded system and up to date versions of the Java when they’re using enums generate like really quite a lot of stuff we didn’t want. And we had a colleague worked with the author of Proguard to add support for sort of removing this stuff to get the performance we wanted. So yes, as we always, with performance, you always get surprised by what’s going on. And Java over time has become more surprising because the relationship between the Java, the language and the JVM is sort of changing over time.
Giovanni Asproni 00:54:57 Probably because they want to support more in the JVM as well, the languages. So they are changing the JVM itself to be more.
Nat Pryce 00:55:04 Yes.
Duncan McGregor 00:55:05 The fantastic thing from Kotlin’s perspective is that JVM has gained virtual threads and they are entirely seamlessly supported by Kotlin.
Giovanni Asproni 00:55:13 So if we summarize, this means that you shouldn’t have bad performance implications unless you have some specific problems if you move from Java to Kotlin, and actually you can have bad performances in Java as well, depending on what you’re doing. So it’s not like saying, if I move from C to Python, will I have performance implications there? You can more broadly say yes, in general. Maybe you see libraries in your Python hotspot.
Nat Pryce 00:55:43 Yes. Yes, yes.
Giovanni Asproni 00:55:44 Okay. Well I think you are coming to the close of the show now, so thank you for the conversation. I think we did quite a good job of talking about this refactoring from Java to Kotlin. Is there anything that we missed that you would like to add?
Nat Pryce 00:56:06 I just encourage people to give it a go, right? Even if you don’t think you’ll adopt it, it’s worth having a look. You can play around with it in an existing project and see what it’s like. I really like it.
Giovanni Asproni 00:56:17 I agree. Sometimes trying different languages, different things can give you different ideas and maybe approach differently what you do with your language of choice. Okay. Then we come to a close. So thank you Duncan and Nat, for coming to the show. It’s been a real pleasure. And this is Giovanni Asproni for Software Engineering Radio. Thank you for listening. [End of Audio]