Engineering
March 23, 2022
15 min read

How Spring Changed Java Application Development

Viktor Gamov

In this Kongcast episode, Josh Long, Spring Developer Advocate at VMware, dives into how Spring changed the way developers build Java applications and introduces you to Spring Native.

Check out the transcript and video from our conversation below, and be sure to subscribe to get email alerts for the latest new episodes.

Viktor Gamov: For those of you who don’t know you as I do, can you briefly introduce yourself and what you do and what drives you?

Josh Long: My name is Josh Long. I work on the Spring team. I’m a Developer Advocate, a Google Kotlin Developer Expert and a Java Champion, and I spend my time trying to help people build better software intended for production in terms of Spring and the Spring ecosystem. I’ve been doing that in my capacity as the first Spring Developer Advocate since 2010.

Viktor Gamov: Yeah, 2010. I saw your first presentation. You did this presentation in 2011 in New York, it was at the MongoDB office and you were talking about the Spring Data project. I remember that.

History of Spring

Viktor Gamov: That was a great time in Spring because Spring and the microservices world exploded around 2014/2015.

Josh Long: I would say around then. Yeah and Spring Boot was super well positioned to tap into that zeitgeist. The wave of innovation and excitement around agility and getting to production more quickly.

Viktor Gamov: I guess it was kind of the perfect storm. People were looking for tools that allowed them to break up this monolithic application to build services. At the same time, many other things happened. Heroku released their 12 Factor App Manifesto, a very famous one. And the folks at Pivotal at the time started working on Spring Boot. Some of the listeners here are not that familiar with the Java ecosystem. Give us a rundown of why this was exciting and how Spring actually changed the way we develop apps in the Java world.

Josh Long: There was SpringSource, then in 2009 VMware bought SpringSource. Then in 2013, Spring and Tomcat and RabbitMQ and Cloud Foundry and GemFire and Redis and all these sort of developer-facing bits that were at VMware – we took all that stuff and turned it into a new company called Pivotal. And I was part of that first wave. I was there on day zero, the very first day we opened our doors at Pivotal. We created Pivotal and that’s when we created Spring Boot, when Cloud Foundry got big, owing a huge debt of gratitude to the Heroku team who inspired fleets of innovation, waves of innovation that would still ripple today. It’s funny you mention that people may not know because again, you’re right, that’s 2013. We’re closer to a decade since Spring Boot's first 0.4 public release.

But Spring has been lucky. Spring is one of those very few projects that has been white-hot popular two different times in its life. Imagine if Struts from 2000 came back today and it was like the most popular thing again. That’s what happened with Spring. Spring came out the first lines of code in 2000 or 2001 that was created by a gentleman named Rob Johnson who wrote a book. He wrote a book about it that inspired others and the code in the book became an open source project that then became Spring. That then inspired other people, including Juergen Haller, who is still with the Spring team. He’s still the lead of Spring Fabric today. It inspired countless other people to hope for a better way to build software.

Microservices, Kubernetes, and Spring

Viktor Gamov: Before that, it was just big application servers, basically a representation of this monolithic application that we’re trying to fight. Spring paved the way and everyone has followed. The many specifications that happened after the emergence of Spring Boot, and even Spring itself as a framework, were in fact affected by the ideas that Spring provided: lightweight, not super slow. People still think Java is slow. And we’ll talk about this, and how this changed with the recent release of Spring Native.

We’re going to talk about this in many details, but I want to try to kind of create this kind of vision so people will understand that it’s actually innovation that is happening and the platform is not stagnating, it’s actually evolving. And in microservices, the cloud, and serverless technologies, developers want to be agile, they want to be delivering the features, developing, delivering their stuff to their users. They want to be fast. They don’t want to wait. They don’t want to wait until everything is running or starting up or things like that. Things around Kubernetes and things around Heroku and Cloud Foundry. Cloud Foundry received a lot of inspiration from many worlds. I'm super fascinated by how it has evolved into something that can be very ambiguous, right? You can run this pretty much everywhere, if you want to run this on top of Kubernetes, you can. Or you can use Cloud Foundry as the foundation to run Kubernetes and things like that.

Josh Long: Right, so it’s the other way around. Today, you would run Cloud Foundry on top of Kubernetes. Twist of fate, right? Tldr: Pivotal eventually got merged back into VMware after we went public and all that. I don’t know what the motivation was, but it seemed like they were really, really interested in becoming this developer-first company. It was a very cool thing for them to do. The companies of Pivotal and Heptio – Heptio was a Kubernetes company and it’s because of that, by the way, that we have two of the three founders of Kubernetes working at VMware – are all part of this division called Tanzu. We have a Kubernetes distribution, one of the best in the business. Cloud Foundry now sits on top of Kubernetes, if you want it to, because Cloud Foundry is an opinionated approach to building software. It’s got guardrails, but if you use those guardrails, it’s super fast. It’s like Heroku in that respect. But still, something needs to manage the infrastructure and nothing in the world is better at that than Kubernetes. It's an interesting mix. We’ve got a Kubernetes distribution, we help people build scalable, operational, usable infrastructure and then we help them build apps that do well in that context with Spring.

Viktor Gamov: Spring influenced some of the lighter versions of everything from day one. If we talk about Tomcat, it was a lighter version of this gigantic application server like the WebSpheres of the world. The way that Spring, Tomcat, and some of the dependency injection that Spring brought up, changed the way people want to develop their apps. Spring Boot changed the way they want to develop apps in the modern world. You want to have a faster turnaround, you don't want to do things like recompile. That’s why the Spring toolkit that comes with Spring Boot allows you to do this. You can change the code without doing restarts or, my personal favorite part, integrating with third party services in a very opinionated way. Think: Spring and Kafka. My personal favorite part of Spring projects. I use this a lot, I taught this a lot, and explain to people how this stuff works and gives us some of the opinionated ways to connect to the services plus use the services in the Spring opinionated way.

What is the new and exciting thing that just happened a couple weeks ago?

Overview of Spring Native

Josh Long: The best part about Spring is that we package up good ideas and good patterns into a way that’s approachable for the community. As much as I love Spring Boot and I think it’s the coolest, nobody on the Spring Boot team would tell you that Spring Boot created everything by itself, right? Of course not. That’s just not the case. For example, we drew a lot of inspiration from Dropwizard, which had a lot of these great ideas before Spring Boot. We even used Dropwizard metrics in the first version of Spring, the 1.X Line. I think that’s what’s really great about Spring, it's always been that we try and fill in the gaps if they exist. But if there’s no gaps, then we just use and integrate the best of breed ideas and technologies and all that. You can use Spring and all these other things. That’s what makes Spring so durable, it works well with these other things, like you just mentioned, the Spring for Apache Kafka Project.

One of the things we've seen people trying to make work lately is Java on GraalVM. GraalVM is an ahead-of-time compiler. It’s also a just-in-time compiler. GraalVM is a piece of infrastructure that replaces HotSpot. The story that I heard was that at some point they wanted to replace the just-in-time compiler, HotSpot, which is built into OpenJDK, the open source project. That code is apparently a tangle of spaghetti C++ code that is hard to maintain. They wanted to replace that with something that would be easier to understand and faster and all that, so they created GraalVM, which is an OpenJDK. It’s exactly the same except for there’s a new just-in-time compiler. Instead of HotSpot, it's the GraalVM just-in-time compiler. A just-in-time compiler is a piece of secret sauce that makes Java so amazing, right? If you’ve ever run Java in production, you probably know that you can deploy an application and as it gets more traffic, as certain code paths are executed enough times, they get turned into native architecture-specific binary code.

When x86 Windows or x86 Mac or whatever specific binary code, for certain code paths, you actually get native performance while you’re running an interpreted language, right? That plus the fact that Java has a whole world view of the memory, you’re not constantly having little bursts of memory acquisition and destruction because you don’t have to do your own garbage collection. Because Java has this whole world view of the memory, it can clean up memory at the right time so that it doesn’t cause disruption to the work that you’re doing. Java programs can actually be much faster than an equivalent C or C++ program, even a well written one, just because of the benefits that we get from using this. My impression is we’ve got this amazing just-in-time compiler, this native machinery, and I think somebody just said, "hey, well, if we’ve got that, wouldn’t it be nice if we just compile the whole application into a native binary."

Viktor Gamov: Exactly, why do we need to have intermediate code? Why do we need to have a Java byte code and why do we have a JVM if we’re so smart with compilers. Why not just that and instead of just-in-time can we have ahead-of-time?

Josh Long: Yeah, exactly. In the nineties when people talked about Java, you could write systems, not operating systems, but you could write big infrastructure and software that ran across all different operating systems, which is amazing. It is still amazing. That wasn’t very common. The fact that you can have this nice, scalable, statically-typed language that worked on all languages was great because people were developing applets and didn’t know what browser they were going to be using or what operating system. But today, all my cloud infrastructure is Linux in a container. It's almost 99%, right? It’s the very large majority of all infrastructure and production – Linux inside of a container. While I want to run and develop on any operating system of choice, eventually I want to make sure it runs perfectly well in Linux in a container.

Viktor Gamov: Maybe in a specific version of Linux that has certain libraries and where we want small containers to run only required stuff because storage might be expensive or network bandwidth to move these images back and forth would be expensive. This is where we're going into an interesting situation where we still have a container and Linux, but on top of Linux we still need to layer Java.

Josh Long: It’s not cheap. Don’t get me wrong, Java is still amazingly fast at runtime and the memory footprint is a lot more compared to some other languages, but it’s not as bad as some other languages still. If you’re comparing it against C or Go or Rust, then yeah, of course your C or Go or Rust program is going to have a much smaller memory footprint. But, try comparing it against Node or Python or something. You’ll find those things take up a lot of RAM too. If you’re not trying to eke out all that extra RAM, then maybe you don’t care. If you’re not doing Lambda, maybe you don’t care. There’s a lot of people who are just happy with the existing state of affairs. And that’s great because the JRE, Java, the runtime gets better and better for free every six months. It’s like Moore’s Law still exists for Java developers. It gets so good, if you take an application written today and, and start it up on a different JRE you’ll see it’s just a night and day difference.

Viktor Gamov: One of the examples that I like to bring up when we were in transition to Java 8, Java 11 and made Java more container friendly, is that Java can understand now that it runs containers and it can understand the limits that containers enforce. It will not just start grabbing things and saying, "oh, I’m dying because there’s not enough resources." Or that the information that I get from my telemetry is not equal to reality. In this case, it will try to figure out, "do I have enough memory to start this?" "I don’t. So I will fail." Another thing that you said, something that you’re getting for free, is around security. This includes TLS and support for the better version of TLS and improving the encryption. It all comes for free. So people who were using the tools like Kafka, for example, and they use TLS, by upgrading to new versions of Java they get these improvements for free.

Josh Long: For free! Thank you Java team. Thank you Oracle. I think it’s still the number two or number one largest community out there. There's a lot of people who have good jobs that put food on the table because Oracle does this amazing thing and they release it for us for free and open source even. It’s even crazier. It’s wonderful. That said, there are still some use cases that might be better served by having native binaries, right? In particular: production.

Production, Scalability, and Serverless Use Cases

Viktor Gamov: Or serverless.

Josh Long: Serverless is a huge use case there or if you had scale. So people are keen on GraalVM so a couple of years ago, at the end of 2019, we started working on what could we do to make Spring's huge ecosystem work well in GraalVM-native image context. And by the way, from here on down, if I say GraalVM, just assume I’m talking about native images and not the JRE with the HotSpot replacement. The question was: "what can we do to make it work well in a native image context or GraalVM context?" This is only a question because GraalVM-native images don’t "just work". They’re not automatic. The problem, and I say this with air quotes here, the problem here is that Java applications can do all sorts of amazing things that GraalVM-native images just can’t yet. For example, in a Java application, it’s possible to type out the definition of a class file in a Java lang string. You have literally a class definition in a string. You can take that string, compile it into a .class file on the disk, load that class file into the class loader, reflectively create a new instance with class.forname and then create the new call, the new constructor passing the arguments.

You can then invoke methods on that class. You can then serialize that class if you want. You can then write that class to disk or you can create an invocation handler, a Java proxy, using that type that you just created all from that string that you started with. You can do all of that without ever having a concrete type. You’re dealing with Java.lang.Object the entire time, right? There’s no concrete type to which you can cast that resulting thing. And this is all happening after the application has started up. The problem here is that Java is a very dynamic language. Sometimes we like to complain and say it feels like an old language, but it’s really very dynamic, just like Python and JavaScript and Ruby and all that. You can do eval very easily in Java. It’s not as easy as eval, but I just described things that you can all totally do within a Java program while it’s running, after it’s been compiled and started. And because of that, the GraalVM approach, the GraalVM-native image compiler, what it does is it looks at the code at compile time and tries to figure out what types are reachable. That is to say, what types it knows are being used by something else. But it can only see the types that are reachable at compile time, which means that all this other stuff that we’re doing at runtime, like the reflection and proxies and serialization and loading resources from JARs and you know, all this stuff, it doesn’t know about it, it doesn’t understand it.

This is a big problem because so much of the Java ecosystem needs this stuff. It works on this stuff. It’s what makes Java so dynamic and powerful. It’s a reason this runtime is one of the beautiful things about Java. And so the GraalVM team, they’re very pragmatic. They said, okay, fine, if you give us some config files that tell us when you’re going to create proxies, when you’re going to do reflection, when you’re going to do all these things, we will make it seem like it works well. Basically they build in a little shim into the heap of the native image, so that when you say, "give me a class file" and you call get declared methods, it doesn’t just return null. It’s a pre-computed sort of reflection API that only works for the types that you tell GraalVM about. If there’s a way, then there’s definitely a will, right? So now that there’s a way, what we did was build Spring Native. Spring Native is a compiler framework. It’s a framework that runs at compile time. It knows about all the patterns that typical Spring applications follow.

Viktor Gamov: And all the things that you just described, all the things around the reflection and how dependency injection works in the runtime, all these nice, neat tricks need to be documented somewhere in order for the compiler to understand, right?

Josh Long: Right and so we don’t want you to have to write thousands of lines of JSON config files to tell it about every little place. That’s part one. We can make existing standard old Spring apps work on GraalVM using Spring Native and that’s been possible for more than a year now. What just happened recently, which you just alluded to a few minutes ago, is we released Spring Native 0.11, which includes a new AOT engine, an enhanced AOT engine. We already had the AOT engine six months to a year ago, but this new part is very, very powerful. This new part is we actually take your Java configuration classes and we transpile it into functional configuration. So now, that whole discussion of which types are reachable at runtime, well, we don’t have to provide the configuration so that spring can reflect on these Java configuration classes because there are no configuration classes anymore at compile time. We turn it all into functional config, which is to say we programmatically register beans in the application context for you and that’s how reachable that code is. You can command click your way all the way through the graph and see what is happening and where. And because of that the compiled binary takes much less RAM and much less time to start up. And the compile time itself has gone down from, let’s say, about 10 minutes in early 2020 to about a minute and a half now or maybe even a minute, depending on what you’re doing. I’ve had builds that take 50 seconds, so can I show you some of that?

Demo: Spring Native

Thanks for Joining Us!

I hope you'll join us again on April 4 for our next Kongcast episode with Henrik Blixt from Intuit.

Until then, be sure to subscribe to Kongcast to get episodes sent to your inbox (and a chance to win cool swag)!