Great International Developer Summit (GIDS)

Refactoring to Modernize Java Applications using AI - Venkat Subramaniam

1:17:30 · 21 Apr 2026 – 24 Apr 2026 · YouTube

About this talk

This talk explores the modernization of Java applications through effective refactoring techniques and the implementation of AI tools. The speaker discusses the necessity of updating legacy code to reduce technical debt and to take advantage of new language features in Java, such as virtual threads introduced in Java 21. He emphasizes the importance of having solid automated tests in place before refactoring, as they ensure that the functionality remains intact post-changes. The talk introduces OpenRewrite, a tool for making predictable changes in code, alongside discussions on the potential of AI to automate more complex refactoring tasks. The speaker shares challenges and insights gained from using AI for code upgrades, highlighting the importance of controlling the changes made to ensure quality and maintainability.

Full transcript

So, we want to talk about uh refactoring to modernize Java applications using AI, but we'll get to the using AI part a little bit later. But, um if you think about modernization, let's talk about why do we want to modernize code? The the first thing is I would I would say having old code is a technical debt. Because the older the code is, the harder it is

to change it in the future, isn't it? So, so this is one of the reasons why you probably have to keep it updated. Especially if you don't keep the code updated, one day you get this little uh surprise that you have to upgrade the code very quickly because there is a security flaw or whatever that is leading to it, right? And then all of a sudden, you

are scrambling to update the code that can be very painful. But, in addition to it, if you update the code, you get to use newer features that can make our code much more elegant. It can make it expressive. It can make us productive. That's the second reason. A third reason, if you think about languages like Java, you might have a feature introduced in the language that can

save so much effort in writing and make your systems much more efficient as well. You probably guess what I'm talking about. Like if you want to use virtual threads, virtual thread is available only in Java 21. And if you don't upgrade to 21, you cannot use virtual threads, right? So, the question is how can we benefit from these improvements? So, we want to modernize it. modernization also

has the while it has benefits, it's got some challenges. What are some of the challenges when it comes to modernizing? The first thing is let's assume for a minute that you are the most knowledgeable person on your team. And you're saying, "Oh, Venkat I eat new versions of Java for breakfast every day. So, I know in and out of what's in Java. I'm the expert in this

language. I respect that. That's great. Thank you for being there. But I bet you, in spite of you being that expert you probably don't know everything. Why? Because we all can accept nobody knows everything, right? So I wrote an entire book on modern version of Java, and yet I keep discovering things I didn't know. That's just a nature. That is because the vastness of things that's available.

That's one problem. The second problem. You are the person who knows everything about Java but you cannot be the person upgrading a thousand projects in your company. So, you might be that person who knows a lot but you're not there everywhere because you don't scale. Humans cannot scale. So, there are other people in the team. And guess what? They are super good in what they do but

upgrading to language is not one of those things. Maybe that person is a super duper Spring developer but doesn't know everything about Java changes. So, their skill set may be different than what is needed. And so, the question is, how can we in a most efficient way when we may not or we don't have the ability to be there everywhere? How can we do it? Upgrading the

code to a modern version is a refactoring approach. So, here is a question for you. What do you need before you refactor? Any any guess? Please? Right. You said test. I was going to think of a cup of coffee, but I'll take test. So, test. You're absolutely correct and maybe a cup of coffee as well. But, a test is super super good. Because, what does a test

do? A test confirms to you that what worked before still works after the change. If you don't have the test and you refactor the code Excuse me. And then we discover that some things are broken. The next time you say you want to refactor everybody becomes you know, hesitant. They start laughing. Like the last time you refactored code and that's not going to be really fun, right?

So, you're absolutely correct. So, what do automated tests do? Automated tests provide safety for refactoring, right? That's one important So, we're not just going to talk about So, I'm saying Maven test and here we can see a bunch of tests passing. So, we are not going to touch it unless the tests are passing, right? So, here is our Maven test and we're going to run the test

every time the code changes and you can see 14 tests are passing. We're going to keep that passing. Great. Now, how can the tools help? As I mentioned, here is a bit of a question for you. Don't worry about being right. Don't worry about being wrong. I'm just going to give you some code examples and yell out what you think. Here's the first code. What are the

things you think you can do in this code? What are the changes you're going to apply? Please, why don't we? Streams. Thank you. I apologize because your sound wave doesn't get this part easily. plus there are three projectors here that wants to be louder than you. So, that's why it's hard for me to hear, but you're trying. I appreciate that. Streams. I love it. So, you can

convert the for loop to streams. That's one change. Any other idea? A little louder? Str- Continuing the streams, a few more changes? No? Think think again. Oh, stream a string builder. Thank you. Sorry again. String builder, okay? But, you don't need a string builder in this case because you can use joining on the string. So, that can be done. What else? Good. Good good so far. And

I didn't know this. I'll be honest. I did not know this. But, then I figured out new locale has been deprecated. Most of us didn't find it. I didn't find it, either. So, the new locale is deprecated. You got to use locale.of. That's another Okay. So, that's good. Let's go a little further. What about this function? Get and names. Any changes you can think of? If else,

how does that feel? Yeah, you can use a pattern matching, Switch with pattern matching than if than else. That's a lot more elegant. Anything else? What was that? Null check. Maybe. Hardcoded numbers? Oh, what was that again? Yeah, and this could be empty. We got a few thoughts. We'll have some surprises. We'll look at What about this function? Get person. Any ideas? It could be a string

builder. Multiline string text block. You could use a text block for it, right? Good. Good point. One more. This guy here. What can we do about this? A record? Great. You could use a record for this. What about the person service? What What do you think we could do with this one? Switch with pattern, right? Okay, great. So far, so good. But we're going to find a

few things maybe that's going to surprise us as well. We'll come back to that in just a minute. So, we talked about having test cases. I like your idea of having test cases. Love it. But, having said that, I would also say I don't want to refactor code that's not committed into the version control. Now, why do I want to commit things into the version control? Right?

Why can't I just change? Now, this goes a little deeper if you think about it. Because if you have a code, you do a Git, you know, Git pull, right? You change the And now you have not committed this yet. And now you do a refactor on it. Notice you made some change, and then you did refactoring. This is purely a discipline more than anything else. It's

easy to get be carried away. But, if you do refactoring, then you refactor the code that's not committed. Now, you say, "What's the big deal?" Here's the big deal. Raise your hand if this is true, honestly. Have you ever in your life made some changes to code and then said, "Oops." And spend the next 15 minutes undoing the changes you did. Have you ever done that? Yeah,

we all, right? It's okay. We all have done it. But, the very first time you do that, what do you say? That kind of sucks. Why did I waste 15 minutes undoing? Because you saved all these files, you got to open the files and make the changes again, right? And then you say, "There's got to be a better way." So, here's the lesson I've learned. And that

is uh frequent commits if you will, reduces the cost of to zero. That's all I care about. If I can reduce the cost of undo to zero, what does that provide? It gives me agility if I reduce the cost of undo to zero. So, I make changes. I go wild trying my ideas. Hey, I like it. My tests are passing. Commit. Oh, I tried this. That's not

the smartest thing. I think I have a better way to do. Guess what I do? Just blow it away. Do a get rebase, and you can move forward. The cost of undo is zero, right? I can throw it away in one shot if I've committed the changes that has happened before. That is something to keep in mind, right? Okay. Having said this, we want to be able

to, you know, uh refactor, but that poses a bit of a problem. I know the session is on AI, but I refuse to just focus on AI because my goal is not just AI, but to really modernize code. And I want to draw the lines between two approaches here. One is to use uh deter- you know, deterministic Uh so, let's say uh deterministic tool, but less powerful

on one hand, versus non-deterministic tool on the other hand, right? So, uh in this particular case, which is unfortunately, though, un- is so sorry, uh very powerful. So, once again, you're hitting this trade-off. I can use a tool that's very predictable, but not very powerful. I can use a tool that is extremely powerful, but totally unpredictable. And life doesn't give you these nice choices, right? You got

to compromise on So, my recommendation is do both. Because if you do both, you get different results. So, and you can benefit from both. The tool I want to quickly show you here is a tool called OpenRewrite. And OpenRewrite is very predictable, but less powerful. It is useful for library changes. So, I want to upgrade from one version of JDK to another. I want to be able

to upgrade from Spring one version to another. It's great for Spring. It's great for Java JDK. You say, "Ooh, but I want to change the person to a record. I want to use a stream API. I want to be able to use the text block." Sorry, OpenRewrite doesn't do that. At least not the upgrade tools that is coming with it. AI, on the other hand, can make

a lot of changes, but nothing predictable. In fact, it might take you backwards sometimes. That's an unfortunate problem. But it's super powerful, but you got to keep a close eye on it, because there's no telling what it's going to do. And and that's what we're going to see here. So, let me show you an example of this before we jump into looking at AI. So, we have

this code sitting here, and I have uh quickly I want to look at I've not committed this code in this case, but I'm going to go back here and compare these two just to show you the difference. And as you can see, target is different, right? So, target but notice the palm is different. I modified it to bring OpenRewrite, but the source is no different. It's exactly

the same. It is not highlighted. So, it means nothing has been modified in here. But if you go into source, you can see main and test. Nothing has been modified in there as well. So, that's good so Now, what do we want to do? I'm going to say Maven in this case, re- rewrite uh discover. So, when I run discover on this, it is listing all the

recipes that are available. That's exactly what it's called. They're called recipes. So, OpenRewrite is a is a open-source tool. It also has a commercial part to it. I don't work for them, so I'm not selling you anything. And the And the open-source tool has a lot of recipes. You can use any of those recipes, but incidentally, it doesn't come with the recipe to upgrade Java. That comes

as a separate library that you have to include. And when you do, what you're going to notice here is that it includes the recipes, and finally I will have what is called an active recipe. As you can see, there's a lot And finally, in this case, there's an active recipe, which is upgrade to Java 21. There's also upgrade to 25. But here's how it's going to work.

Your code may be in different versions of Java. How many of us have code in Java 17? How many of us have code in Java 11? Java 8? You know what you need to do this weekend? Java 7? So, the people who raised their hand Java 7, how do you say you're using Java 7 without saying you're using Java 7? You're using WebSphere. Is that true? In

some cases? This is unfortunate, right? Always. People don't use old version because they enjoy using old version. You don't go to work and say, "I'm awesome. I'm using Java 7, right?" It's because the libraries you depend on [clears throat] or the products you force you to stay on that. That's unfortunate thing. But when you do get an opportunity to upgrade, you may want to go from 7

to 25, right? So, if you're going to go to 7 to 25, how do you go about it? You can say go from 7 to 25. OpenRewrite will do multiple upgrades for you automatically. It'll take you from 7 to 8, 8 to 11, 11 to 17, 17 to 21, 21 to 25. You kind of know what these numbers are, right? These are all the LTS versions and

they have upgrades for each one of those. So, in this example, I have activated the upgrade to Java 21. And how did I do this? Very simple. If you look at the palm file, I've got to show you a couple of things here. There's no magic here. So, if you look at OpenRewrite, I brought in the dependency for And I also mentioned the the recipe I want

to use, which is the artifact ID on the dependency, and the active recipe is upgrade to 21. That's how it knows it. So, basically we told use this OpenRewrite and use this library and apply this active recipe for me, right? That's all we have specified. So, I go back here and say uh run. So, we are run. Basically, we're asking it to execute the active recipe. So,

at this point, it's transforming the And guess what we need to do once we transform the code? You know the drill, Make sure that the tests are passing. Because if the test is not passing, it doesn't matter what we did. So, test just passing. Great. The Now that the test is passing, I want to see what's the difference. What are the things that changed is the question,

right? So, on the left side is the old code, on the right side is the new code. Did you notice what is different? Yeah, target is different, but that's generated stuff. But let's look at palm. The palm file has a difference. What is the difference? From 11, it went to 21. So, it modified, as you can see, the version of Java for you. Great. The rest of

the stuff is the changes I made, which is the plugin that dependency we brought. Oops, sorry. Uh then, let's look at the source. Good news. What is the good news? It didn't change the test. That's a good news, right? So, one of the tenets I follow, either you change the code or change the test. Never change both at the same time unless you want trouble. So, don't

change the test and the code. They got to be separate. You say, "What? Why would I change the test?" Maybe you want to upgrade from Java, sorry, JUnit 5 to JUnit 6. That could be a change. Or you want to clean up some tests, that's fine. But to me, the commits have to be completely separate. You change the code or the test, never both, right? In this

case, notice it did not touch the test. That's good news. Then, you should look at the source. The source is modified. What did it change in the source? If you go in, it's going to change it did not change person and person service, but it changed the name transformer. What did it do in the name transformer? The one I mentioned earlier. It removed the new locale and

put What did it not change, sadly? It didn't put your implementation of streams you mentioned, right? Because Open Rewrite recipe is not capable of that change. It doesn't go through paradigm changes. It only changes library upgrades for most part. But surprise, nobody mentioned this. But look at that. It took you from get zero to get first and get minus one to get last. Much more civilized, right?

Those functions were added part of JDK to 21, Java 21. So it realized rather than using get zero and get you know, size minus one, it can use get first and get last. So that's basically the upgrade it did. Keep an eye on that get first and get last for me, right? So keep an eye on that. So this is a bit underwhelming you may say, but

keep in mind when you have a large code base and you have a large number of projects, this can be very effective in upgrading your your JDK functions or your Spring code, it can be very useful. So we saw how we can use a tool like Open Rewrite. Uh it takes more effort to configure this than to actually run it, so you can benefit from it. So

what did we do? We ran the discover, we ran the run, we did the git diff to see the difference between them. In this case I used a diff, but typically I would do a git diff before I commit. I'll run the test and then I will commit it. And that's basically what we will do. But I want to use AI to also be able to modernize

the code. Now, back to what we talked about. Open rewrite makes library changes. What about AI tools? Uh it can do a language changes. So, while open rewrite focuses on library changes, AI can go even further. So, think about this as having a very sharp knife on your hand. It's great, but you can hurt yourselves with it as well. So, you got to be very careful with

this, right? It's a very powerful but you got to be careful how you use So, with that said, let's talk about how we can benefit from using AI for this. So, let's take one step at a time. You can go to AI and say, "Go. Change everything in this entire project." You can do that. But what happens if you make a lot of In a way, I

would say to me, AI is not very different from what I would do with my fellow humans. Remember the goal is to change, to improve, and to make sure things are still working. So, a important part of this is the feedback loop. So, forget about AI for a minute. Let's say you're working with a bunch of colleagues. we all can be doing good things, but nobody none

of us is going to be perfect. So, this is why we constantly do code reviews among colleagues. When I write something, I request others to review. When they write something, I like to review as well. maybe you work for this company, and companies often create rules. And why do they create rules? They create rules because somebody thought it was a good idea. And you're going to get

some good results for it. There's the saying, right? Pathway to hell is created on good intentions. So, one rule that is completely absurd, but I've seen companies do, they say, "Your code cannot go to production until it has been reviewed." Anybody have seen that rule? Yeah, right? So, what happens? You don't push it to production. Why? Because nobody reviewed it. So, one fine morning, the boss comes

around and says, "Hey, why is this not in production?" Oh, because it's not been reviewed. Review it now. What's your review? Looks good. That is the worst review you can ever have. The review called looks good. I hate that review. In fact, I would do something absolutely stupid in my code, and wait for somebody. If they say it looks good, they are fired. Because this is clearly

broken. How could you say it looks good? That kind of review is useless, right? So, one of the things I often tell my developers is this, "Ask for smaller, frequent reviews if you want my feedback. Ask for large, infrequent if you want my blessings." You decide what you want. You want my feedback or you want my blessings? You write a little You say, "Venkat, what do you

think?" I'm going to give you that amount of feedback. You're like, "Whoa, that was a lot for a little code." Yep, dude, you wanted the You give me a lot of code, all I can tell you is, "Looks good." Because nobody has the time and patience to review all of that, right? You kind of do a simple, you know, sample here, sample there. I'm done. I can't

do this anymore. Because we get tired looking at a vastness of information that's in front of us. So, here's the you know, conundrum, right? And that is give me a little, I give you a lot of feedback. Give me a lot, I give you a little feedback. That's the irony of what we do, right? So, if you want a lot of feedback, give me a little. If

you want a little feedback, give me a lot. So, this doesn't change with AI. It even got worse. Because this AI makes a lot of change, and I tell you review it, you're like, "Yeah, right. I don't think I have patience to review all of that, right?" So, my my uh recommendation is AI can go really fast, but you cannot go that fast. What's the point in

running AI so fast that you cannot keep up with it? And then, the problem is we suffer from what it produces, which may have maybe of poor quality. Uh so, go incremental on it. If you go incremental on it, you are able to It is fast anyways, so it doesn't care, but you can go at the pace at which you can review the changes. So, my recommendation

is, "How much of AI should I use?" My answer is as much as you can review. Don't try to use AI more than what you can review because it goes out of you know out of hand. And when when it goes becomes overwhelming, we tend to lose So, let's look at how we can use this. So, I'm going to go back here. Let's start with something. Let's

start with person. So, what do we want to do with person? So, I go here to Copilot. Before we go further, you probably know this already, but if you look at the bottom over here, I also like to say Copilot is like a facade. So, Copilot is not your AI model, but it's a pathway to your AI model, right? So, one of the things you may want

to do here is to select what you really want to use over here. I'm going to just select Claude Sonnet 4.6. Doesn't matter. Pick one. But, this is also the fact where you can try different models and see how they behave. My general recommendation, try at least three models because you can get a few different variations, and that way you get they get that covered really So,

here I've chosen one of the models I want to use. And notice this one here. It is telling you ask. Ask is basically read-only. So, you're telling Copilot query the code, get me information, but don't touch it. Which is nice and safe. But, if if if and why not, if I have the code committed into the repository, I can go beyond ask. What's the worst thing that

can happen? It can change my code, and I won't Oh, by the way, don't also ask it to commit. That could be a problem, right? So, you can just ask it to change the code, he can review it. So, you can change the ask to edit. And if you change it to edit, it can allow you to modify the code as But I actually prefer agent. So,

the reason I prefer agent is edit is going to modify your files. Agent can run your test. So, it can run your test and sometimes, you know, it makes changes where it doesn't work properly. And when it runs your test, it realizes, "Brew, the code doesn't compile." It'll rework. And it says, "Oh, the code compiles, but the test fails." It'll rework. And the agent mode can reiterate

a few times until it can get things to passing. Then you can take a look at it and review it. So, I put this into the agent mode. And now I'm going to say, uh in this case, "Modernize the person class." Right? Modernize the person class. That's what I've typed over there. So, I'm asking it to take the person class. You probably notice something here. You notice

this is old-style code. So, it's using get name and get age, right? But in this case, notice it is saying, "I'm going to take away all that code for person and write it as public record person name and age." There's a good news and a bad news. What's the good news? The good news is this is very concise. The bad news is there is probably other pieces

of code that using get name and get age, and that won't work anymore. it needs to preserve the get name and get age. Now, let me pause right here and and say something. I mentioned earlier, this is non-deterministic. This is not the first time I'm running this example, obviously. I've done this demo before. And I just did this 2 days ago in another conference. And when I

did the same example there, it actually wrote the record and it added a line of comment which said, "Keeping the old style getters for backward compatibility." And it left the get name Now, it didn't. That's because it's inconsistent, right? It's non-deterministic. That is something we need to keep in mind. So, in here, I'm going to say accept for And it provided that. And the question is, what

has actually changed, right, in this case? So, I'm going to go back in and sure, this is actually run this and verify uh in this case to run the test and verify it's working. Uh but I'm going to run the test here. And notice what happened. Notice, in this case, uh it is complaining. Uh what's the error? It says, "Build failed. Get age and get name are

not available." So, this is where it failed, as you can see, right? So, I'm going to say, uh does the change actually actually work, right? So, does the change actually work? You can you can kind of challenge it and see what what does it say? Like I said, I'm a little surprised it didn't run the test. It should have run the test. And in this case, in

this time, it didn't run it. So, I'm saying, does the change actually work, right? I'm asking the question. And what does it say at this point? Um it's going through. It's saying, "Running the command." In this case, Maven test. and you have go ahead and run that for So, it's running the Maven test. It errored out. the test failed. And then it's saying uh read file. It

says reading the person service. And it's still working on it. Name Working on it. And like I said, the last time it was much better. It kept the older functions for compatibility reason. This time it failed on me. It didn't comp uh uh you know, keep it. And what does it say? Two files changed. In this case, it decided to change the names transformer and the person

service, right? So, notice it changed the get age to age. And here it changed the get name to name. So, again, inconsistent. Last time it added those functions, didn't change those classes. This time it failed to recognize it. I don't know why it didn't run the test. But when I challenged it, it figured it out and it went and modified those functions. So, I'm going to go

ahead and accept them all right now. So, we did. And and I'm going to say continue. And notice it went through. And I'm going to go ahead and check this one here and see if that passes. You can see in this case the tests are running and they passed as well. Build was successful, right? So, oops, pardon me, I'm running the wrong stuff. So, let me go

ahead and run the run uh test. So, Maven test and let's make sure it's passing. There you go. So, it's basically found out that it had made a mistake by changing one code and not another. Like I said, when you're in the agent mode, it should have already run the test automatically. This time for some reason it didn't. Hey, it's inconsistent, right? It's it's a non-deterministic. So,

there's no guarantees. So, that's our very first change we made. We modified this to have a record. And it's a good time to commit the code at this point. Your tests are passing. Your change is good. Commit it. That's another recommendation I have. I like to take refactoring incrementally. Why? When I look at my Git repository, I ask what changes are made. I've got these different commits

with different refactoring. What's the benefit? Two weeks goes by and your colleague says, "I don't like this change." You can selectively remove that change without affecting other changes because they were part of different commits. If your commit is large and you had a lot of changes in it, and if you want to remove some commit from it, that's more expensive time-wise. Granularity helps you in this case.

So, I like to keep them separate So, good. We we modified the person. Let's close that out. Let's go to the So, what am I going to ask with the person service, right, is the question. So, let's let's make sure this is still passing. That's passing. Commit the code. And what I can do now is I can say, "Modernize the person service class." So, I'm not suggesting

do one function at a time. I mean, if it's a really big function, yes, but not otherwise. But, a class at a time if you don't have too many functions is fine as well. So, now you asked it to modernize the Let's take a look at it. And you can even run this in a CI tool if you want to. And your Git action maybe can run

the test. And if you like the test are still passing you have confidence, you can push it and you can then do the review as well. So that is something you could Okay, notice what it did. It said return switch on the person.age. If the case is age with the age is greater than 17, you know what that is called? That's a guarded pattern matching, right? So

it's using the guarded pattern guarded pattern matching. If the case is 17 or if the case is default, uh do the following. So I'm going to say go ahead and run it. But as it turns out, guess what? The pattern matching with primitive type was only added in Java 25. Because the upgrade was to 21, you cannot do that. But unfortunately, Co-pilot didn't stop with 21, it

went all the way to 25. So maybe when you say modernize it, you could tell it up to what version of Java to go to. And and that could help. It It should have seen the context and decided, but yet it went all the way down. So as you can see, that didn't work. So you could say in this case, uh you know, keep the uh the

language features uh features uh to uh Java 21 or uh older versions, right? So not the 25. So we can ask it to keep the, you know, change to Java 21 or or older. So let's just run through it. Let's see if it's able to figure it out and and change it. It's still working on Let's see what it does. And it's changing. Continue. Uh it's struggling,

right? It's still struggling. But let's see. So, right there Huh. No. It is still trying to do us different but it's not able to nail down what the problem is. And ah, primitive pattern matching in switch are a preview feature. Let me check if the Java version and upgrade that and it says what's the Java version. Well, it's going to be a little bit of a trouble

because my version is 25, but the palm is using 21. That's where the problem is, right? So, so it's going to have a really bit of a hard time uh figuring that out. So, I could say keep the language features to Java 21 or older version, but it's need to yield me to provide that actually. So, let's see if it comes through. So, it's still trying. And

maybe it'll fall back on previous version. We'll see if it's if it's able to do that. Okay. So, while it's doing that, we can change this to newer versions uh as you can see in And uh So, it is telling us the palm doesn't have a compiler setting enable preview. So, it tried that and it's trying to change the palm file and seeing if that's helping. And

let's see. It's still keeping 21. And uh I might have to intervene and ask Oh, there we go. So, you can see uh it's getting the uh switch on the age default when age is greater than 17. And um case 17, I don't think it's going to work, but let's try this. And finally, I could say cancel. Right? Roll back the change. I would rather go back

to what it was before, but then I could specify what I want this to do, right? Again, if you have committed this, you can just blow it away and get back the new one. uh modernize uh modernize, sorry. features. So, let's see if it is able to do that. So, to use the pattern matching but not go all the way right into it. It's working. Let's find

out what it's doing. So, editing and uh still it's it's not figuring it out. It's using int age. That wouldn't work. Like I said, it's a bit inconsistent So, let's give it a try. Otherwise, we'll fall back and say use the integer. All right. Let's just move on from here so you get the point what it's going to and you can work with it. So, next thing

in here, uh this is going to be uh age, obviously, right in here. And let's move forward. What about name transformer? So, join uppercase. So, modernize the method uh to newer feature of Java, but what method are we asking it? So, the newer feature of Java, and this is going to be uh join uppercase, right? So, join uppercase. And what did you suggest before it should be

doing? You said it's going to be it should be be the stream API, right? So, let's see if it's going to do use the stream API and we can see how far it goes with it, right? So, let's do that in just a second as soon as this comes back. You know what? Let's just kill it, get a All right. So, modernize the method to get a

newer feature and let's ask it to do this. is that good? Are you happy with this? So, it says I will use a stream name to uppercase locale of collect joining with a comma. That's pretty good, right? Nice change. So, we'll accept it. And as a result, you want to know the test is passing and it's passing, right? What's the next step here? Uh next step get

end names. Remember, using if and else, get first, So, I'm going to go back here and say this case, modernize. This is going to be get end name, right? So, get end names uh newer features of Java, right? So, we are asking it to modernize the What's it going to do to modernize that So, it says I'm going to change it to a And it's going to

be name.size, case zero, case one, and a default. So, once again, it's hung up on that Java 25 feature, right? But, I want to pause here and mention one thing. super inconsistent, right? So, last time I was doing this example, I didn't even pay attention until somebody pointed out to me and said, "Oh dear, look at that." And they found out it had gone from get to

get zero. And from get last to get size minus one. So, it actually went back while applying pattern magic. highly inconsistent. But more important, it's also learning from me. And saying, "Oh, you went went and undid it, so I'll remember to use this." So, good news and bad news, right? Bad news, it's inconsistent. Good news, it continuously learns from you as well. So, that can be very

rewarding. And of course, you want to be sure it's working, you can go back and review your uh changes. And in this case, I've said you know, 25. So, I could make sure the tests are passing. And for that reason, let's go to the palm and see if we can find I'm going to do this here to make it a bit easier. So, this is going to

be the palm file. And I'm going to take 21, make it a 25 and see if that actually changes, right? So, going back let's go run this test, get the feedback, all the tests are And so now, go back here and verify this is actually passing as So, let's try this. And uh that's passing, so that change was good. Uh what's the next step? Next step this

is the get first Sorry, greet So, I'm going to say modernize the greet Let's say modernize the uh so sorry, greet uh person, right? So, modernize the greet person. Uh and ask it to execute that code alone. So, let's find out. Uh we suggested it to use a text block. That's pretty Pretty good, isn't it? So, it's using the text block and modifying that. It's even using

formatted, which is one of my favorite functions, as well. And it modified that. It's working on running the test, I'm supposing. We'll find out in a minute uh what it comes up with. So, it text block, formatted. That's pretty good. Let's see if it's telling me it is going to run test. It didn't this time, but you could ask it to run the test. So, if you

save that and come back and run this, you can see the test is passing. So, what's the moral of the story here? The moral of the story here is that you can ask it to refactor and it's going to do these kinds of changes. So, going back to what we talked about, it can do the paradigm changes. You saw that happened, right? It's able to modify the

uh code and use your stream It's able to make changes using the join function. Uh but, having said that, a word of caution. It's able to change the code to using modern features, I've also noticed when we do this, it might sometimes write functional style code that's completely a violation of the functional programming tenets. This is something you ought to be careful about. Huh. I was doing

a workshop and when I was doing this workshop, I was giving a bunch of problems for the team to solve, and the challenge I placed was, "Here's a problem, solve it as humans by collaborating, and then use AI to solve it." But for one of the problems, I said, "Here's a problem, solve it as a human." And then I said, "I have not seen a AI tool

as of day which has solved this problem." What I meant was, I forgot one word, the word correctly. So, I said I've not seen any AI tool solve this problem. And the team worked on it. as as time went on, humans were solving And they used AI. And one of the person in my workshop you know, called out and said, "Hey Venkat, you said no AI tool

has solved this problem." I said, "Yep, I haven't seen any AI tool solve it." And then the person said, "But mind it, I'm like, "Wow, that's pretty cool. If you don't mind, can I take a look at your code?" So, I go down and look at the code. saw what he had done. And I looked at the person and said, "What do you think of this?" And

the person said, "Well, you said it wouldn't solve the problem, but it did." And I said, "Wait, but right in the middle of the stream functional pipeline, in the third line, it is mutating a data which is outside the pipeline. That's a no-no. You shouldn't be doing." And the person said, "Oh, I don't know functional programming. You said it wouldn't solve it. It spit some code. I

thought it it has done the work." And that is one of the things we have to be careful about. So, when the tool is doing things, you still need to be able to verify and say, "Is this good or is it bad?" And if it is not a good code, it's not that you have to change it. But you can go back to the tool and say,

"All you have to do sometimes is to ask, what do you think of the code you wrote?" And it would literally tell you, "Well, that was really bad." "Well, then why did you write it?" So, essentially, it can spit out code, but you can challenge it. And if it finds problems, it'll go fix it. If it cannot fix it, you can continue through the iteration a few

times, but here's the key. If you know the difference between right and wrong, it becomes a lot easier. But if you don't know and it creates some code, and if you just accept it, there could be consequences later on. So, this is why it's not a substitute for your knowledge, but it's a substitute for your manual labor, right? So, that's where it it it becomes beneficial. So,

as a result in this example, we have altered this to bring in a a text block, and it tells you what the text block is doing, and how the you know, code can use it. Here is a pattern matching, and here is the stream API that you really wanted, and it also preserved the get first and the get last as the functions to use as well, which

is which is pretty darn good. So, as we talked about, it is able to do paradigm changes. It is also able to do language changes as well. So, going from the text to text block, going to use a pattern matching, all those changes worked really well also. So, so that is basically the power uh of of these tools. And like you mentioned, you can also try this

with a few different models. You can change it and see if one or the other works a lot better, and you can decide to use a couple of different models. If you are making a change, and if it's not giving you the proper results, you could then go back and try a different model and see if it gives a better result. You can also go to the

model, use it. You can use a different model to go back and verify the code and say, "What do you think of the quality of the code?" It might give you some suggestions for it as ways to improve it as well, and then you you improve it at that particular a point. So, any questions or comments on this before we move forward? Thoughts? Your own experiences? If

you've seen something similar, something different? Please. Look at the feedback problem. Okay, give me 1 second. Mike is heading your way. Oh, that was pretty quick. Good job. Thank you. Thank you. And you can say for a cash now and see if you get it so quickly. Yeah. Well, go ahead, please. Okay. And when you say check with another model, do you mean to say review the

code with another model? Yeah. Yeah, absolutely. Not just code. Almost anything you want to check. So, here let's step back for a minute, right? You got a lot of inconsistence. And when you have inconsistence, especially if you're going to put this in automation mode, one of the things you want to do is you want to verify, but you also want to minimize what you verify. If you

get a lot of garbage, your verification is harder. If you get a better quality, verification is easier. So, what what you can do is if especially if you're automating it, you can unleash three models on it and see if there's a consistency between the changes they are recommending. And if two of them suggest one and one says suggest something different, maybe you can lean towards what's more

common and see if that works better. That's one But but more important, if you are asking one model to write the you can ask a different model to critique it as well. And even if you're asking one model to critique it, that model may not give you the best criticism about the code. Let's say our team wrote this code, but you want AI to review it. Sure.

But have that done by couple of So, the feedback you are getting, the beauty is it may say, "This is wrong." Another model may say, "That is wrong." And what you will find out, most likely, is there's an overlap, but there are differences. But the benefit is you got a comprehensive set of things to look at. So, that makes your effort much more stronger, right? It's kind

of like what you would do with colleagues. If you're sitting around with colleagues, you're asking for ideas, two people suggest the same thing, but they also suggest there's different things. So, now you ended up with three things, and not just two things. So, so that is the power you get out of this. So, you can have the models review your uh code for quality. I also do

this quite a bit when I'm doing design reviews. I would say, "Hey, review this code and tell me what design principles are being violated." Or this uh tell me about the quality of the design and how you would improve it. And then you can just look at the different recommendation from models that can give you some nice ideas, as well. Another question related to this one. Uh

modernization is good with the help of an AI, but for example, uh take the example of streams. If I'm using a shorter data set, like 100, using streams is not a good idea, right? Having a for loop is a better idea than having streams. So, how do you handle these kind of uh things? So, you're you're saying from the performance point of view? Yes. So, so one

of the things to be careful about is this is my experience has been 90% of too excited about performance than reality actually requires. Most of the time it's a war and the time the performance is very critical, I usually have a test to verify the performance I want. And if the change gives me that performance, I'm happy. So, the key here is the following, right? This is

one of the things I always say uh that we need to be careful about. And and that is I I would say uh you know uh when it comes to, right? This is a recommendation I often have. So, when it comes to performance, the right question is not if it is fast, but if it is adequate. So, this is what I would always recommend. I don't want

the fastest code. I want the adequate speed. Why? Because it's trade-offs. If I really focus on the speed, I may have compromised on other things, including maybe code quality. You know, I can write code which can poke into memory and give me performance. That code is almost impossible to maintain. And then I realize, while I needed this performance, I really worked hard to provide this. And in

the process, I lowered my quality of code to maintainability, right? So, this is when YAGNI comes in. You're not going to need it. But, the problem is between what is adequate to what you provided, nobody gets this benefit. So, it's a raw performance that is sitting there for no and you have maintainability lost over here. So, that's the trade-off you have to be careful about. So, what

I often recommend is if performance is write a test for it. When you start writing a test for it, you are going through a much more deliberate set of questions. You're not saying it's got to be fast. You're asking, "What does it mean? What's your data set? And what kind of environment are you running in?" You're trying to be more specific because those things matter for you

to provide the performance you need. And and also, if it is a small set of values, if it does the job, it's easier to understand, easier to maintain. You probably didn't have any impact on performance, but you got a more maintainable code. So, it it's a trade-off you have to be careful If you can pass the Thank you. uh First of all, thanks for all the sessions

today. It was very interesting, sir. Thanks a lot for that. Thank you. So, reverse engineering is, you know, one step or maybe one of the approaches which teams use for modernization. Right? So, where does, you know, how much AI has been adopted or can be adopted or to to use as part of reverse engineering while modernizing the applications? The short answer I can give you is that

eventually the lawyers will be the ones profiting. That's the bottom line. That's where we're heading. the the Think about it this way, right? If I have the capability to break into a bank, that doesn't make it legal. It is still illegal to break into a bank. just because I have the capability to reverse engineer, it doesn't make it So, eventually the whole thing is going to be

get played out in the court of law. that's the answer. So, it's not a technology problem, right? At certain point. So, if I have the ability to do something, what prevents me from doing it? Ethics? Morality? And if nothing else, fear of law. And and that's what it boils down to. Um when when you were Or So, uh I don't know if you got a chance to

look at it. I haven't read through this fully, so I'm not pretending that I read through it also, but it's a glance I look at in glance. This happened couple of days ago, I think. Oracle JVM team Oracle announced that the Java OpenJDK will not receive any AI-generated code in their pull request. >> Right. And the reason one of the reasons stated exactly this problem. Because their

fear is they could have lawsuits from people claiming that their code was used. And and I mean I I'll be honest, there are days I use AI and it generates a code and all I can think of is which poor programmer wrote this. Because it's so much like what somebody else could have created, right? And the problem is this. When I when I use it for educational

purposes, that's one thing. But if I commercialize you put it in my commercial product and release it, that's potentially going to be leading to lawsuits. And at that point your attorneys really care about your pocket size. If you're you have a bigger pocket, they want to come after So that's what you really need to answer, right? So yes, it's cool to have these tools, but that's like

saying, I've got this beautiful tool to break That's that's what it amounts to, right? So eventually this is going to get played out at the court of law. Uh not something that any of us in this room rejoice or enjoy because most of us are technical people like you. Leave me alone, I like I enjoy writing code. I don't want to be getting dragged into legality most

of the time. But unfortunately, that's a consequence of what we do. So I don't have a good answer other than, you know, you have to think about ethics. You have to think about morality. If if nothing else, you got to think of the legal consequences. That that's what it amounts to. Sure, thank you. Any other questions? Oh, there's one in the front. Let Let's Let's make sure

the questions are far away from each other because he needs steps. He has not gotten his steps today. He needs some exercise, right? Hi. Uh like when we have tried it personally or in uh my company for using AI to modernize or update any version, so what we faced was it doesn't get transitive dependencies very right. Like there are certain libraries which are compatible with each other

and the model doesn't know the right version uh or at least the latest version. So, have you faced similar problems or do you have a solution for that or Yeah, good good question. >> to search it and then Yeah. So, so so beautiful, right? So, one of the things is if you have transitive dependencies, it doesn't always work through it properly. Uh what I've noticed is uh

when you realize something like that, if you specifically probe it with that If you don't like it, you can tell me you don't like it. You don't have to whistle through that. No, just kidding. Okay. So, this is the problem is this, right? If you probe it with that particular question, it often leads you to better answers. So, that may not be the focus. It might do

things based on uh what it's looking at. But, this is also where when you're on an agentic mode and if it's running through test cases, and if the test were to fail, it often may result in probing lot further and and wait until it resolves it. So, that's one answer. You can try to write it in agent mode. The other is when you find that issue, you

can ask that question specifically and say, "What are the consequences of this in terms of transitive dependency?" and it will walk through your dependency graph and figure it out as well. So, so it may not succeed in instance one, but eventually it tries to resolve it. And that could be useful as well. Right here. Good job. Oh, there you go. Thank you. So, one of the interesting

instances we have seen when upgrading libraries is um they generally take more time to somehow get to a point where the compilation succeeds, but at run time it starts acting up. Connecting with different middleware, whether it's Kafka, Elastic, or Redis, starts breaking. So, we made it a point to not just run unit tests, but then run the entire integration suite after this whole library upgrade is done.

So, that gives us more confidence that this is actually working as expected and there are no class loading issues. No, totally. That's a very good point. you you want to be sure that everything that's supposed to work is actually working, right? Uh but typically what do you want to do? You want to run your unit test, but you also want to run your functional integration test as

well and make sure that all of that are working. Uh absolutely. Uh in fact, here's a way to think about it, right? Now that you have these tools that can make you move faster, why would you want to compromise on the feedback loops that confirm that you really ran fast to get to where you need to get to rather than end up in a in a ditch,

right? So, so let's not try this madness of I have to run fast, but to say, let's make it sustainable and and be able to verify this. And I think that's where we can actually get the real feedback. But we can also put those into a CI pipeline. So we don't have to manually do this as well. So that if the CI were to fail then we

can go back and review it and see what's happening. If the CI passes, then we can maybe do one more review and then we can push it. So it can be automated as well. So good point, absolutely. Thank you. Nope, right here. Thank you. Thanks. Yeah, Venkat, my question is so inspect driven development or any agentic development pattern, so the app modernization should go hand in hand.

Like should we include this in the skill.md and for every feature implementation, so we should take care of the modernization also? Or it should be a separate task individual? Right. So so I would generally say you would want it to be a separate task. The reason I would say you want it to be a separate task is this goes back to what I mentioned earlier. I need

clarity on what changes were made. So think of it as cohesion at the task level. So when I walk in and say what did you change there? I don't want you to say this and that and that and that. It's like that's the one that It's very easy for me to remove that change if I have to remove something. And when it's cohesive, it's easy to remove

it. When it is coupled with other changes, you're going to spend more time removing it. So so to me the number of commits is cheap. effort and time to fix a commit is expensive. So that's where I want to really ask the question, what do I want to save? You don't gain much by clubbing commits together. But, you gained a lot by splitting them because it becomes

easier for me to identify changes and and modify them. So, always ask the question, what's the consequence? What's the cost saving? And we often try to focus on things that don't actually save and in the process we end up actually losing in the long run. So, I'm very particular about isolation of these things so that it's easier. Okay. So, should we tell agents also to do the

frequent commits by mentioning in the scales or in the agents MD? You could. Except to be sure that it doesn't commit unless all the tests are So, so you need to make sure it follows good discipline as you would. Otherwise, it has partial files and it's and it's committing and then goes back to commit other files. Granularity is important, right? You don't just like I said where

I said I don't want one change with a lot of stuff. I don't want two changes which should be part of one. So, that that definition of the granularity is important. What is that unit? We need to be sure it's following that as well. Sure. Thank you. Thank you. Are we Yeah, we're about 10 minutes over, but you tell me. Let's see if there's a really last

question. Oh, there's one. We'll take that. Two more and then we'll be done. Two more and we'll be good. Okay. in case of moving to a modernized application, we have a Struts CJB. That's a in Java EE. So, which how would we approach this modernization? So, it is still running So, what is the approach do you in order to modernize this kind of application? A lot of

coffee. >> [laughter] >> So, I would recommend taking that So, here's the thing to really ask, If you do a lot of changes in one shot and when things don't work you got a lot of things to waste your time on. Uh this is a lesson I've learned the hard way. You really want to have a good control on what you're doing. So, I would say draw

out a map and say which of those changes are uh high priority and in in what order to do. So, hey, I've got a application written in Ant uh using a build written in Ant. I would attack that first. And because you're not touching any code you're only changing the build and that's a safe bet. Once you move that from that to Maven or Gradle commit. So,

now you have a way to run this in a modern tool. And that's done, right? So, then I can say, "Okay, now that I've done that now let me attack the next one and the next one and the next one." And and so do that in stages. The only reason is even though the tool may be capable of doing it much bigger I need to have control

over reviewing it. And that becomes easier if I know what I'm changing. Uh so, prioritize but do one at a time. Uh like I said, the key is to min- you know uh minimize the breakage. That that's that's essential, I think. Uh along the way, hopefully there is enough test. But that that we will talk about here. Yeah, please. So, I think uh as you just said,

uh you I have heard your two sessions. I think previous one also, this one also. you started with the test-driven one. Now it is relating to the modernize application here as well. And which is I think the foundation of the this session as the having the test cases uh primarily on the place that, okay, in a 100% coverage might be you are looking at. But I'm coming

from that uh my question is from in the from the different point of view. When I have a code base which is maybe the legacy code where that I don't have a test coverage not 100% where that I'm I want to modernize that. Eventually, I'm either I will be writing a test cases first or I either I would be modernizing the code first. And I'm both the

places I'm using AI. So how this loop basically going to close basically? Either I should write the test cases manually first and then going for the going with the AI. What do you suggest? Yeah. So the so so first and foremost key is I'm not a fan of 100% code coverage. So when I teach classes I emphasize that because I want discipline. But in practice, I don't

emphasize it. Not because I don't care about it, it's because it's meaningless. I'm I'm not I'm not exaggerating when I say this to you. I have companies where they write tests with no assert so they can have 100% code coverage, which is kind of meaningless. So so I think it's important to really understand what we are doing and why we are doing it. So the goal is

not 100% coverage. The goal is meaningful test. If the tests are not meaningful, you could have 100% coverage and you could be misleading and still have problems. So having said that you are refactoring but you don't have any test. That's a reality happens in some cases, My recommendation is not unit test. My recommendation is feedback. If you are able to have unit test, great. If you're not

able to write unit test, you know, sometimes we refactor so we can write test. So, if you don't have then at least try to introduce um automated integration test. You're like, "No, even I cannot do that." Well, then at least write some manual test. In other words, write some test that can give you the fast feedback, the fastest you can get. Ideally, you work from here to

here. Ideally, automated unit test. If you cannot get that, automated functional test. Cannot get that, automated integration test. You cannot get that, But, have any one of them, the closest you can possibly do. That's the first thing I would do. And I would use AI to write them as well, but I would verify them thoroughly to make sure they're meaningful. And once you do, you got the

benefit. You didn't touch the code. If you modify the code, and if the behavior changes, you have no way to know. Unless you start detecting bugs later on, that's expensive. So, by writing the test first, you are making sure the tests are passing, and that tells you the code is doing what the test is saying it's going to do. Now, you can refactor the code for whatever

reason, modernizing, improving, and the same tests are And now you say, "All right, the code has been refactored to the extent possible. The test is passing. Commit." Now, you say, "I really want to break this code even further, so I'm thinking of writing other test cases, and then moving forward. And then once those tests pass, and the code is working, then come back and fix those other

test cases." So, so in other words, the tenant I mentioned earlier, you change the code or the test at the one or the other, not both at the same Uh but I would always start with a test because the minute you put a test harness, you got the feedback loop going that it improves your confidence. And then you can move forward even further after that. Thank you.

Yep. >> [music]