About this talk
In this talk, Ben Evans discusses the evolution of Java, focusing on how applications can be de-evolved or migrated backwards through different Java versions. He shares insights from his experience working at IBM and Red Hat, emphasizing the changes in Java's features, such as virtual threads, pattern matching, and scope values introduced in recent updates. The session includes a practical demonstration of an FX trading application, showcasing the differences in code structure and capabilities between Java 25, 21, 17, 11, and 8. The speaker highlights the challenges and nuances of migrating applications through these versions, illustrating key points with examples. He concludes with a look at Java's current usage statistics in production environments, noting trends and the adoption rates of various versions.
Full transcript
Okay, well, okay, that might spit out. There we go. I'll have to speak softly, which isn't my usual conference speaking voice, as many of you know. So, welcome to today's talk. Um, I'm Ben Evans. My My talk today is called the DEvolution of Java. Um, I had kind of an odd career, I think it's fair to say. Um, I'm currently at IBM, but I'm still actually working
exclusively on Red Hat stuff. So, although my group moved from Red Hat to IBM last year, it's like nothing's changed. Before then, I did a lot of work in observability at New Relic, and then before that JClarity, and also worked in a bunch of banks. Um, I'm a Java Champion, Rockstar Speaker. I used to sit on the Java Community Process Executive Committee, and before I moved here
to lovely Barcelona, I helped to run the London Java Community for about 10 years. Okay, I have a bunch of books out. These are my most recent books. The new edition of Java in a nutshell will be arriving this September. It's in production as of last week. And there's a bunch of additional resources on my website there if you want to take a picture. Okay, cool. So,
what are we going to talk about today? Well, we are going to de-evolve an application. We're going to take it and move it backwards in time to earlier and earlier Java versions, and just kind of see what happens, see what changes, see what breaks. And there's a few surprises. In fact, when I was writing the test application for this, and did the process of actually running it
back, which I did, the the breaking changes and the the um, most um, interesting parts weren't necessarily where I was expecting them to be. Okay, so we'll talk more about that as we as we go. But just uh, before we start, let's just do a quick exercise. I could just about see you. Um hands up if I'm going to going to list the Java versions that that
that the the highest Java version that you have running in production. 25. Hands up for 25. Oh, good. Glad to see that. 21. About half the hands go up. Great. 17. A few. 11. Yeah, you want to you want to do something about that. Definitely. >> [laughter] >> And and Java 8. Who is still any poor souls still stuck on Java 8? Oh, man. A few a
few hands go up. Okay. Anything before Java 8? No. There's only one person. My sympathies. >> Um and anybody running a non-LTS version? Even more controversial. Nobody. Uh that's pretty much always the answer. So, there we go. Um I've got some numbers to share with you at the end. So, um we'll see how accurate a representation of the room is. Okay. So, for now, we are going
to meet FX trading application. It's not a complete application, um but it is it is there. Um I've just realized that I meant to um mark the repo on GitHub as public before my talk, but I forgot to. So, and I can't get any patches submitted live cuz it's still a private repo. Never mind. Okay. So, [snorts] move my IntelliJ window over, where is Ah, here we
go. So, here we have the app. Um it's a pretty pretty simple. It's got a a a a logging facade. It's got two main order management classes, the order manager and the um the what we we call the order response manager. So, for those of you that know a bit about financial trading, the order manager is the thing that's going to talk to clients, and your clients
are going to send you orders down it, and the order response manager is going to be the thing that communicates to market. Okay? So, so far, so um so straightforward. Uh I've got a very simple little logging facade here. So, I've got just got a FX logger with just some log levels defined. Static method for creating a couple of these different types of logger. And of course,
this is a sealed interface, which permits these three different implementations that we have. pretty standard for a Java 17 and upwards uh build, I would say. Um what else have we got here that's probably worth noting? I mean, I'm using uh I better make sure the repo knows that we're back on Java 25. module settings. Come on. Oh, you're down there. Because of course, as I go
through this, I'm not only changing the Java version, but I'm actually changing the SDK as well. So, we've got to bring you all the way back up to 25. And modules, 25. Perfect. So, now all the reds disappeared, and sure enough, we do have the Java lang IO class available. Um I've been using that for a while. I kind of think that it's it's a little bit
of a cheat. Um the functionality as an experienced Java programmer, it's good if you're just bashing out some test code, but I can't see that anyone who is anything other than a complete Java beginner is really ever going to touch it. You actually do run against up against its limitations pretty hard, pretty quickly. Um but still, you know, if it answers the older Well, you can see
one of them Because if you were actually going to to do this and actually get a stack trace out, the IO class can't help you. You have to go back to using system out immediately for it. So, that's not great. Um what else could we show? Well, we should have in the config So, over here we've got our just a simple config handler. and a resources file,
which has got our properties, which we use to set ports and tick size and various other things. So, that's pretty standard boilerplate stuff. We've got the order manager itself. Uh and Why aren't you not picking up effects cancel order? Okay, I don't know why IntelliJ is refusing to pick up that class, cuz it's very clearly there. Hm, don't know. Okay, so IntelliJ is having a brain fart.
There we go. Um it does that sometimes, I don't know why. So, this is another API which you might not be aware of. This is the scoped value API. Hands up if you've heard about scoped values. Okay, only a very few people. Interesting. Hands up if you've used thread local. Keep your hand up if you like thread All the hands go down. One person wasn't quite sure.
for those of you who are using thread local, have code which has thread local in it, you want to investigate this, because this is essentially thread locals done properly. Um it's only available in final uh Java 25. So, you will have to come all the way up to the latest version, but it works really nicely. Um how do we do it? Well, in the order manager here,
this is we've got a a builder. We're taking a config. We get a server socket. And we build our our order manager. Now, in the run method, cuz this is just a runnable, we've got Actually, we find out whether the pointer works or not. I don't think it's going too very well. On these dark screens, it never Yep, no. Can't see a bloody thing with the pointer.
Um so, we have here this this part of the code. So, this is just a straightforward server-side socket that we're building. and what we're we're actually doing is we are running this on an executor's virtual thread. Something else you might also notice, which is a bit weird, is that this is in a try-with-resources block. So, that will tell us that this executor is actually auto-closeable, which it
is. ExecutorService has been refitted for to be auto-closeable precisely for this use case. Now, the reasoning behind that, we'll talk more about virtual threads in a second, but the um the executors for platform threads are considered to be expensive heavyweight objects. So, you create them once, you stash it in a field, and then you you don't have to pay the cost of additional threads when when you
need to to spin up or spin in the case of a cached thread pool, for example. Virtual threads are super super super lightweight. They're just Java objects, right? So, why would you have the same semantics or the same approach uh as treating them as though that they're expensive? Well, you wouldn't. So, you you can set this up and do this with the um the auto-closeable trick. in
two minds about whether this was a good idea or not, because I'm sure that I'm going to be debugging code where somebody has done the same trick uh but with platform thread executors. Um so I think that might come back and bite us. But anyway, that's what the API is now. Um we do this blocking accept on the server call and then once we've got a socket,
we chuck it into the executor. So this triggers the creation of a new virtual thread. And here is where the scoped value comes in. What's happening is that it takes a lambda, so it's going to run this runnable, and it's binding the value socket SV to the current socket. So far, so good. By the way, if the font isn't big enough, let me know and I will
in enhance it. So what we now do is we get the socket back in this method here. So we have socket SV.get up here on line 75. Um so this wasn't passed as a parameter. It's been magically taken out of the context. And that is kind of the other way to think about this. This is something which hasn't existed in Java before. Has existed in a bunch
of other languages. It's existed in lisps, it's existed in shells, it's existed in Perl. It's called a dynamic scope. And until now Java had no JDK level way of representing this. Um the other thing which you could compare it to um but I'll probably get um heckles from from any Scala programmers in the audience is Scala implicits. It's much much less powerful than Scala implicits, uh but
it's a very Java-ish way of doing something to do with dynamic scoping. I have to say just using it for this is kind of not that impressive because what we're doing is we're stashing something into a into the scope just to pull it out again, and I could have just modified the function prototype here, but hey ho. Okay, and then we do some stuff. We we decode
and get an uh valid order if one exists, and if it's present, we then send it to market. And all of the the market side stuff is all mocked mocked just by using some blocking queues in an ordinary sort of way. Okay, so that's our um Now, let's have a look at the response Some reason it doesn't like effects accepted either. Right, so what's what's happening here?
Well, it's it's modestly similar. Um we don't actually use the config, but I've left it in there anyway. Um we have separate queues. Um the general incoming response queue, and then a queue of fills, canceled, and rejects. So, we're essentially fanning these out in different ways. Um the reason why you do it this way is it's actually a trick from a real life um order management system.
You don't want to be in a position where um a bad actor who's one of your customers has um sent a load of orders which are malformed. They they they they're well-formed enough to pass your your order validation, but the market is just immediately going to reject them. Um the way the reason you do that is so that you can flood you can send your market orders
to market, so they sit on the market open, and then you send a load of things to reject, so that none of your competitors can get an order in. so you do want to find these out so that they're handled by different queues and different thread pools. It's a real thing. It comes from a real uh order Buy me a drink later and ask me how I
Okay. So, down here we also have some some cool stuff. We've got the uh switch the the which has been upgraded to switch expressions and patterns. just uh some simple switching by type We've got the unused uh variable. So, the underscore from Java 25 and upwards uh I think came in in 22. It is is just there to to reject or to say I don't care about
this parameter. Stop talking to me about it. I mean, you can just bind it to to something you don't use, but the underscore has now has a very very clear intent. Okay, what else have we got? So, we have a couple of order responses. So, the order response type and then we have two different orders. Everything has units, currency pair, side, and date and time. And then
a limit order has a price that you need to hit and how long you want the order to remain in the market for. Um a market order says, "I don't care about that. Just give me what you can." Um but it will typically only happen at whatever the best price is. Um all or nothing says, "If you can't fill my order in one go, discard it." So,
they're just different types of order which you you see in in real trading. Um okay, so that's basically the the application. So, if I come back, I still can't quite figure out where the screen's located. I think it's to the to the left. Come on, where are you? No, Oh, down. It's up. Okay. Right. So, go back in time. Um let's start by going back from Java
25 to Java 17. What are we going to lose? We're going to lose pattern matching. We're going to lose the scope values. We're going to use lose virtual threads. pattern matching we were just just talking about. So you have these patterns with switch cases. You also have the ability to do um uh pattern guards, as they're called, which means you still not show up on the board,
but you can have a case if you're trying to figure out whether your pet is a dog or not. And if it's a dog it will say true, if it's a cat it will say yes, it will say no. Except on Mondays, when it will just lie to you, because it is after all a cat. record patterns also can be can be deconstructed. So you can you
can extract things safely. So you can you can do this type of things. If you've done JavaScript, you might have seen or Python, people talk about destructuring. Um this is a very similar Java-ish way of doing it. This is not its final form. More stuff is coming um in later releases of Java, but this is just where we've got to now in terms of the features that
are actually released. Um you can also extend stuff by mixing it with the the sealed interface. So you can do then this type of stuff. Um you also have the case null and the case default. Uh one of the slightly rough edges about switches is that switches will throw null pointer exception if you pass it a null and don't have a case null. Also of note, the
reason why these two are both commented out, default does not match case null. If you have a default in a switch expression and you pass it a null, you will get a null pointer exception. So be careful of the the unnamed patterns and variables work well. You can also do this type of You know, cats always insist on being addressed as sir plus their name. And if
you've got a dog and a cat, it will be the two of them and the dog answer to its its name. Uh no primitive patterns as yet. Um this is another building block. I I'm only really talking about final features in this part of the talk. Um primitive patterns are not there yet. The The system is not single rooted. And as you may well guess, this is
tied up deeply with what's happening with Valhalla. Oh, by the way, did anyone hear the news? We have official confirmation from Oracle that Java 28 will contain the first pieces of Valhalla in preview. For those of you who thought it was never going to land, which I was sometimes one, um this is some pretty big news. What that means is that in a well, probably quite soon,
I think once once the Java 28 betas officially branch from mainline, um which is coming up soon, they will land the patches there. That's likely to take a while, but we should start to see Java Java 28 uh betas with initial Valhalla uh included certainly by the the autumn. Scope values, we talked about these. Um I think I said everything here. The fact that they're immutable but
with the ability to rebind them in a subscope a virtual thread you spawn from a thread, um mean makes them much more useful and much less dangerous than thread locals. So, if you have thread locals, here's where you want to um move away from them if you can. So, this is just the pattern you see quite a lot is a public static final field of the scoped
value so that it can be accessed from any piece of code. Um but then the contents of it are kind of like a completable future, immutable once it's been set. So, that provides you with the same safety guarantees, provided you haven't done anything really weird, as a final field. So, that's I think probably the best uh best practice. Java 21, this is the virtual threads. This is
This is sort of the the the the basics of how you build you you when you have a a thread which is is runnable, um, so it's sat in the run queue, the OS scheduler is going to move it onto the running core, you're going to do some work here, and then one of a number of things will happen. It can be the case that you that
you just use your entire time slice in uh user mode, so you don't do any kernel work, you don't do anything else, you're just doing some calculations, uh and then you get moved back into the ready-to-run state. That's That's fine. You would be surprised how rarely that is actually the case. In most cases, most bits of code do not make it all the way through their time
slice before they have to either sleep or wait, so they're removed from the core in that way, or they do something which involves the operating system stepping in. The typical example is network IO. Okay? Now, because of that, what was done within Java to prepare for Loom within inside the JDK itself was to rewrite the internals of the socket and the other APIs that could block. So,
that all of that IO under the hood is now non-blocking. Even the stuff which uses a blocking Your API is blocking, and your thread blocks, but the kernel is using non-blocking uh IO. So, from there, you see it's then a matter of okay, well, when a virtual thread, not a platform thread, it blocks in the usual way. When a virtual thread hits that code, it unmounts itself.
It dispatches the IO call, the kernel gets on with it in the background, and another virtual thread can step in and occupy the rest of that time slice. That's the mechanism. It relies upon having previously rewritten the implementation code for sockets. Okay. Um So, Java 21 said, "What if there was a new type of thread?" And that was the stepping stone. It was done all the way
back in Java 13. So, even the folks on Java 17, you're already using the non-blocking implementation of socket, even if you didn't know it. Which I hope will answer the question which sometimes come before that of what are the performance impacts? Well, you tell me. Were there any performance impacts? Did you notice that that that socket had been rewritten under the hood? Of course not. Did you
notice that reflection has been rewritten completely And the old reflection cut pathways are also gone? No, you didn't notice that either. it's important that to to recognize that sometimes the performance discussions can get derailed. People can fixate on a small thing when actually major root-and-branch changes have gone on and not a murmur from anyone. Okay. So, uh is there anything else here? Yeah, I will just also
say virtual threads are probably most similar to go routines. They were decided as the way forward. Other alternatives were considered, specifically async await, uh well, colored functions, or an internal reactive uh API. All of that was was kind of ignored. There are continuations. So, if you wanted to think about continuations and coroutines, yes, they are there, but they're internal and you can't access So, there is still
some debate within the Loom folks as to whether or not user-accessible continuations are a good idea. we'll see. Okay, so time to do Java 17 to 11. So, let's uh So, let's switch to Java 17. And swap it in the language level as And now we can see we still have records and sealed types. So, not much has changed here. Uh obviously the order manager obviously our
IOs have now changed back to system.out. Um we don't have our nice switch expression anymore, so we just have to do this horrible if-else instanceof, but we still have the type patterns for instanceof. Um but that's kind of a residual. It's not really a a great deal left uh at this point. Let's have a look at the main order manager. Where are you? And you see sure
enough at this point I've actually had to introduce an executor service. So, I've replaced my virtual thread executor with a cached thread pool and I'm initializing it in the uh in the the constructor here. Other than that, things look slightly different. I've had to change the the the signature here because I no longer have a dynamic scope to use, so I have to explicitly pass the socket
as a parameter. Apart from that, the code hasn't actually changed all that much. So, what are we going to lose now? Well, we're going to go further back in time. We're going to go from Java 17 to Java We're going to lose text blocks. We're going to use algebraic data types. We're going to lose those last remnants of And this was the one that surprised me. The
the the drop back from 17 to 11 is where there was the most grief and the most pain, not just in terms of typing, but also in the sense of how much expressive power in the language we were losing. Um so, for those poor souls who are still stuck on Java 11, you really want to get up to 17. Um the the the move from uh 11
to 17 is less painful than 8 to 11 was. Um 17 to 31 or 25, it's less painful still. Bit by bit, the migrations between LTSs are getting easier and easier. And not only are there all these lovely language features, there is of course the question about security and also the question about performance. The JVM gets faster and faster with each version. So, yeah. So, text blocks,
those of you who don't know, they're just string literals that extend over multiple lines. They do not support interpolation. String templates was contemplated and there were a couple of previews of it, but eventually it got withdrawn. It doesn't quite work in the form that it was originally proposed. It's being rethought and reworked, which I Some people are cross about. They wanted to have, you know, [snorts] the
version that was that was proposed. But, I I have to say that actually I think withdrawing it was the right idea. So, the sealed types, which is X is a type of A or B records, are what I like to call the just carries fields pattern. So, it's an approach to two pools. Um You can also have named two pools, I think, in Python these days, but
the Java ones are better. And there is a very nice migration path where you can start with something as a record and then iterate on it and turn it into a full class. What is also interesting is that in some of the work that that Brian and the other folks at Oracle are doing um is they're now fairly convinced that records and classes are not the only
parts of the continuum that um that that the the the Java object model should support. I think I've said that right. Um Cuz at the moment, there is sort of a bit of a cliff. You either have the full-on record semantics where everything's immutable, all the fields are visible, there's no composite state, you can't build any composite state on construction. You have to have all of that
in order to proper record. And if you fail even the the smallest, tiniest bit of that, well, you lose everything and you're straight to a class. So, what if that wasn't the only way to do it? What if there was something, or perhaps several somethings, in between the the semantic step of record versus class? So, that that discussion is still, I think, incubating. Um I think one
of the names that Brian's thrown around is carrier classes. Um but, this may not be the final word, I think is what I want to say here. So, we now have we have these these pattern remnants. You You saw this in the code. Our switch statement has uh Oh, and apparently this is truncated to Olin. That's cool. Uh the switch expression is still exists as well, but
it's not married up to patterns. This Java 1 0 switch is statement only and it was retrofitted to allow the expression form, introduced the yield key. Although the yield key is is present, the arrow form for the expressions is much more common. So, [snorts] you can use the colon just as you do for the statement form and provide a yield, but in practice everybody just uses the
arrow. There's no default fall through for switch expressions and the expression must be total. So, every possibility must match. Um and if not, you need a case default. wind that back. Let's go back from 17 to Oh, and just to prove that I'm actually doing this, sure enough we're now building against version 11 in the palm file. Okay, now things look somewhat different because of course we
don't have any ceiling anymore. So now our FX order interface is can just be implemented by anyone. We've got no control over it. We could have some malicious order type that somehow gets injected. Our our domain model is not total anymore. Um our limit orders have to be classes rather than records and look at all this stuff that we got for free just by writing Um and
of course now of course it's not safe either because if I add a field well, if I don't remember I need to add a getter and if I don't remember to update it equals hash code to string are all wrong. records do have not only the design element to them, the actual ergonomics of how you use them in practice is superior. Um some people really don't like
being forced into that world where everything is immutable and I I get that. But it is good practice. So for at least some of the time I would highly recommend that we you do that. Um the main method now we have a default banner with some dollar ends in it instead of our text block. ooh. And of course I've actually forgotten to change this because I was
also using um instance main methods here. And this is still valid Java. But it won't run and as you can see IntelliJ doesn't recognize it as a runnable class because it isn't. It doesn't have an a version 11 semantics um expressible main method. we still have optional so we've still got the config part still works. Uh Uh the order manager was basically unchanged. And the order response
manager, because we're just using the the instance of, we it's the same code, but now we have to include the explicit casts because that instance of pattern is is just gone as So, let's go back further. Java 11 to 8. What do we lose here? It's going to be the LVTI, local variable type inference, also known as var, uh and encapsulated internals, i.e. modules. So, var is
just a magic type name. It's not really a keyword. implemented solely in in javac. So, there's no runtime or performance effect of var at all. Um I did have someone who was very confused trying to tell me that there was a performance impact for using var. There absolutely is not. It's not even visible in bytecode. It's gone but long before runtime. Um for type resolution, it uses
some constraint solving, so just as you do for lambdas. So, when the lambda is is written, it tries to figure out the type on the left is this. Has it got an abstract method? Yes. Does this method signature match that? And it it does, you know, effectively a a solution of constraints in order to do that. Um this means that you may not something you may not
know, the um the type resolution for lambdas uh and LVTI, they agree, but on earlier versions of Java, there are corner cases where methods won't link properly. So, when I was backporting, I found a couple of my methods, when I went back from 8 to 7, suddenly wouldn't link anymore. And it's to do with the fact that the the the type resolution uh logic changed between between
8 and 7. Okay, there's the style guide. If you haven't read the style guide, do. It's good coverage of how we use var. Oh, and of course, because var is just a type name, you can quite legitimately a variable called var. Um but please don't. That's not a good And finally, modules, which I'm going to explain through the lens of encapsulation, encapsulation of the of the internals.
Uh and to do this, I'm actually going to go forwards in time for a little bit, and then we'll we'll we'll go backwards. We've got a TARDIS, we can we can do that, backwards, forwards, doesn't matter. So, in Java 8, you can call public methods on any public class you like. Is the class public? Yep. Is the method pub- public? Yep. You can call it. Um if
it's in internals, in one of the sun packages, for example, um you may get a uh warning, but that's pretty much it. And you can do both directly and reflectively. Okay, in Java 9, huh, you can call any public class you like, both directly and reflectively, provided that if you call them directly, it's in an exported package of a module. Or if it's been put into the
automatic module, I it's on the class path, you get the the same semantics as before. So, we summarize that as subject to additional restrictions from modules. Okay, so what happens when you go from 8 to 11? The warnings have become errors. Certain calls to internal code will no longer compile. Um things changed, most people didn't notice because essentially the only people that would ever be impacted by
this are things that broke the rules. If you were doing things that you weren't supposed to be doing and grabbing around in the internals, you would get caught by this. But that compiler change will catch you. So, reflection is the same story as Java and then we come to Java 16. So, you can call any methods you like both directly and reflectively, but now there are restrictions
on reflective access as well. And the reflective access is actually implemented in such a clever way that you cannot disable it. Um it has a hard list of excluded things which are invisible and filtered from And that includes the reflection protection subsystem. it's quite neat. I I was wondering how they were going to solve that problem which looks kind of circular. Um but that's that's how it's
done. So, here's a like a summary of public methods. Um if you were on 8, do what the hell you like. If you were on 11, do what the hell you like reflectively, but you might get compilation errors when you compile. With 16, both direct and reflective are subject to constraints. >> [snorts] >> For 16 only, that's the only version for which this works, you can re-enable
direct access to everything reflectively with a switch. But from 17 onwards, you can't. that's that's the reflection story. And then of course, you know, we should measure mention unsafe. Unsafe always only able to be accessed reflectively. In 9 plus, it moved to JDK un-supported, which is visible by default, but the the the the the the trick of using it moving it to it's effectively a proxy to
a real internal implementation which is largely Um and the reason for doing that is so that you can take methods off the proxy one at a time to make them not available even to to evil library authors. Um but while still allowing it um access within the JDK itself. it's not a smooth migration, and now we come back to Roman times. Java 8, Java 7. Oh, we
lose a lot here, and I have to skip over certain things. I have to skip over Java time and optional and completable futures because there's just not enough time. There's so much to talk about here. So, let's see what happens if I go down And again, the the order response and unchanged. The only real thing that's noticeable here is that we have to we have to stop
saying var. So, there's virtually no changes here. The really big changes come when we step from 8 to 7. Where is my cursor? So, what are we going to lose? Well, we're going to lose lambdas and streams and all sorts of So, lambdas are creating new object of unknown type. It has a compact syntax, but only for declaration. Um lots of people have never noticed this. That
in Java you were required to say f.apply of x rather than just f of x. Lots and lots of other languages would have preferred it to just be f of x here. Um and there's a whole deep discussion about design philosophy there, which I'm not going to go into. they're not implemented as inner classes. Hands up if you thought lambdas were implemented as inner classes. Nobody. Oh,
good. That message finally got out. Have a look at your compiled files on disk. You will see that there are no secondary class files and instead invoke dynamic is what's used. Hand in hand with lambdas go streams. This is because the collections are mutable and fully materialized. This is actually one of the major difficulties about evolving the Java language. It's just the fact that the collections interfaces
are so prevalent and they are frozen. And they contain methods which are clearly designed to mutate. Um you can't really implement a a good Java collections compatible set of data structures which are immutable. Because on a mutation, you would have to throw. Because at a typical functional collection, you would return a modified version to simulate a mutation. But that's not what the signatures of the add and
remove and so forth methods look So, Java has no traversable concept and this this one I actually find a little bit puzzling. I don't really know why we don't have a traversable interface. Um the idea being that it would be more general than iterable. Um Scala has one. I can see I can see the value of it there. I don't know why we uh why we don't
have it. Um streams in general are to a limited degree. You can think of them as func- functional containers. Um with in heavily inverted commas, the functional programmers know what I'm talking about here. Um streams are not fully that thing, but they are not far off in some ways. Uh oh, one important thing, streams are ephemeral and consumable. They are not stable objects. They are not intended
to be long-lived. Do not pass them around. If you find yourself passing a stream to another method, that's pretty much always a code smell. They're they're to be created and used on the fly. Yeah. So, then then we we also have methods, which is to maintain backwards compatibility. Um this also has the interesting point that they weren't really designed this way, but um they can be thought
of as uh a stateless traits. Static methods on interfaces, there was no real reason why any of this was was needed. You could have added static methods in interfaces as far back as Java 1.0. But the the philosophy was interfaces are kind of like header files. They don't contain code. They're not there to be to be um used as anything other than than API description. What that
leads to is this sort of companion class or plurals class with help methods, and it's just a bit ugly. Okay. So, now now comes the big step. Let's go down to Java 7. Well, I have to leave it at on the SDK because it's starting to get harder and harder to actually find something to um go with. Okay. Now, See, and even the IDE is trying to
helpfully convert my runnables back into lambdas. Um thanks, IntelliJ. And then if you look at our main, so those those are our runnables themselves, and then where is the utils? >> Now these have to turn into uh final classes as well. Um so yeah, so all our lambdas are gone. Everything's Everything's uh not great. Oh, we also have lost our ability to deal with um optional. So
everything now has to be um handled with nulls. So for example, we now have to deal with um if something's null, we we we have to pass nulls around in the interface. And the functional forms of if there's a value present, then do something on it are all gone as well. Okay. So I've got about 5 minutes. Nearly out. So now, if we have to go any
further, we really are back into the into the stone age. Try with resources, and I owe you and we would also lose invoke dynamic and fork and join. it was fun going this far back because I I remembered all of the the the work that was done to handle resource cleanup The original Java 6 code was wrong 60% of the time. So people at Sun who were
writing the the collections libraries had it wrong and there were resource leaks and all kinds of nasty bugs in 60% of cases even in the JDK. So this is just a classic case where the computer should be doing the bookkeeping, right? Humans are not good at this. We should We should let the computer do it. And it's a very obvious case that has just become so much
part of the furniture that we don't even miss it Um paths and files were huge step The The file class misses basic functionality. there there are so many things about it. You can't read the contents of a file directly. You have to introduce the stream of the IO stream abstraction to get to it. You abstract over files and directories because there's not really a difference between them.
And this platform independence and this massive appeal to the general case you know, prioritized over usability. This is a design bet that has not aged Um and we can go further back um with some apologies to uh evolutionary biologists. you know, generics. You get back past generics and you're into the mystery meat collections, also known as heterogeneous collections, but I think mystery meat is a better name.
The first real proper job I had writing a lot of Java professionally, I was writing pre-generics Java. The the the the upgrade to Java 1.5 came partway through my first or second year Um but here, it's we run into the problem that it's just very difficult to get anything to build on these versions at all. you can get OpenJDK 6 kind of, but everything before that is
technically not uh open source. Um if you really want to see how it's done, this piece from Chen Guard is actually showing how you bootstrap up. Essentially, what they do is they cobble together version which more or less implements Java 5 um enough so that you can actually run the build process for OpenJDK 6 with your cobbled-together Heath Robinson JDK Um and then from there, you bootstrap
6 to 7, patch that some more, and keep going until you get to a point where actually the modern uh tool chain will step up. Um it's it's a a lovely piece of work, but I'm very glad we don't have to do it. So, we're almost at the end. So, I just want to to show you these numbers. I can't share with you the sample size But,
these are genuine Java versions in production. This is the These are Red Hat numbers. So, this is what we see customers actually running. So, as you can see, the 17 is is the big piece. That's nearly 50%. It's nearly the majority. Um we have we see 11 still, but it's going down. And actually, perhaps I'm not surprised by this. But, the eight segment is hanging around for
longer than 11. 11 is actually the fastest shrinking. Um so, I think we we will see that um shrink to almost nothing in the in the coming year. Uh what I am also surprised by is that Java 25 is not more widely adopted yet. We are almost halfway through its uh its lifespan as being the the most up-to-date LTS. Um but, we don't we don't see huge
usage of it yet. But, still the numbers for Java 21 is just a shade under a quarter. So, 17 and 21 being almost three quarters of the map, I think I'm pretty happy with that. Okay. So, I'm going to skip my section. I've got 30 seconds for questions. >> [applause]