About this talk
This talk discusses the experiences learned over three years of running Quarkus in production at Tenet, a high voltage grid operator in the Netherlands. The speaker explains the migration from Java EE applications running on WebLogic to Quarkus, highlighting its development benefits. Key topics include improving the developer experience through features like build time optimizations, continuous testing, and modular application structuring. The talk also explores deployment strategies, memory management in Kubernetes, and the advantages of using GraalVM for native image generation. Additionally, the speaker touches on semantic versioning for internal libraries and the importance of proper testing and tracing in applications.
Full transcript
[music] Thank you for being here at this uh early morning and today we're going to talk about what we have learned in the last three years running quarkers in production uh and especially the road up to uh getting stuff into production. So, first off, um there is a QR code uh on almost every slide for the Slido app. So, if you have any questions, you can put
them in there. My name is Aago. I work for a company called Tenant. Tenant is the grid operator of the Netherlands, of the high voltage network and part of Germany as well. Uh and we build software with around a couple of hundred uh developers, maybe 100 plus Java developers. Um and we built Quarkas applications. Well, why quarkers then? Well, tenet comes from a history of java e
and uh what they were running on was uh stuff on web logic. So everything was in a container in a J container and then they wanted to move some to something newer something modern. Uh and that's where Quarkus came into because Quarkus is a subset or a light version of the Java EE um and that made it easy to migrate current applications and there was even a
phase where we had some application running both on a web logic stack and on the quark stack side by side to see uh any differences in there and it was really helpful to find u small bugs that eventually will arise. So, a little bit of disclaimer. Uh, the lessons that I'm going to show you are simplified because I don't have time to deep dive into everything and
not everything is Quark specific. So, uh, who here is running on Spring Boot? Okay. Any Quarkas users in the house? Oh, more than I was expecting. Let's say 80 20% something like that. Um, so some of the lessons you will can actually apply to uh your Spring Boot application So, first off, let's have a look at the developer experience for a Quarkus application. Maybe you have seen
this already in uh in other Quarkus examples, but let's go back to the basics first. So, when you download a Quarkus application from the start.quarkus.io uh website, um you can configure your application just like a spring boot application and then you have something that you can import into u uh the ID. why is my mouse not working? Sorry. Okay, demo cards are not with me today. So,
let's reboot Intelligj. Great. Now it works. So, Caucus comes with a little bit of hello world application stuff. So, you have a greeting resource here that is an example. uh and you can uh run this application uh if you have the uh commercial license of IntelliJ you will have actually a run option here u but I don't so you can also run it from the command line
so you can do a maven compile corkus dev and I will actually compile the application and and start it there is a bit of difference here between spring and quarkus with spring you have a starter application that you can just can start from your ID but quarkus does a lot of things on buildtime so it will actually scan your um your class path and that kind of
stuff and do some optimizations uh that can be used later on that has to be done within a maven plugin or gradal plugin. So application is started up and uh I have some logging and who here is a fan of light mode some so [snorts] for those are on light mode I'm here in uh presentation mode as well uh quarkus isn't fan of lights mode so the
logging is a little bit lighter uh so we can fix that so there is a uh application properties uh just like your if I do a quarkus log console darken uh and restart my application if If I hit S on the keyboard, it will restart the Quarkus application. And as you can see, I don't have to wait until a full recompile has been done. And now my
logging is actually readable. So that's quite nice. Another thing I could do is hit D on the keyboard and then it will uh open up the developer tools. And here you have uh all kinds of extensions uh that can be enabled. If you have, for example, swagger enabled, it will show up here. But this is a bare bones application. What I can see here is that I
have a hello endpoint. I can click on it and it will show me the um hello quirkus. Great. Um but if I change the code hello JCON, just go back to the browser and hit refresh. You will notice that it took a little bit longer than uh than normally, but that that's because the entire application has been restarted now. So you don't have to uh keep in
mind when to restart your application. It does that for you. This is a bit too far. There is also something nice and that's called continuous testing. If I uh hit this button, Quarkus will actually run my unit test in the application and as you can see there's one failure because I changed the code and it doesn't like that because there was a unit test that will verify
that it says hello quarks rest and not jon. So if I fix this automatically my test also have run and you can see that everything is is green again. It will run only the unit test so stuff with test at the end because if you want to run all your integration test and that kind of stuff you can configure it but it will take much longer to
do that. So this is a nice uh little feature. So that's nice. So we had a single module application. we downloaded something but when we move to enterprise applications we normally end up with a lot of modules in our application right a good tactic to split up into multiple modules is when you have uh a separate functional boundary in your application and when that uh boundary becomes
a little bit bigger then it's time to move to a module or you can move to a module but just like spring quarkus needs to discover where are your beans or your components or your services are. So you can do that with a beans.xml. It's part of the JA spec. However, if you do a full JA beans.xml, quarkus will not look at the contents of the beans.xml.
It will only look if there is a beans.xml. So actually will scream about hey you can just make this empty. So this is one way. A better way is to use a jundex index. A yandex is index is created at buildtime of that module and it is a binary file that contains metadata of your classes. This is actually being done by quarkus if it encounters a b.xml
but if there is a yandex index it will actually read that and well your build time or your startup time is a little bit faster. This is quite helpful. Don't expect seconds of gain in your build. Uh the beans.xml discovery takes a few milliseconds. So guess what you're going to uh get back. So let's have a little demo back to the ID. All the code will be
on GitHub later. And there is a next script in there. So there are steps in in the uh in the branch of in the in the main branch. And with next you can move them to the to the next step and quark is running here and quarkus died because I changed the entire structure of the application and it doesn't like that. So now we have a app
module and this app module is empty. It doesn't contain any Java code. You can of course put Java code in here. No problem. But everything is now in the greeting module. So here is my greeting resource and the kind of stuff. So if I reboot my again, it will take a little while because now it needs to compile two modules. And we're back up and we can
do the same thing. So continuous testing is still and this says hello rest again. So everything just works. So it will discover also the unit test in your different modules as well. So this works just fine. So the lesson here is you can split up your functional domains or your functional packages into modules but there is a cost to that. So please try to make them worth
the while to build build times will increase. Adding a different module will increase your build time on the CI server or if you run Maven verify for example. It has some gains because if you have a functional domain and do a change in that functional domain, you can just run that functional domain, run all the tests in there and well then you know that that part of
your application is still working. We did this for a couple of applications of ours and we saw that the build time went from 2 minutes to 4 minutes. So it's a quite significant change there. So our builds crawl to a little bit of stand still. So what can we do about that? There's actually a build cache. So back to the ID next. Uh let's go this application.
So if I do a Maven uh clean verify for example, let that run for a bit. Then we can look at what we need to do to uh introduce build caching. And that's in the Maven folder. We can have a extensions.xml. Who is familiar with extensions in Maven? Couple of hands. U these extensions will load before anything else is going to happen in Maven. So this is
quite useful if you want to build like let's say a caching mechanism because you would have to hook into every life cycle of Maven and well they did that here. So just adding this XML allows you to have a Maven build cache configuration and for this demo I just used the local repository for uh for the cache location. So we will create a cache and we're going
to attach everything. By default, it will only cache the class files. And if you look into the target folder of our application, there is a caucus app for example that will not be cached by default. So saying that we are going to attach everything will help cach that as well. So also my unit test results, Jakoko files, everything is being cached. So that's So if I get
back to the build, we'll see that the build took around 13 seconds. If I run it again, it should take only a couple of seconds or two, threeish. So this saves a lot of time. What it will do is if there's a change in let's say the code, it will recompile everything. So if I make a change in the greeting resource, everything will be recompiled and be
done. But if you have split your application in multiple functional slices, then only the slices that you actually altered will be built again. So that saves you uh a lot of time recompiling stuff locally. So we saw that there's also a way to make this a remote cache. So if you have a Jenkins server or GitLab CI for example, you can actually use a Nexus repo or
whatever repository you want to store your build caches. A good way of good uh practice is to let them remove the the cache files if they haven't been accessed let's say for 48 hours or something like that because those cache files will grow quite hard. um our application when it's built successfully is around 100 megabytes. So every single code change is 100 megabytes in this repository. So
if the caches aren't being accessed for 48 hours, uh well worst case we need to rebuild the cache and this allows us to use that remote cache locally as well. So what we can see is that we have enabled the raw remote cache and you need to uh enable writing on the Jenkins server or the build server and then you can pull stuff from this cache locally.
So if a big project would takes 20 minutes to build let's say uh and you make the change in some functional slices all the slices that you don't have touched will be downloaded from the remote build cache and uh you will able to use that locally. So that will speed up your build significantly locally as well. So then it makes sense to split up into multiple uh
modules. So the lesson here is build caches are a timesaver. So let's deploy our application. Right? We have built something. Let's deploy it. I think this is a picture that everybody knows about. Google, right? You have the internet somewhere that connects to a load balancer and a load balancer distributes loads above uh among three nodes, right? Simple deployment strategy. and those nodes maybe access the internet for
let's say um a software updates license checks we have to call external APIs um or maybe S3 bucket somewhere so who's running this in production few hands scared hands who knows about this one what's this anybody familiar with this it's only a couple years ago lo for shell Yeah. So what happened here was that a attacker could make your application lock something uh let's say for example
a header or some user input or whatever and lo forj would actually interpret that as a lookup somewhere and somewhere it could be on the internet download stuff from there a class file and execute that class file. [snorts] Yeah great stuff it's actually being patched but um it was fun fun times on the internet. So a better way uh and hopefully everybody does this is to have
the internet uh connect to the load balancer. The load balancer is in DMZ and that load balancer will connect to a private network zone uh and distribute the load for that. But if you have let's say the external APIs that you need to call you have to plug in uh a specific rule in the firewall that you can access only that specific URL and not everything else
on the internet. This will make it harder for software updates to run. uh that means you have to have an internal repository for your software updates. But hey uh it's definitely worthwhile if you're not want to be hacked. So the lesson here is there is no need for internet access on your application host. >> [snorts] >> So speaking about virtual machines and that kind of stuff, um
when we started out we had some virtual machines where we could could deploy our applications and well the virtual machines we just got and then got 16 megabytes of 16 GB of of memory. So we allocated 12 GB of heap. Then the Java process had some overhead and well the OS had some some wiggle room. That just all worked fine. But then we moved to Kubernetes and
then we had to think about okay how much memory do you do do we need? We need to say our application has a x amount of heap but we also need to say okay this docker container will use x amount of memory maximum. So a heap of 512 megabytes. Let's do a guessing game. How much memory would this application on Java 25 actually use at idle? No
users whatsoever. Let's hear some numbers. >> 650. Who Who wants more? >> 800. Yeah. More. No more. Is actually 852. Well, your operating system needs 6 megabytes. Your JVM needs 30. Your uh JAR files needs to be mapped into memory. That's around 70 megabytes for a Caucus application. The garbage collector wants additional memory. You have meta science. You have code caching track stats. Uh you have some
class metadata. And you've got some other bits and bobs. Uh this all counts up to 820 850 megabytes. It's quite a lot. And actually a lot of those can be configured a bit. Um for example, the data space is unlimited by default. That sounds like a great solution, right? Uh well actually it doesn't store that much information. and it's just for uh caching of your class files
and that kind of stuff. But it can grow unlimited. So you can put a limit on that and then when that limit is reached you will get an out of memory exception with a out of metace exception. So if you would run this application on let's say kubernetes and you would say okay maybe I give it a little bit more memory. Let's say let's give it a
maximum memory of 700 megabytes. You will probably have encountered this little guy the 138 killer. You will get a nice error message in your log. Process filled with exit code 138. Great. I know this is because Kubernetes will actually kill your application if it uses too much memory. Kubernetes is quite forgiving if you allocate a bit more memory than you say said you would. So if you
said I'm going to get I would use maximum of 600 megabytes, you would actually use 800 and Kubernetes is just fine with it until it needs resources. then it will kill first the application that are overall allocating memory. So your application will be first. So please have this in mind. So then we have the elephant in the room and that's Grav VM because well Grav VM known
for its low memory sizes and low CPU sizes. So Grav aren't familiar with it is a a way to build your application into native code. So made so all your JVM stuff will be gone. Everything will be compiled to bite codes for your specific CPU. So if you run 80 x86 CPUs on your server, you need to bind of built a x86 binary. However, it's a lot
faster to start up. Our application started up instead of 7 seconds in 1 second. So for a real application, that's quite nice. And at 1 second actually was flyway doing checks in the database if everything was up up and running. So if I would skip flyway then it will probably be in the demo specs of couple of hundred millconds or something like that. They promise less memory
uses. So let's have a look at it. And it is more secure because if you would run your grav image instead of your Java image when the uh lock for shell occurred then you wouldn't be affected because it would load up a class file from somewhere from the attacker side. But if you compile everything into binary, there is no Java anymore. So it can't load up that
class file. So it can't execute the malicious code. You have to do lookups, but that's about it. So it's it's a lot more secure in remote access stuff. However, there are some cons. The build will take a bit longer. What we saw was a application that took 15 seconds to build with a Java uh VM will take around a minute to build with Growl. It's not that
uh big of a change, but hey, it's it's still and when you have a complex of large application with a complex setup, you need to do some uh some tweaking around with grow to get everything up and running. But hey, uh that could be a different entire deep dive talk uh for another time. So, let's look at the memory. On the left side, we see the uh
JVM u us memory usage. uh and I hope it's readable the numbers but uh we started up with around 850 megabytes of of uh memory and we put on 100 users concurrent users just spamming the system for 30 seconds and after 30 seconds our application reached almost 2 GB of memory that's a lot a lot for 512 heap right this is because threats will actually also use
memory memory that is not in the in the heap. So every thread it is two megabytes that you will have as an addition thread will be cleaned up. So this graph will go go down when the the load has died down. Um but it is uh well it can spike to to 2 GB with ease. And on the native side it's just a lot calmer. The CPU
usage was also a little lower and we saw that the memory uses only reached 110 megabytes or so. So that's quite nice. So if you're running into containers and you have memory constraints uh have a look at uh graph VM. So it saves a lot of resources but your dev and build time will increase. So you have to maintain those graph VM uh configurations. So it may
be worth your while. So let's have a look at inner source. Like I said, we were a uh not an IT company. We have only 100 developers working on our project. We have around 10 scrum teams, I think. But we use a lot of the same patterns and a lot of the same code. Then at some point it will make sense to have something like a inner
source. It's not open source. It is stuff that we use within our application within our applications within tenant. So here you find specific business logic stuff or specific patterns that we like to implement and the way to version those things is to use semantic versioning. Who is familiar with semantic versioning? Couple of hands. Cool. That's nice. So quick recap. If a major version is being bumped that
means that something is broken in your uh your library for example is not backwards compatible. If a minor change happens then something new was added and if a patch happens then well then some bug fixes or whatever was introduced. So let's have a look at some examples. Let's do some show of hands. Who think this example going from a empty implementation to doing something somewhere who thinks
this is a patch version? Nobody minor major version. That means a lot of people are unsure. But this is just a patch version. This is just changing the internals of a method. Doesn't really matter what what's going on. But if this is all your library exposes, then it's fine. Then it's a patch version. So let's add the deprecated annotation. Who thinks this is a patch version? minor,
major, all majors. Okay, so most of you said minor and actually it is minor because in the samware specification there is actually a section for deprecation. So if you add a deprecation annotation uh it should issue a new minor release because if you use that library you somehow someway config of confronted with stuff being deprecated. So you have to do something somehow but it's quite annoying. So
another one if we change the void do stuff to a boolean do stuff who think this is a patch version minor h interesting because if you use this function let's say as a consumer you have do stuff in your code and now it suddenly returns a boolean it doesn't matter for you you're backwards compatible so that's a minor If you flip it the other way around, if
we have a boolean return method and then a void, then stuff will break and that will be a minor of a major version. Lastly, let's change the compiler. Let's change from 21 to 25. Right? We think this is a patch, minor, major. Luckily, a lot of people said major because this is a major version upgrade. should not ever of course every time be a backwards compatible incompatible
change. If everybody you know is already on that specific version of Java, then it's fine. But if somebody or you don't know if everybody is already on that version, then then it's absolutely a major breaking change because they will have to upgrade their entire Java version in order to uh make use of your application or your library. So what we saw was that a lot of people
showed hands randomly. Some some said patch, some said minor, some said major. Uh and this is also what happens within the company as well. So some people said it was a patch, some said bit minor. Uh and well, we saw a lot of times that for example a major uh Java version was just being changed as a patch version. So I created a Maven uh plug-in. It's
open source uh and it will check your code against the last released version. So if we run it for example u it will see that there's a change in let's say the bite code and will complain hey it was targeted as a minor but it should have been a major and this will do a lot of checking uh and I've written a full blog on Java Pro
you can grab the article for free uh it's a PDF and that will show to you uh how the internals of this uh plug-in work it's uh quite useful because now everybody agrees or is forced to agree on the same versioning scheme. So inner source works really well but it requires discipline not only in um in versions but also in code styling and So speaking about updates,
Caucus also needs an update sometimes and Caucus upgrades sometime break. Um so what we saw in in uh earlier that was the major version is breaking. However with caucus major means it is heavily breaking and you need to invest heavily in getting stuff back up again. Uh and minor and patch sometime break. They actually will deprecate stuff in a minor version that is correctly and then a
few minor versions later they will actually remove the kind of stuff that they've deprecated. So if you don't u fix your stuff quickly u then you will run into breaking changes but actually there is a nice uh migration guide also on uh on quarkus. So every version has a migration guide on how to upgrade your application and if we go into a quite recent one you can
also see that some parts can be done automatically. So you have a command line to a quarker CLI that can do a lot of those upgrades automatically especially if there are renames in let's say property files or whatever that is easily being done automatically. Um but sometimes it will require manual steps. So you can actually do a quarkus upgrade for this application for example and say hey
I want to have the uh 3.2 and it will actually do uh the automatic conversion and upgrades to that specific So, Quarkus can and will break sometimes. Uh, but the Quarkus upgrade plug-in works well. Uh, but don't name your in the source modules or whatever modules something that starts with Quarkus because it will upgrade every model that starts with Quarkus. So, that's fun. [snorts] the uh dependency
management of Quarkus and this holds true for Spring as well. I think uh what Quarkas andspring has is a uh bill of materials. You can import this bill of materials as a dependency management and you say it's a pom and I want to import it and then every version that has been specified in that uh bill of materials is being used in your uh But there is
more in Quarkus than just Quark stuff. There's also Mojito in there because they have a Quarkus Mojito plugin and that works just fine with that specific version of Mojito. But if you have a Mojito in your application that has a specific version or some overwritten version, those two can clash and those two can break sometimes. So it's better to use the dependency management of the quarkus bomb
and don't specify the stuff that is already in that quarkus bomb uh in your application bump because then uh you have better compatibility with the stuff that quarks has. So let's do some testing, let's do some coding. Okay, demo guys are really not with me today. Okay, let's fire up Intelligj Oh, and it execute actually executed the calls correctly. So, I need to go back a this
step. All right. So, I have a start stop listener in my application now that will log if the application is is uh starting and is stopping. And if I run all my tests, so let's run all the unit tests in uh in this application. I have a few more. There's one failing because it has been removed, but the class file hasn't been removed. So that doesn't matter.
And if I go look for uh stopped, we'll see that it's occurs twice in this application or in in this run. So my quarkus test will actually start up a full quarkus application just like spring would and then it stopped and then it restarted a quarkus application again. So let's have a look at what's going on there. Well this is a test that just is a caucus
test but here is a quarkus test that has a test profile. And here we have another caucus test. What it actually does is first group all the quarkus test that doesn't have any profile run the test in there and then that profile you can do everything in that profile. Quarkus doesn't know what's going on. You can change the property but you can also change beans. You can
change behaviors. Um so quarker doesn't know. So it will actually stop the normal application and restart a new application with the test profile in there as well. This holds also true for spring. So um yeah, lesson here is just don't use that much test profiles. Try to minimize that as much as possible. let's make our application a bit more complex. We have a uh greeting resource that
now can get greetings from the database. It's just a find by ID. just uh super simple. But we of course have made a test for this as well. And if I run this then there should be one failure because the first test is just doing a hello then with a random ID which not found then we create a greeting on that endpoints. So using the application to
create a greeting, get it back from the application just works fine. But then I want to bypass the creating a greeting in the application and just putting it in a database and just getting it back from from our application. So here I have a create greeting method that will actually insert uh the greeting in the database and we'll get it back. But as you can see, that
one failed. And that's because we have something called a test transaction. So every unit test has a transaction that is automatically roll back after the test has failed or successfully executed. That's quite nice, but it has some limits, especially if we would use um the Yakaria. So we use uh rest easy for the uh yaks uh or for the rest endpoint and that rest endpoint is using
a new thread new transaction everything. So everything we do inside of this test transaction is never being committed. So I need to set a at transactional on this create greeting because then it's being committed to the database and then the application can actually get it from the database. So if I run it again now that should also be fine and check. So this is a bit of
a caveat in uh in testing. If you would if you don't use Jax easy or whatever then it's just fine. Um but there's a limit with uh with this. So demo time. So lesson here is minimize the test profiles. Um in in that way you don't restart quarkus that often because in a small application it takes a bit of a couple of milliseconds but if your application
grows then it will take a couple of seconds to restart the entire thing even for testing. So then it becomes hurtful. So we are on time. So let's do some tracing. I skip forward a bit. Intelligj is not liking me at all today. So, where are we in the thing? So let's start the application clean verify a compile cork dev and what we have now is uh
the greeting resource is being changed a little bit not yet I see. So do next. So we added open telemetry. Um which you're familiar with open telemetry and with traces and spans about half of the room. So that's nice. So if I go back to my uh the application is is up and running. And if I go to my hello endpoints is doing a bit more now.
It's creating a password for me. But how is that being done? Well, it's being done by um creating a password with a uh random character otherwise maybe not up to date. Um and I added a span on that. So if I go to uh is a way to view spans locally. So if I click this uh this open I will see that a get greeting is being
called and then a lot of uh random jars and those calls are really really fast uh microsconds because well we don't do that uh don't do much there but if we change the implementation bit instead of doing uh 10 characters we do a thousand characters. be reloaded. It's still fast. That's that's nice. But if I go into Jerger we see that there are now a 101 spans
here. And if I click this open, we get a nice little long thing. So trying to find your spans that actually take a little bit longer uh will becomes quite annoying. Uh and we have a little uh uh sometimes we have some caching uh behind some calls. So the first call is quite slow but the second call and well the the remaining calls will actually return in
microsconds. So we're not really interested in seeing those uh very fast calls in our spans. So what we introduce something called um with slow span where we can say we want to have a minimum duration of uh in this case 1 milliseconds uh in order to show it up. So we put here a thread sleep. So if I run this again it should be a little bit
slower because it's it's sleeping a bit. And if I search for this, you will see that there is a 11 spans and it now take around 2 milliseconds each. Well, that's expected. But if I want to filter this out, let's say everything above or below 3 milliseconds, we're not interested in. We can actually do that as well. And if I search for it now, then there should
be only be one span. It's because every all span is too fast in order to uh uh be captured by the uh by the spans. And this will reduce uh the number of spans in the traces. Your monitoring team will be very happy with you because you limited the the amount of storage that they need because spend will take up uh this space uh and your traces
are a lot more uh compact and more viewable. So we have some custom code for for this. Uh it's on GitHub. So we just created a with slow span interceptor. Uh but can you look it up onto GitHub and and use it yourself. But also the lesson here is tracing can be used as a poor man's profiling tool or a richman's profiling tool in production. Because if
you run traces and span in production, you can actually see where the bottlenecks are in your application or sometimes even be warned that hey stuff is not executing parallel but is exe executed sequential because you can see that in the spans and the traces uh in how they are laid out in the uh the UI. So that's quite nice. So at the top right there is the
slido uh question thing. Um slides are available on slides share and that's that for now and I like and I hope you learned something and let's see if there are some questions. There are a lot of questions when a public message changes its signature uh like your boolean acceptance. What is the breaking change? Um if it break I overwrote the methods in my code. Um the breaking
change if you had a boolean then um if you would actually assign that to a variable you're uh you're calling a method that had a boolean uh and assign it somewhere and the library now has a void then your code will actually no longer compile. So that is the breaking change. What is our biggest pain uh when running Caucus into production? Um well, bottom logaric falling over.
That's the uh the hardest pain that we we encountered. Caucus is just a layer uh and it's also a small layer for your application. It's uh maybe one or not not even 1% of your codebase. So it's just picking the right tool for your organization. So no specific hard upgrades are sometimes painful. So let's see if you reach slow start time with grow native images do you
use JPA or another OM so JPA app startup even with growl um yeah we just use the hibernate uh JPA and there sometimes is some reflection being done and then you need to run the growl agents to capture everything that is going on in your application all the reflective calls and that way you and uh make it up and Uh with 18 times lower memory uses in
native modes, you're probably saving a lot of energy manage. Would you have any numbers to share? Unfortunately, not yet because we are still in the process of uh getting this uh into production. Let's see. [sighs] Do you miss anything from the non uh community edition Intelligj? no, the the the the full uh IDE uh just works fine. There was a little bug a few iterations ago where
it would not resolve the workspace artifact. So, if you change something into a module, it didn't show up. Um, but I fixed it and well, it's it's running successfully over now. Uh, are there any tips to reduce buildtime in native image in in production? um you can actually set some parameters with the um gravm to do less optimizations and that way it's being built a little bit
faster uh but then your application will yeah be less performant but if your application is just uh some simple application that doesn't matter and with that there were the questions on slido and I'd like to thank you and enjoy the rest of the conference >> [applause]
More from this event
See all 34 talks →
Java: 30 Years and Beyond | Ana Maria Mihalceanu (EN)
39:47
Scaling Data in a Sovereign AI Platform | Johann Strauss & Markus Kett (EN)
49:29
Code Is Cheap. Software Isn’t. | Markus Eisele (EN)
47:23
Building a Digital Product Passport with Java and Cardano | Fabian Bormann (EN)
46:54