- Hey everybody, Phil Webb
here from the Spring Boot team. Ordinarily, when we
release a bit of software, we get the opportunity
to come to conferences and tell you all about it. Unfortunately, with the global
COVID-19 pandemic going on, that's not really an option. So I thought it might be useful to record a little screen cast to show you what's new in Spring Boot 2.3. Let's get into it. (technological music) So the first thing that's
new in Spring Boot 2.3 is the release itself. And by that I mean you
might not be expecting it. If you've been using
Spring Boot for a while, you're probably used to upgrading once every 12 months or so, and this release is
about six months early. That's because, starting
with Spring Boot 2.3, we're going to try something different. We're going to start releasing
based on a calendar date, so every six months you
can expect a new release. This means that releases might be lighter, there might be less in them, but the cadence is going
be more predictable, and it's going be easier
for you to upgrade. There are a few other
projects that are joining us on this adventure,
for example Spring Data is also moving to a
six-month release cadence. Other projects are going to
stick to feature-based releases. So for example Spring Framework is only going to release when it's ready. Regardless, you can be
sure that when we release a new version of Spring Boot, we're going to pull in the latest versions of all the dependencies that are available and make sure that they
work well together. Talking of dependency upgrades, there are a few things you need to be aware of with this release, especially if you're a Spring Data user. This version of Spring Boot
pulls in Spring Data Neumann, and that brings with
it some driver updates. Specifically the Cassandra
driver is now at version four, and the Couchbase driver
is at version three. These include breaking changes, so it's possible that
you may need to change your code when you upgrade. Other notable upgrades
in this release include Micrometer 1.5, and this is the
first release of Spring Boot where we're offering Java 14 support. The main part of this
presentation however, I want to talk about Kubernetes, and for that I want to
dive into some demos... So here we are at start.spring.io, and what I'm going to do
is generate a new project that we can play with. I'm going to generate a Maven project, I'm going to make Java
the language of choice, the latest version in the 2.3 line, and then I'm going to upgrade
the Java version to 14. I also need to add a
couple of dependencies, so I'm going to add a web dependency. And I'm also going to add Actuator, because I want to demo some
features from that later. Click generate, save the file, and we should have a zip file. Let's head over to our terminal. And then we can unzip this demo file and import it into our IDE. So we need to make this
wiggle a little bit, so let's add a @Controller, let's just a Hello World @Controller. To do that, I just jump in the code, I'm going to create a new class, I'm going to call it 'HelloWorld...'. 'HelloWorldController`, and
then I'm going to make it a @RestController, and I'll add one endpoint. And just make it say, "Hello World!". And we're going to map that to '/hello'. So that's it, that's our basic
application up and running, let's see if it works. Here we are. And there we go, jump
over to the web browser and just make sure that we
can hit that 'hello' endpoint. And there we go, "Hello World!" Now pretty much the first thing
you're going to want to do when working with Kubernetes
is take that application and put it in a container. Now it's always been possible
to do this with Spring Boot, but sometimes the results
were less than optimal. For example, you may have
found that if you change a lot of files, that your
images start to get bloated, and new layers are created each time and a lot of disk space gets consumed. Or you may have found
that you simply want it to run with the fat jar and
performance wasn't as good as you expected. So to help with this, what
we've tried to do is make image creation a first-class concern. And we've done that by leaning on something called Buildpacks. Now Buildpacks are technology
that have been around in some form or other for quite a while. And the Buildpacks.io project
is an open-source project, developed jointly by VMware and Heroku. What a Buildpack does is,
it takes your application, and it creates a sensible Docker image. So let's have a look
at what we've got here. I'm going to jump over to the manual and just show you the
Maven plugin documentation. So specifically I want to show you how you can package an OCI image. So section six of our
reference guide includes information about this new
goal called build-image. Now you can add that goal as an execution, as is demonstrated here,
or you can just call it. So for instance if I head back over to this command line prompt, I can just type 'mvn
spring-boot:build-image'. And what's going to happen here is, my app's going to build,
the tests are going to run and then an image is going
to get created for me. So what we'll see is some
output at the later half of this build that
shows Buildpack running. And here you can see, this
area here, is the Buildpack doing its stuff, so it's
detecting Buildpacks that can work with the software. And then ultimately it's
spat out this Docker image. Now by default, when you use the plugins, the name of the image will
be the name of your project, and the tag of the image will be the version of your project. So in this case we've
got demo as the name, and 0.0.1-SNAPSHOT as the tag. If I want to run that application, I can just, let me stop the existing one, and then just type docker run, and then I'm going to tell
it to use an interactive mode and port forward 8080 to 8080. And then run this image and tag. And there we go, you can see
our application's running, and if we head back
over to the web browser and hit 'localhost 8080/hello', we get "Hello World!" again, but this time from a Docker image. One tool that's very
useful if you're working with Docker images is
something called Dive, and Dive will let you dig into an image and see the layers that
make up that image. So I want to show you, by default, what the Buildpack has done here. So I'm going to type 'dive',
and then I'm going to give it the name of the image and the tag that I want it to view. Once that's loaded I'm just
going to head over here and uncheck the unmodified option, so that we can only see
things that are added, or removed, or changed. The way this screen is organized is, you've got contents on the right and the layers on the left. So if I scroll down,
you can see which layer adds which content. So starting from the top,
we can see some APT stuff, some more operating system stuff, and then a little bit further down we should see the JRE itself being added. And then some additional
options by the looks of things, but the layers at the bottom are the ones that are most important. This 18 MB layer at the
bottom is our application. There's one final layer that
the Buildpack also adds, which just provides some
metadata for it to read later. Now I mentioned that having one layer that gets updated with every code change is less then optimal. So one thing we can do
now is tell the Buildpack and Spring Boot to generate a layer jar. In order to do that we need
to add some configuration. If I head back over to
the documentation again, in section 5.1 you can
see the configuration that you need to add to your Maven plugin if you want to create a layered jar. So I'm just going to copy and paste that and stick it into my POM file. Let's head over here and
build this application again. And this time, let's open up Dive and see what the differences are. So again, I'm going to just
show the updated content. I'm going to scroll down
to the interesting stuff, and already we can see that
there is some differences. So, unlike previously, we
now have one 18 MB layer that just contains our jar files. We then got something that's about 200 kB, which is our Spring Boot Loader classes. We've then got a layer that's 0 bytes, and this would be snapshot
dependencies if you had any. And then finally we've got our kind of application layer itself. So here you can see that our application is now just 5.2 kilobytes. Now this layered approach is really useful if you've got an application
that changes frequently, because it means the
only layer that's updated is that one that's 5.2K near the end. If your dependencies are the same, then they don't change. And that means that you take up a lot less disk space with each edit. Now the Buildpack stuff's quite useful, and we think a lot of people will like it, but we also understand that there's a big ecosystem around Dockerfiles, so we wanted to be able to
support Dockerfiles as well. And to do that we've added
something called a jar mode. To show you our jar
modes I'm going to jump back into this code again. And this time show you what happens if you run the application as a jar. So ordinarily, if you run 'java
-jar' and then our fat jar, the application just starts. However, if you want to run
this thing in a different mode, what we can do is add a flag to the front. So if I add '-Djarmode=layertools', what this is going to do is,
it's going to start that jar, and it's going to offer me a
different set of functionality. What this is actually doing is, it's just starting up the normal launcher and then diverting off
to a different process, and providing some built-in functionality that we think you might want to use. Layer tools is the first
one that we're offering. You can see here we have three commands. We have 'list', 'extract', and 'help', and help is the one that's
being displayed by default. If I run this command again
and put 'list' on the end, you'll see we get the output
that represents the layers that this jar supports. So in this example the
jar's got 'dependencies' as the top layer, the
'Spring Boot loader' classes that we saw previously, then
'snapshot dependencies', and then finally your 'application'. So the other command we've
got here is 'extract'. So if I run this command again, this time change it to 'extract', and give it a destination. I'm just going to create a temp directory. What this is going to do
is, it's going to extract the contents of that jar, so that this jar is now
kind of self-extracting. But it's going to extract the
contents into layer folders. So let me show you the
contents of that unpacked area. So you can see here we have a
folder called 'application', a folder called 'dependencies', one called 'snapshot dependencies', and one called 'Spring Boot Loader'. And these match the contents of the layers that 'list' provides. If I dig into a bit more detail and just show you a tree view of that. You can see we have our application layer, it's the same kind of light 5.2K stuff. Our libraries are all in the lib folder under the 'dependencies' layer, and the 'Spring Boot Loader' provides the loader classes that we need. Now the reason we've added
this jar mode functionality is allows you to write proper Dockerfiles. So if I jump back into the documentation, and this time I'm going to look
at the main reference guide. So this chapter explains how you can write a well-formed Dockerfile
that's going to be both performant and efficient
in terms of disk space. And the way that we recommend you do this is to use a multi-stage Dockerfile. So I'm going to copy and
paste this example here and we're just going to
edit it just a little bit. So let me create a Dockerfile. And paste that contents,
I need to make a couple of minor tweaks. I want to change the JDK version to 14, both in the builder and
the main Docker contents. So if you've not seen these files before, they're quite interesting. They're called multi-stage Dockerfiles. And what they allow you
to do is have one section that's effectively thrown away when it's no longer needed. So in this example we have a first stage, which is the 'builder' stage, and a second stage, which is
our main kind of Dockerfile as you probably know it. So what the first stage does is, we've named it builder, and
it's going to take our JAR file, it's going to copy it into
this temporary ... Docker area, and then it's going to run that
layertools extract command. So that's going to end
up with a bunch of files in this kind of temporary area. The second stage we then
make use of those files. So here you can see that
we're again coming from a particular JDK version,
setting a work directory, and then we're saying we want to copy... And this from command is saying, copy it from this previous operation that we ran. So here we're copying dependencies, and the loader, and the
snapshot dependencies, and the application. It's important to note
that those orderings need to be correct, they need to match what
the list command says in order to have an
efficient Docker image. The final part, here
we have an entry point, and this is just going
to run Java and point at one of our launcher classes. And this launcher is a piece of code that if you run 'java -jar', it's responsible for loading the fat jar and finding the nested jars. But you can also actually
run it with an exploded form, as we're doing here, and it will just pick the appropriate classpath
and load the nested jars in the same way. So if we want to run this we
just jump back over here and we type 'docker build' and
give it the current directory. And you can see here the stage is working, so we have our builder
stage that starts off, the jar gets copied up,
the extract command runs and creates these folders, and then a little bit further down we copy the contents into the image. Now unlike the Maven plugin, the Dockerfile doesn't
actually have a name here, because I haven't specified one. I could use that hash
and point Dive at it, and we can take a look at the contents. Again, just jump over,
show any of the updates, and you can see the various commands that make up this Docker image, and these are the ones that
we're interested in here. And again, they match
what the Buildpack does, so our 'libraries', our 'dependencies'. We have our 'loader classes', our 'snapshot dependency' is empty, and then our 'application
classes', again 5.2k. There's a few things I want to
show you inside the jar file that will explain how the
jartools command works. So I'm going to jump
back over into my window, and I'm just going to look
in the target directory and unpack the jar file. And if I look in the BOOT-INF folder, you'll see there are a couple
of files here that are new. So the first is called 'classpath.idx', and the second is 'layers.idx'. Now 'classpath.idx' solves
one of the longstanding issues with Spring Boot. And that is that when
you run an exploded mode, the class path order
was non-deterministic. If I just 'cat' the contents of this, you can see it's a fairly simple file. And it just lists the jars
that we're going to load and the order that we're
going to load them. The other file we've got
in here is a layers file. So if I 'cat' the
contents of 'layers.idx', what you'll see here is a YAML-like file that describes the layers and the order that they're going to appear
in, along with their contents. So you can see we have
a 'dependencies' layer that contains the BOOT-INF/lib/ folder, the 'loader' layer that contains the contents of the org/ folder, the 'snapshot dependencies',
which is empty, and then our 'application' layer, which contains classes
and these idx files, along with the META-INF folder. And this layer.idx file
describes the entire jar, so once you've read this file, we've got the complete
jar carved up into layers. I'm going to jump back
over to the documentation, because I want to show you
something in the Maven plugin documentation that explains a little bit how you can customize the
layers that you might want. Now because that layer
file is quite flexible, it allows you to choose how you want to carve up your application. We've picked sensible
defaults that we think will work for most applications, but it might be possible
that you want to do something a little bit unusual. Perhaps you have a bunch
of static resources and it makes sense for you to
have those in their own layer. Or perhaps you have some
kind of custom libraries that you use inside your organization that change less frequently
than your application, but more frequently than our dependencies. In order to do that, what you can do is, you can define a layers configuration. So here in the Maven documentation
we've got a little bit of explanation about what we need to do. I'm going to copy this line, head back over to my
configuration and just add it in. And what this is saying is "I want you to configure the layers
using this layers.xml file". So we need to go ahead and create that in our source directory. And then what I'm going to do is, I'm going to jump over
to the documentation and look at some examples. And down here we have
custom layers configuration example that shows you the kind of file that you can write. Now depending on whether
you're using Maven or Gradle, the customization logic
is slightly different. So in Maven we've chosen an XML file that you need to define yourself, and in Gradle there's a DSL that's part of the build plugin. Both are documented in the
reference documentation. I'll briefly explain how
this layers file works, because the concepts are the same. So what we have here
is three main sections. The first section is 'application', the second is 'dependencies', and the third is 'layer order'. Now 'application' defines
the parts of the code that aren't jars, that
aren't your dependencies, and explains how you
should slice those up. 'Dependencies' of the jars themselves, and then the 'layer order' is the order that these layers should actually appear. Each of these work in a very similar way. You have these 'into' blocks, and this is saying "I want
you to take a section of this code and I want you to
put it into a specific layer". So in this example we're
saying I want you to put into the 'spring-boot-loader'
layer anything that's in this folder. This final 'into' layer
here is saying anything that's left over, just put
in the 'application' layer. If we look at dependencies,
it's got the same structure. We're going to say here
I want to put anything that ends in snapshot into my
'snapshot dependencies' layer. Anything that starts with
com.acme I'm going to put in my 'company-dependencies' layer, and then anything that's
left over I'm just going to put in the 'dependencies' layer. So that's anything that
hasn't been covered by the previous ones gets
put into this default layer at the end. And then finally our ordering,
which makes sense here. So here we're saying we
expect 'dependencies' will change least frequently,
followed by 'spring-boot-loader', then 'snapshot-dependencies',
then my 'company-dependencies' which we expect to change more frequently than our regular dependencies, and then finally my 'application', which we expect to change
the most frequently. Now obviously we haven't got
any com.acme dependencies, so I'm just going to change this and pick a random dependency that we have got, so I'm going to pick Jackson for this. So I'm going to do fasterxml.jackson.core. And what this is going to say is, any of the libraries
that are in this group will end up in my
'company-dependencies' layer. Let's head over and
rebuild our application. I'm just going to run 'build-image' again, let it do its thing, let
the Buildpack kick in. So start Dive again. So we can see, if we jump down here, we can now have a few more layers. So we have our 16 MB dependencies layer. Then we have our loader layer, then we have our snapshot dependencies, which is empty again. And then we have a 1.8 MB
for our company dependencies, in this case it's just
got Jackson inside it. Then we have our application, and then the Buildpack layer as usual. So we hope that that layers configuration is simple enough to understand, but powerful enough to allow
you to do what you want to carve up your jar if our default layers aren't working for you. The next thing I want to show
you is graceful shutdown. So to do that, I'm going
to jump back into the code. And I'm going to make some
changes to my controller. What I want to do here is, I
want to make this controller act like it's very, very slow. So I'm just going to add a Thread.sleep(). And I'm going to make this really slow, a five-second delay for each request. If I jump back over to the command line, and this time I'm going
to run this application using 'spring-boot:run'. Let's jump into our web browser, and hit our '/hello' endpoint. And you can see there that that request took a significant amount of time. What I'm going to do here is, I'm going to turn on the web tools, and I'm going to show
you the network traffic. I'm going to hit refresh
again and you can see the request happening
and it's going to take about five seconds and then
the response coming back. Now in early versions of Spring Boot, if you had a request like this and your application needed to be stopped in order to be moved; so for instance if
you're running Kubernetes and the pod needed to be
relocated somewhere else; then this kind of long-running request would not shut down properly. I can show you that now if
I hit refresh on this again. And then jump back over here
and just ctrl-c like mad until this application stops. You can see not only do
we get an ugly stacktrace, but if we head back to our web browser we got an incomplete response. So here the user experience is not great. If the user happens to be hitting request when your application is shut down, they don't get to finish that request and they get this horrible experience. Now in order to fix that
what we've done is add something called graceful
shutdown support. So I'm going to jump back
over to my application and I'm going to open my
'application.properties'. And what I need to do now is
add a couple of properties. The first is called
[...] 'server.shutdown', and there are two values available. The 'immediate' is the mode
that we ship out of the box, and 'graceful' is a new mode. So I'm going to choose 'graceful'. The second property that I need to set is a lifecycle property, and that's 'timeout-per-shutdown-phase', and I'm going to give that 20 seconds. And what this property
means is when Spring is running a phase, when
it's doing a shutdown of a lifecycle phase, it's
going to give it this amount of time to complete that phase. So 20 seconds is about
right for what we want. Let's head back over to the app again and do 'spring-boot:run'. And then I'm going to head
back to my web browser, do a refresh, and jump back
over here and hit ctrl-c. And immediately you can see
there are some differences. So we no longer got this stacktrace, instead we got this message
"Commencing graceful shutdown. Waiting for active request to complete." So what that's going to do is, it's going to drain the web server. All the active threads
are going to complete, but no new ones are going to be accepted. If we head back over to our web browser, you can see that this time we
actually did get a response. So an inflight request was finished. Now this kind of functionality
is very important if you're running in a cloud
platform like Kubernetes. Kubernetes wants to be able
to take your application, shut it down, and start up a
new version somewhere else, and we want that experience
to be as seamless as possible for our users. One of the ways that Kubernetes is able to understand about your application, is through something called probes. To show you probes I'm going
to jump back over to the code. So I need to add a couple of properties. The first one I need to add is
the cloud-platform property. And I'm going to pretend that
we're running on Kubernetes. Now Spring Boot's had cloud
platform support for a while, you can do @ConditionalOn
the cloud platform and pick the platform
that you're running under, but previously you weren't
able to customize it. We've now added a property, which means it's very
easy to pretend you're in a specific cloud platform
without actually being there. It's quite useful for testing, and it's quite useful for demos. The second property that I want to add is my exposure for my Actuator endpoints. So I'm going to do expose
for the web endpoints I'm just going to include everything. The way that we've offered
probe support is to lean on the existing Actuator functionality, so I need to make sure that
all of my web endpoints are exposed and available. Let's start this application again. And then I'm going to head over, and instead of looking
at my hello endpoint, I'm going to look at the Actuator endpoint that is provided by Spring Boot. And specifically the one I
want to look at is health. So here you can see we have an overall application health of up, but you can also see that we
have two groups available. The health groups have been
available in Spring Boot since version 2.2, and
it's a way of collecting health indicators that make
sense for a particular purpose, and grouping them together. What we've decided to
do for probe support, is lean on this group
functionality and provide a couple of auto-configured
ones on your behalf if you're running in Kubernetes. So you can see we have
one called liveness, and one called readiness. If I look at liveness, you can see we have a status of up. And if I look at readiness, you can see I also have a status of up. To show you how this stuff
works I want to jump back into the code and dig into some in the Spring Boot code itself. What I want to do is, I'm going to add a couple
of new methods here that are going to simulate this application going up and down. So I'm going to add a down method. And I'm going to add an up method. I'm going to map those as appropriate. And just return a string response. So the way that we've added probes is, we've added a new feature in Spring Boot called "application availability". So there is a new class called
AvailabilityChangeEvent, and this is an event
that Spring Boot itself will fire when it thinks an important part of the application lifecycle has happened. All this event does is it
carries one piece of data, called the AvailabilityState, and that's just an interface. And we have two of these that
are provided out of the box. We have a LivenessState, which is an availability
state for the liveness probe, and we have a ReadinessState, which is an availability
state for the readiness probe. So you can see here, these
are just simple enums. We have a liveness state of 'correct' if the application is running, and 'broken' if it's not. And a readiness state
for 'accepting traffic' or 'refusing traffic'. And what Spring Boot will do
is as your application starts, it will fire these events
at the appropriate time. So for instance once the
web server is started and Spring MVC has come up, it will fire an event to say
I'm ready to accept traffic. But if you want to you can also control these events yourself, and you can fire them
from your own application. So what I want to do here
is simulate a publish. And I'm going to publish
this event to a publisher. I'm going to use 'this' as the source, and I'm going to change
my readiness state. So let's pick
ReadinessState.REFUSING_TRAFFIC for down, and ReadinessState.ACCEPTING_TRAFFIC
for up. If you're not familiar
with an application event, it's been in Spring
Framework for a long time, and a publisher is just an interface, so I can inject it into my controller. So what's going to happen
here is our controller is going to get created,
Spring is going to give it this ApplicationEventPublisher, and then later on we're
going to use that to publish an AvailabilityChangeEvent
and we're going to publish either one with 'refusing
traffic' or 'accepting traffic'. Let's restart our application. And then if we head over here, if I hit the readiness state
now you can see it's up. If I go to a new tab, hit
'localhost:8080/down', we get our 'down' response
and our application is now returning a
response of OUT_OF_SERVICE. If I hit '/up' again, our application becomes
healthy and Kubernetes will start routing traffic to it. And these response codes will work out of the box with Kubernetes, so once you wire everything up, then things will just work. And there's examples in
the reference documentation that show you how to do that. Whilst we're in this Actuator endpoint, there's one more feature
that I want to show you that I think is quite useful. If you look at the
'/configprops' endpoint, we've added a new field to it, so I'm just going to a
grab the raw data here, pretty-print it and copy
it into a text editor. And I want to show you the 'graceful' property that we set. So right here, what this
endpoint is showing you is that we have a class
called ServerProperties, which is a @ConfigurationProperties class, so if I jump into the codebase, we can actually look at that class, this is part of Spring Boot itself. You can see it's a
@ConfigurationProperties class that binds anything
that starts in 'server' to these properties. And indeed, one of the properties
is the 'shutdown' property which points to an enum. So the '/configprops' endpoint's
been there for a while, but what we've added
is an additional field. So if I jump over back
into our properties again, what you can see here is the value that's set on the property is the enum. But just below it we
have a list of inputs. And what this is saying is "where does this value actually come from". And if we scroll down we can find the 'shutdown' property. You can see here we have
a value of 'graceful', but this case it's lowercase, and then it will tell us the
'origin' of that property. So right here you can see that we come from a class path resource
named 'application.properties', right down to the line and column number that this is defined in. So if I jump back to the code, look at our 'application.properties', then line one, column 17 down here does indeed match where
this value came from. But we think this kind of
end-to-end traceability is really useful. It's really helpful to have rich, type-safe configuration
properties objects, but still know which PropertySource
contributed the value, right down to the line and column number. And that pretty much wraps up the demo. There are a couple of things
that I wanted to show you that I couldn't get to. That includes the R2DBC
support that we've added, so if you're a reactive programmer, you might want to check that out. And we've also added RSocket support with Spring Integration. Both of these are documented
in the release notes, and you can check out the
reference documentation. There are a couple of
gotchas that you should also be aware of. One has to do with server threads, so if you manually configure
your server threads, you need to migrate that property under a specific technology group. So for example it might live
under Tomcat or Undertow, and that's because the server
threads are subtly different, depending on which
technology you're using, and we didn't feel
comfortable having a uniform property that worked
differently depending on which servlet container you where using. Finally, if you're using
the 'whitelabel error page', you should be aware that we
hide the details by default now. There's a single line
property that you can change if you want to show those again, and all of this is documented in the Wiki on the release notes section. I'll leave you with one final interesting bit of information. If you've ever contributed
to Spring Boot itself, you may be aware that we used to build the project with Maven. Starting with Spring Boot 2.3, we've actually migrated to Gradle. This means if you've got
an existing checkout, you may need to reimport it into your IDE. The good news is that
most IDEs support Gradle really well out of the box, and the new build is significantly
faster than the old one. If you need extra help, jump
over to the contributing guide on the GitHub page and
you'll find detailed instructions on how to work with the code. We really appreciate all
pull-requests and contributions that get made to the project. And that's pretty much it from me. I hope that next year
we'll be able to go back to conferences and present
this information face-to-face, but for now this will have to do, cheers.