 Nikolai, thank you very much for coming. I know it's late for you and you had a very busy day. So Nikolai wanted to be the last one to present because he has the latest features of Java to present to us. That's exactly the reason. Yeah, thank you very much for having me on. It's really cool. The time difference doesn't bother me, right? I was just having lunch with my family and now I'm here screaming into the void. Amazing. Well, Nikolai, thank you again and the stage is yours. Thank you. Well, I got to actually share something, right? Yes, the shirt is on top of your head. Right. There it is. OK, great. So hi, everybody. To quote Brian Getz, I prepared too much. So I'm going to talk too fast. So I'm going to talk about myself if we have a couple of minutes in the end. Let's get going. So yes, Java after 11, by the way. Usually the talk was called Java after 8. But that's way in the past. But that's where the chocolate theme came in, right? Because of after 8 chocolate. So anyway, Java after 11 plus chocolate. There you go. So the plan for today is we're going to start with the Java 11 code base and then we're going to update it all the way to 15 and a bit beyond that. Meaning, well, it was still going to stay on 15, but we're going to look at some preview features that were not really finalized yet. And I hope that you're going to be amazed by how much changed even since 11, because for most people, 11 is pretty much the most moron thing that they could run on. And yet there are greater things after that to come. So this talk is not a tutorial, right? I don't have the time to teach you all the details of these features. I'm just going to slap your left and right with new stuff. So you're going to get out of here and be a little bit dazzled, but also like, hey, that's really cool. Let's look into updating that. It's also not a complete list. And I have to tell, I stream on Twitch occasionally. And the people there have been very, very helpful. At least half of the ideas in this code base were not mine, only the bad ones. So that's a bad idea. It's probably mine if it's a good idea, most likely somebody else. If you go to slides.codefx.org, on the right-hand side, you'll see Java after 8. That's the header. You can see the slides there. And then you can also go to the link to the repository that I'm going through here. So if you want, if an IDE open, you can just do this on the side together with me. Quick jump into Java 11 was released 1 and 1 half years ago, not two years ago, actually, at September 2018. Oracle just provided free support for just a couple of months. But the community at large, under the guidance of Red Hat, offer support until at least October 2024. So still a couple of years that we can enjoy this release. And there's more free and commercial support to be had, lots of options there. So it's a great release. And it's the first modern Java release, meaning the first Java release after 8 that has long-term support by many different vendors, free and monetary, as I mentioned earlier, as a really solid feature set. Not only everything cool in Java 8, also the model system, virus pretty neat. And like Java 9 brought a bunch of API improvements that are easy to miss, that I would normally show in the stock. But so that's a really good 11 great feature set, pretty stable, and already mentioned the support there. But things are already moving forward. So what it seems like 11 is, well, not cutting edge, but pretty much still the edge. There are thrilling new language features that are either already released or are in the making and could be released and finalized very, very soon. A couple of improved APIs, a few very interesting new JDM features. And usually performance improves, although there's a asterisk there, it doesn't always. Actually, in the specific code that I wrote, we're going to see that it's not actually getting much better. OK, so let's show you how much you're missing out if you're still on Java 11. And I'm saying this a bit precisely, fish, whatever. How will you say that word? Anyway, so I'm not entirely serious here. I know there are good reasons to stay on Java 11. I don't want to shame anybody into updating. But still, if you have the chance, take a look. So let's start. Let's go look at the code base. But this is a Java 11 code base, pretty generic. Nothing much special going on. What it does is, this is actually a recommendation engine that I wrote for my own blogs, for my blog and blog posts. So I have articles there. I have a couple of talks there. I have a bunch of videos there. And these are all markdown files that have, oh, crap. We don't want to have the preview, though. So there's a markdown file here, right? So we have this frontmatter here, which is like a title. And then they have tags on each post and a date and stuff like that. And then the content is down here. And what I want to have is, in the end, I want to recommend. Oh, so you've read that blog post. Why don't you read this blog post next? You know, like most blogs do. And this is what this engine does. So really, it looks about how related things are. That's where the term genealogy comes in. So the basic domain model is this. It's in this repository post. And you can already see there's a title there. And there's a title there. There's tags there. And there's tag there. So there are these small classes here that basically wrap each of these information. There's no date class here, because we have date time for that. We don't need that. But for Slack, there's a Slack class. And this stuff is very simple, most of these classes. Just like basically, a Slack is a string, but I like to have proper types, not just throw strings around all day long. So I have a proper type wrapping that string. So that's what Slack does, for example. And then you have an article, for example, here. An article has a title and a couple of tags and dates. You know, you can see how this gets together. OtherFactory is in charge of parsing these files and creating the proper instances. And once we've parsed and created these instances, they go through this Genealogy API. So there's a Genealogist API here. It's decoupled. I'm going to show that in a second. And a Genealogist is very simple. I'm going to give you two posts, and you're going to tell me how they're related, how much they're related. Give me a score between zero and a hundred. How much, you know, are they related? And once you piped all the combinations of all the posts through this Genealogist or several of them, you end up with something that's basically what this code here does. Genealogy does that. It takes a bunch of posts and a bunch of Genealogists, and then it pipes all of them through that. And then you end up with a recommendation engine, also pretty straightforward. Give me all of these relations between the posts, and then let me know how many recommendations you want to have per post. And then I sort them and give you for each post, like these three other things that you can look at. OK, so that should somewhat work. Let's have a look. Let's go for each version. It's Java 11 running here. You can also have Maven for that. No. Yeah, I got it. Shut up, IntelliJ. So you can see Maven 3.6. Java 11 is running. So let's quickly execute this. I'm going to build this quietly with Maven in the background. That's why you don't see any output, but that's what's happening right now. And then we're going to run it 10 times. And this is a very superficial performance test that should by no means be reproduced to make any meaningful decisions, but it gives us a little bit of an indicator. So this took, like, 1.1 seconds roundabout. And you can see that we got 2,150 lines of code. So put a pin into that. Let's say 2,160 lines of code and about 72,000 characters. Because we're going to see this number drop while the code base improves. OK, so let's go. Let's go to Java 11. Let's go to the parent POM. And if you think that updates, oh, look, there's 8 here. That's a bit weird. Let me see. This shouldn't say 8. Why does it say 8? Oh, yeah, probably didn't check out the right version. We should be on this one. And now it says 11. OK, so I actually didn't see the right stats there. Then let's do this again, because this is already right. So the code base, as I said, goes back to Java 8. On Java 11, we can already see a bit of an improvement when it comes to size. So remember, we had 2,160 lines of code. And now we have, well, we dropped like 100 lines of code. And we got like a couple milliseconds longer, but this is surely below what can be precisely measured. So there you go. We got like 3,000 characters and 100 lines of code. Now, let's go to 12 now. That was the original going, right? And the first thing we're going to notice is if we go to 12, that VentureFore actually doesn't do its job anymore. So it can't see, oh, well, that's not happening yet, because we also have to switch to the Java version, of course. But we execute this one. OK, so once again, building this with Maven, but it fails, because the SureFire plugin says it's unsupported file major version 56. And that's going to happen if you update beyond the long term support release. Occasionally, some of your dependencies will not be quite up to date. The solution for that is because the reason why it's ASM, and if you update the ASM dependency of SureFire, then this problem goes away. So let's have a look at the SureFire plugin. There it is, SureFire. Let's add this dependency that is copied from off-screen. It should go right. So OK, so now we're in Java 12. We have a new ASM version, and now this should work. And now we can start using Java 12 features. There you go. It runs. It does its thing. Good. So the first thing I want to show you in Java 12, it's a really small thing, but it's really cool too. So you know a completable future, right? A completable future has been around since Java 8, and it has a rather big API. There are tons of different, basically, orthogonal concerns that this API solves, and then the API is more or less the combinatorial explosion of all of these possibilities. Do you want to do stuff on just any thread, or does it have to be ASM? Are you dealing with things that return a completable future, or just the pure result, like a map-flat-map situation? Or you have to handle errors as well, stuff like that. And there's a lot going on. A few holes were still left, and some of them are being plucked. So this, for example, is a bit unfortunate. So I checked something, but the important part is that after this, I get a completable future of the string error that I have here. Now, what I want to do then, if that does not work, if there's an error, I want to do something else. And this something else also returns a completable future. So this is a classic flat-map situation, like in a stream or an optional, right? Flat-map, exceptionally flat-map, didn't exist before. It doesn't now. So instead of having to actually execute this and then join within the lambda, which is a bit unfortunate, we can also, I'll say, exceptionally compose. There it is. And now we can just, oh, it's static, right? Yeah. So we need the class name, it's config, if I remember correctly. All right, doesn't have the right parameters. So actually we cannot use a method reference, or we can't get rid of the join. So that's good. This is no special syntax I use here. I just use two underscores to say, I don't care about the parameter here. So that's one cool thing. And then another one that's new is, if you call something like exceptionally, or I think then, what's the name of the non-exceptional case? Anyway, on a completable future, you're not guaranteed that it's actually gonna run a background threat. You can use async for that. And that's new as well, that you can now have exceptionally async so that it can force the completable future pipeline for this step to be its separate threat and not running the potentially in the same threat that you're calling this, for example. Okay, yeah, sorry. Yeah, you can see the bottom line here. That's a bit unfortunate. What can I do? Yes, we can do something. Let's pull in, let's pull in another terminal. And then because we can, and then we can do this. And this won't work because I also have to select Java 12 here. And now we can run this here. And now this should not be covered by my image, right? That should be better. So now we're building and running this with completable future updates. Yep, and here you can see the stats. Okay, that's just a nice little improvement of an already existing API. In other news or in related news, something similar happened to the Stream API. So there's something that I'm doing here that happens quite often for me. I have this stream of typed relations. And what I now want to do is like, so that means a type relation and a solution between two blog posts that got a score how related they are. And there's a type attached to it, meaning the kind of genealogist. I forgot to show you those, but the different kinds, like the different ways to evaluate how similar blog posts are. You could compare the tags, you could compare the dates, you could compare what the content is. Right, so there are different ways. And if I use three of them, I get basically the same relation of two blog posts with three different scores and three different types. And I want to put them together. And what happens here is that I basically need an existing piece of information and a new piece of information. And then I need both of those pieces in the next step of the pipeline. And if this happens, then you're often in a situation where like you have, like this is not, stream pipelines don't offer an obvious way to do that. In this case, I got kind of lucky because it was towards the end of the stream pipeline. And what you can do then is you can use a new cool kind of collector. The collector is called teeing. It means you get the stream of data in, it tees into two different collectors and then takes the result and puts it together. I'm not gonna talk you through how exactly this piece of code that I'm now gonna post works. Let me see, probably should copy all of it. Because it's a little bit complex, well, not complex. I could talk a few in a couple of minutes. But I'd rather spend those minutes with the cooler features, even cooler features towards the end. There's a couple of imports here and this one as well. Sorry, I'm sure at some point when I have all of these done, there you go. Okay, so this mapping here, this teeing, sorry, takes two collectors. This is the first one, it has some kind of result. And then this is the second one, it also has some kind of result. And then I got two specific things, like a single thing, well, two single things that I can then put together. And that's what I'm doing here. So for example, let's say you have a bunch of numbers. You want to compute the average and the sum. That's really simple now with teeing because there's a collector that averages and there's a collector that sums. You can now use both of these collectors and then get the result in this function and put the result together anywhere you want. That's a pretty cool feature that's new in 12 as well. The other cool thing in 12 is switch expressions. It's a preview feature in 12. And we're going to look at it when we come to 14 because that's when it's actually finalized. So let's skip switch expressions for now. We can continue. Two more interesting things I want to show you in Java 12. There's also improvement of class data sharing archives that now is a default archive, but let's not go too deep into that. Instead, we're going to actually straight jump to Java 14 because 13 has text blocks. Once again, we're not yet finalized. So I want to show you them when they are finalized. So there's nothing else I want to show you in 13. So let's go to 14 instead. And now I can use switch expressions. Now the problem is here that this code base doesn't actually switch a whole lot. I had to force it a bit to actually show you a switch expression, okay? So if you look at this piece of code and think, well, there would have been a better way to solve this problem, you may be right. Like, right, it's a demo project. So here are the genealogists and they're in charge of comparing different posts, right? They get two posts each and then they're in charge of giving them a score. And one of them looks at the type of a post. A post can be an article, can be a video or it can be a talk. And here I basically assume that I want to give different things, different scores. I want to say, maybe I could say if you haven't read an article, I want to give you another article recommendation. If you've watched the video, I want to give you another video recommendation. But that's not actually what I want to do here. Instead, I want to say, look, I want to promote my videos. So if the second post, the one that is going to be recommended is a video, then I'm definitely going to give you a high score. Okay, so how do I do this? I take this class name and switch over that. And if you cringe now, stay with me. We're going to come back to that in a bit later. But for now, that's what I'm doing here, right? I'm going to take the class and I'm going to get the simple name because that's the simplest thing to switch over and then I'm doing that. Okay, so this is how you would do this if you don't pull the switch into its own method. You create a variable, then you go through a switch, assign the variable and have to remember to break. And there's a bunch of uncomfortable things here. First of all, you have to remember to break. Otherwise, things break. I think that's where the key word came from. Should probably call it non-break, but I never mind. And then also you have to make sure that you've covered all the bases, right? That you've covered all the cases. So let's do a switch expression. A switch expression allows us to not have a switch as a statement where basically like an if statement, but we can turn it into like the term or the condition operator basically, which means the switch on its own gets a value. And this is very simple. You can just have the switch on the right-hand side of an assignment, right? So then we say, well, the score is whatever the result of this is. And now there are two different ways to define this. You could do this, no, sorry, it's yield. You can yield these values here. And then when you yield, basically yield is like a return from within a break, sorry, from within a case. So there's not actually a fault through anymore. So you don't have to break. Even better, more concise, you can use this arrow form, which you already know from lambdas, right? So you can say, and I think this is really highly readable. Well, if this thing is a string, also if this thing is an article, then the value result is 50. If the switch over this is a video, then it's 90 and so forth. Now the compiler complains because I didn't cover all the bases, right? Like what if it's neither of these three? Well, unfortunately for now, I still have to do this. Okay, now it's a statement, so I have to put that there. And that's the gist of such expressions. And I think they're really, really cool. Great, so let's switch to Java 14. By the way, if you're using, if you're not using SDK man yet, when you occasionally switch between dedicates, I can highly recommend SDK man, like this is what I'm using here. And that's pretty cool tool to make this very, very easy. Okay, so now we can run this and let's see. So it still works. That's kind of cool. Okay, that looks good. Now let's check something else out. So I always, I'm a big fan of optional, like in this entire code base, there is no legal null value anywhere. So whenever something is null, it's always a bug. Which, and then many people like, why don't you use optional so much? You should use optional. Now there's a perfectly great value to use as a signal that something's not there. And, you know, like, okay. But then Oracle made this kind of like a tournament of the different Java features. And the helpful NPE exceptions made it like into the semi-finals or something, which is ridiculous because it's a pretty small feature. And if we just avoid null pointer exception, we don't have this problem. So I thought a bit weird that on the one hand, everybody tells me, now there's not that much of a problem, but a trivial feature like that, one against amazing stuff like JFR or something. So that was really weird. So let's have a look from raw config. There's something here. Where was it? Create, am I right in the right folder? Yeah, I want to have something here return null. We have to be clever about it because we don't return null in the right place. Things are going to break. I'm apparently renamed that method. So let's try returning null, let's see, create, where's that called? Where's create called? Oh, wait, let's call from the outside. Okay. So I need something that returns, when we return null there, that definitely leads to null pointer exception. So, you know, I can show you null pointer exception. Re-project object. Let's try this. Let's try returning null here for now. Let's see where this works. Return null. So now we're going to break this on purpose so we can see, oh, right, sorry, wrong terminal. I want to break this on purpose so we can see how the new null pointer exceptions look like. Great, okay. So this is a normal null pointer exception, right? That you can see on the right hand side. It says exception in the threat main. This is the null pointer exception and then, you know, where it was exactly. But not much more. Now, let's have a look at the stats file. The actual command that runs is this. So let's use this to run. We're going to see the same null pointer exception. There it is. And now we're going to unlock these details that we can see. So this is a command line flag that you have to add. It's xx and then show code details in exception message. If you run this, it starts the same way but now the exception message is there. It's now actual message. And it says cannot invoke, exceptionally compose async because the return value of this method is null. And if you're a developer, that might not make the biggest difference because you're already in front of your code, right? You get the line here. So that's already pretty helpful. You can already get very far without this additional message but imagine if there are different layers of support in your project, for example. Maybe somebody who's not as technical who doesn't have an IDE open and counters this error. This can really be helpful in various situations. And unfortunately, you still have to turn it on but I think on 15 or 16 or something, this is going to be the default. So from then on, we'll always see this. Good, let's re, let's mend the code. There it is, works again. And now in 15, we can actually start using some of the newer things which would in this case be text blocks. So the output that I create is this JSON file. Where is it? It should be somewhere here. IntelliJ doesn't see it, should pop up there. The output of all of this is a JSON file which basically says, well, if you've watched or looked, if you read this post, then the next one you wanna read is that post. So I create a bit of JSON. And I'm not the biggest fan of taking an entire dependency for just a bit of code. So basically, this is all the code that actually writes the JSON. Well, actually, no, sorry, the method here writes the JSON. But this is not only the JSON-y part, so to speak. And you see that these are strings that go across several lines, but I have to use string concatenation and backslash and to make this somewhat readable. And this is where text blocks come in. And IntelliJ already promotes that, you know, we play to the text block. That was a bit helpful. Now we've got three quotation marks here. Don't have to put the new line there anymore. The single quotation mark is a lot more special. So the escaping went away, but we can do a bit better. Because we can use the indentation in the code base to define the indentation in the string. But look at the string. You can much better see how it's indented. This looks reasonably like a JSON part, right? You can now see, better see that this is proper JSON. And that's really cool, right? So the marker here determines this line that you can maybe not see on stream, but that's a thin green line that IntelliJ put there. And from that line, the indentation will show up in the actual strings of indent this one more. You will see that everything is now also in the string indented a bit more. So that's pretty nifty right there. Another cool thing that was added together with this is a string format. So one thing that's quite common is to do something like this, right? We have a string that has placeholders there for strings or whatever under other data. And then you have to call string.format the static method and then provide the data. It's a bit weird to do it round about like that. We're used to that, but why do we do it that way? And in Java 9, sorry, Java 14, 15, that's where we are, right? Yeah. We now get a formatted method, an instant method. If you know format by heart, then this is not a big improvement. But imagine all the people coming into Java having a .formatted method there makes much more sense for most of them, I guess. Okay, unfortunately, all of the time, but I'm gonna stay two more of you minutes because what we're gonna do now is we're gonna see the big new features that came after Java 5, sorry, Java 15, that are not yet all I showed you now is stuff you should and could use in production if you're running on Java 15. But if you wanna look ahead, we have to unlock preview features and that's what we're gonna do now. So first of all, I'm gonna give to throw everything away that I just did. And then we're gonna check out the upgrade branch. And if you look at the history here, as I said, it's linked in the slides. You can see master is in Java 8 and then we upgrade to 9 and then to other versions. And now we've done this, text blocks and strings. Now we enable preview features and use record and sealed classes. So oops, that was the wrong purpose, there you go. So one thing that's really cool is records. Earlier I showed you these things here, right? That's luck, it was really simple. It was just like a string field and then a ton of getter, sorry, one getter and equals and hash code and two string and constructor and all of that. We don't need all of that ceremony, right? It's just a simple thing. It's basically just a string and a bit around that. An article, an article is just these couple of fields and not much more. And with records, we can now express that very succinctly and really describe if something is just a data holder. And that's important to understand. We are expressing something and we're then in exchange don't have to do the boilerplate anymore. This should not be seen as I can avoid boilerplate. This should be seen as I can express that something is a record, a data holder. That's the more important part. The no boilerplate is something we get in exchange for that. So that's one big thing. Another big thing you might have, as I mentioned earlier you might have not liked this very much. That I switch over the type here. Like, what if a new type comes along? Then this falls apart, right? Now I can express that this is no longer possible. This is the newest preview feature. It's in Java 15. I was introducing Java 15, which is this sealed. I can now express that this interface post will only ever be implemented by these three classes. And if I would have another class, like right now I move video here for going to the video class, I would see a compiler error because I can't actually implement post because it's not allowed in the sealed hierarchy. But so this way I can express within my code who is allowed to implement, or sorry, who is allowed to extend, and implement an interface or extend an abstract class. Not just abstract in general, a class. And then there's some other things, like for example, video either has to be final and records are already final. So I don't have to make it final explicitly or they have to be sealed themselves. So there's an interesting way that you can further manage that extension hierarchy, but between final class, which allows zero extensions, sorry, zero implementations, and not final, which allows arbitrary many extensions implementations. You now have sealed, which is in between. Basically you could see the old cases as special cases of sealed, where sealed permits asterisk is everybody can do that. That's an actual syntax, right? But conceptually, a non-final class is a sealed class that permits everybody to implement it. And a final class is a sealed class that permits nobody to implement it. And now you can also express something in the middle. And with that, something like this becomes, where was it? Like this becomes much more bearable. And in the future, we're gonna be able to put proper types here. Nobody knows how that's gonna look like yet, but actually, I guess a few people do, but let's assume it would be something like this. And as I mentioned, this is not actual syntax, rather it's just me making stuff up to show your concept. Or instance of. So this could be something in the future. And this works amazingly well with sealed classes because the compiler can know, oh, wait, this is a post. A post can only have these three implementations. I'm done, I don't even need a default branch. That's not there yet, but that's probably the next thing we're gonna see out of this feature set here. There was one more small thing I wanted to show you was that. Records, oh yeah, pattern matching. Right, last thing, then I'm gonna let you go. Let's go here. But one thing I wanna do here is I wanna see whether to post that I compare, whether they have a repository that's linked to them, that's an option. So it could be there, it could not be there. And I wanna give a score based on that repository. Is it there or not? So I need to get a repository out of the post. The problem is not all posts do have a repository. Articles and videos have a repository, talks don't. Articles and videos may have a repository, talks don't. So I need to do, once again, basically like something I can instance of. And what I would have done in the past, what would have been this, right? If I already devolved so far that I had to go into an instance of checking, usually do an instance of, and then you still need to cast. It's a bit annoying. And so the new way to do this, also to the preview feature is you can write instance of and then it matches, it's pattern matching and matches on the type of video. And then you get the result of that match assigned as a new variable, which is of this type. So now I get a variable called video, which is of the type video, and I can access stuff there. So that's pretty cool as well to be able to do that. I mean, once again, this feature and see it as well, none of those are like features like records. I'm sure I'm gonna use records every single day from now on. If I write a Java code base that it's new enough. We see classes and this pattern matching as far it is now, not so much, but it's still really important if you have that, if you're in the case where you need it. Or at least it's very helpful. See classes, it's important because they can do something that you couldn't do before. And this is really helpful because it's so much more comfortable to write something like this. Equals methods become really simple with this. You wanna do an instance of base equals method. It's so easy now. Okay, with every time to go through this, I invited you to look at the slides, look at the code base. I just wanna show you the final slide, which will be this one. If we look over on this entire code base and look at the stats again, we start in Java 11 with this many lines, 2060, and we dropped like 500 of them. Granted, most of those are records, but still that's a lot, right? We lost like a quarter of the code base. And I would argue the code got better for that. It's not more cryptic, more complex. It's just more expressive. It's more clearly expresses what it's doing as a side effect basically becomes less code. Runtime in this case might go up a bit, but that's mostly because I'm using like the newest features and sometimes the newest features in the newest JVMs are not as optimized as they were. If you run the Java 11 code with a JDK 15, it's the same runtime. It's a bit faster actually. So it's just like these very new, very raw features that might not have been that much performance improvement packed into them. And then there was a bunch of things, like once again, look at the slides that I couldn't show you now, like much release charts, we didn't look at those. They have GC improvements as a package or now with the Java flight recorder, data case now more freely accessible. So monitoring got better. There's so many things that Java 15 or Java after 11 makes better or improves upon. And I can only invite you if you have projects that have the chance to go beyond 11 to seriously consider it and to stay on the six months release cadence. If you have questions about that, about like one of the trade-offs in upgrading to beyond Java 11, one of migration problems, how can I use stuff like for example, the module system we now skipped over because was introduced in Java 9. I reach out to me. You can either ask me here in chat or in the Slack or I'm on Twitter. I'm at me.pavx, which is also there. You may need to hide my inbox, my actual physical inbox. But there you go. So you can find my Twitter. And thank you very much for your time. Sorry, there are two couple of minutes more. I hope you have a good rest of the conference and which is over, right? Is it over now, Ed? Do I get that right? Yes, well, your session was the last session of the track and now we have a closing ceremony. But before that, amazing content. It was a lot to process in just 30 minutes. I'm pretty sure everybody was amazed and hopefully they will be migrating to the latest Java version very soon and not Java 1.4 anymore. Yeah. Go from Java 1.4 to Java 15. It's basically just drop the decimal point. Perfect. Nikolai, it was amazing to have you. Thank you very much for attending our call. It was super nice to have you here. I hope we- Thank you for inviting me to talk in the next virtual conference because physical conferences appear to be like a distant future. And stay tuned. You posted your links here. We don't have time for questions now but I hope people will reach out to you just in case they have any doubts. And what I'd like to invite all of you if you're still watching, we have the closing ceremony. So if you go to the top of the screen, you can see the schedule. You click, we're on track number five and if you click on the next session, it's a closing ceremony with Berserter. Well, we might have something for you there. So I hope to see everybody there. Bye.