Pasha Finkelshteyn at Spring I/O 2024: Advanced Kotlin Techniques for Spring Developers

Transcript

My application. Nano-service. Dependencies

So, yeah, let me jump directly to my example. I will I will explain everything on a like Nano service. There are microservices, they have some logics, my thing does not have any logic whatsoever. Like controller service repository, that's all. I mean, nothing. And still there are some interesting parts! On MVC, obviously, because it's the most popular thing you write in. Spring validation, because it's hard to live without validation. I will showcase JPA and some things about JPA and JDBC. I will try to showcase something about tests but let's see if we have enough time. I always start from start, ".spring.io", just because it's the easiest… Oh, wow, it's old. Sorry don't look at the slide. You get the basic idea, right? Like, I try to use latest version of Spring. I last tested it on 3.3. I promise! Everything works on 3.3, I just forgot to update the slide. So, Gradle - Kotlin 3.3.0 So, yeah, this is the set of libraries I'll be talking about, and when we've hit the button "generate" on the start of spring.io we have only two files generated in our archive. build.gradle.kts... I won't even explain it. I'll dive in some parts of it, it's extremely long. 129 or something clients. Let's jump directly to the main class, and it is actually very simple class, right? It has some synthetic class "sampleApplication", just to put "SpringBootApplication" annotation on top of it, and also it has this "runApplication" inside the "main". Who knows this notation? What does it mean "runApplication" and "SampleApplication" inside triangle brackets? OK. So, there is such a thing in Kotlin called "reified generics", if you write libraries, you probably know about it, otherwise, you don't care, but this is a nice way to not pass ".class" arguments into functions. It's just a static function "runApplication", to which you pass arguments. Internally it does the same thing we would otherwise do in our Java Spring application, we just call SpringApplicaion.run and pass the arguments there. And well, it's the first thing you can do in your own code. When you see that, something in Java, you use some library in Java that accepts class, you can create a new function with a reified generic (it should be inline function with reified generic) and use it this way just to make API a little bit nicer.

MVC + Validation. Primitive types. Jackson

Let's start implementing our application. I always start from MVC + validation because for me it's a sane way to do something useful for customer.I immediately start thinking about domain from their prospective not from mine. Obviously, it's not that important in our nano-application, nano-service, but in another scenario, it actually could be very meaningful to create API first and then all the Logics behind the API. The controller in Spring looks very simple. It's your usual request mapping... Everybody knows what's request mapping is? Yes? Yeah, nice. Cool. We have a function with post mapping on it, and it doesn't even have a body. This function doesn't have a body. It's a fun function that doesn't have a body. It has "@requestBody" and "@valid". Who knows what does "@Valid" annotation means? Nice! "@valid" annotation means that we should check not only the top-level argument that we actually got the person, but we should dig inside the person and check, if that person is actually valid. "Person" is by the way a very simple data class with two fields, Constructor parameters, "name" and "age".

In our case "@Valid" does mean that "name" should be string, and "age" should be int, and both of them shouldn't be null. OK, sorry, my code is all is also buggy sometimes. I don't know, how I didn't mention it, didn't notice it. So, let's test it. Let's test our "POST" on our "person". And I just used an Idea built-in HTTP client to create a post request with a with an empty "body" of "Type: application/json". So, I kind of want to check if Spring validates "person" for me. And then the answer is "400 Bad Request" with time stamp and so on. Since "person" is non-nullable, it is validated without @NotNull annotation. But how? The thing is there are several lines in our build.gradle.kts,

which I didn't show you. They say something like this: for each task with Kotlin compile, we should add compiler argument called "-xjsr305=strict". What does it mean, what is JSR305? JSR305 is an annotation for Software Defect Detection. For us what's important that it checks "@notNull" and "@CheckForNull" annotations, and Kotlin, when compiles your code with this argument, adds an annotation "@NotNull" to each non-nullable argument. Come on! Sorry for that. Also, there are all kinds of different annotations, localizations, and so on. Now let's test! Let's pass the real "person" like the real object with a "name": null, and "age": null. And we still get our "400 Bad Request", which is a good thing, and on server we can see this error which says that "name" was missing. This is an important part. And also, now let's try to pass a person with a name, and it should fail I think because now "age" is also non-nullable. And we somehow get 200, which is kind of okay, so it will try to save or do whatever without "person", but it shouldn't work, right? It was my face when I saw it first time. Yeah, okay, something like that. Let's look, let's look at our person again. So "name" is string, "age" is double, they are both not non-nullable, it should not work, why does it work? The thing is, in JVM primitive types have default values, right? So, what are primitive types? Do I have the list here? Yeah. They are Double, Int, Float, Char, Short, Byte, and Boolean. They have default values. All of them have "0", Boolean has "false" by default. And Kotlin kind of struggles with it, because, I mean, you should either put question mark on your type and make it nullable, and make an object wrapper, you probably don't want to do it. Yeah, you can do something like this: put a notation NotNull on the double @field and make double nullable. Of course, it will solve the problem and, let's check it, we pass "age" null.

Now, okay. "Bad Request" and, obviously, there will be some error on the no server, Field error in object 'person' on field 'age' rejected value 

It's how it should work. It's not very beautiful. You should be careful about it though.

For controllers, for controllers and for Jackson it's solvable. Jackson has this nice setting which is called Jackson: deserialization: FAIL_ON_NULL_FOR_PRIMITIVES, which solves this problem for you. You will avoid putting this non-nulls and question marks on your fields,

but imagine that you do not work with Jackson. You have integration with gRPC or whatever, your own custom protocol. You should be mindful about this semi-feature that JVM has default values for primitive types. And you should have a way to work it around. Either putting some additional validation on it, like I did in my example with data class @NotNull and question mark, or doing some something like Spring did in their configuration, like check for nulls in primitives. For whom it's a new information? Yay! I promised, I promised you'll learn something new. That's cool! I mean, half of the audience is semi-satisfied! Let's make a quick summary. So please use -Xjsr305=strict if you use Gradle, and if you do not use start.spring.io spring if you do not generate your starter projects on at start.spring.io, it makes sense to put it there just to have additional validations. For JVM primitive types you have to put @field:NotNull, or solve it some other way like Jackson does.

Yeah, sometimes you can work it around with Jackson. Now, let's dive into JPA a bit.

JPA. Data classes. equals. hashCode

Who uses JPA? 3/4 of the audience. I'm sorry, folks. Who likes JPA? Almost the same people, almost the same people.

So Kotlin has this concept of data classes, you are probably all familiar with it. Let's look at our nanoentity, it has an entity annotation in it.

It's a data class, it has values "name" and "age", and yeah, here are some issues of this class: It is a data class, and yeah. And there is no no-arg constructor because you cannot have no-arg constructor in data class. It is kind of obvious, right? Because data class is a class that is supposed to have data by default, so if it doesn't have any fields, why is it a data class? It is something different. You could do it with POJO, you cannot do it with data class. So, data classes have by default "equals", "hashCode", "copy", and "componentX" functions defined.

Did I put "copy" twice here? My bad. It's very important function! Very important and very useful, but not in the context of entities.

So, we actually don't need any of them. We don't need default implementation of equals, we don't need default implementation of hash code, we don't need this componentX functions. Yeah, so let's get rid of this data key keyword. Something like this. Now! Now, there is an issue. "name" and "age" are values, and JPA does not work this way. For JPA to work correctly with your entities, everything should be mutable. I don't like it, but life is life, right? It was the age before I even started programming. I started programming like 15 years ago.

Java beans were already there. They appeared in Java 1.1 long before I started. So, yeah, it seems we have to convert vals to vars.

Now we have these annotations.

@Id, @GeneratedValue, @Column, and it's a class, and all these annotations are on the arguments of the constructor.

Who thinks that JPA will work this way? Nobody. Because I gave it out with my question, right? Well, it was stupid of me.

But how do we fix it anyway? Of course, there is a way we can fix it, we can convert columns to fields, but it's not beautiful. There is actually a magic plugin, and it's also in our generated build.gradle.kts, which is called "plugin.jpa", and it obviously shouldn't be 1.8.0,

it should be 2.0 because JetBrains just released the second version of Kotlin. It does some useful transformation to your code.

Not to your code, to generated classes actually. First of all, it puts notations on the fields without you explicitly specifying it. Explicitly specifying "please put this notation on the field" makes code clunky. I usually don't want it to be this way. I want magic to happen automatically. It's Kotlin! It should be beautiful! Well, at least, as beautiful as possible. And also, it adds a default Constructor in bytecode. In Kotlin the default Constructor without arguments would be impossible for data classes (obviously) because, again, they should have, they are intended to have some data inside. Doesn't make sense to add a constructor that does nothing. So, in bytecode there appears an invisible constructor that does nothing. So, if you will try to construct from this entity from Java, will have all the fields set to null, even though fields are defined as not nullable. So, our current result is something like this. But is it enough? I think, that not quite. At the very least we should redefine equals and hash code. Who knows how to define hash code and equals directly for JPA entities?

1, 2, 3, 4, 5, 6, 7... Seven hands. Yeah, I don't know either. It's actually a complex question. JPABuddy wrote an amazing article on how to do it in different situations. This is one of ideas you can use: you can always compare by id or always return zero as a hash code. At least this way your sets containing your entities will work more or less correctly. So, if this entity is not attached to the database, then JPA will think

that they all are different, because they don't have IDs, otherwise it will compare entities by id. But it's only one approach, there are different approaches, and yeah, it will be a homework, I won't dive deeper into it. Then, JBDC. This part I like more. I like more control over my code.


JDBC. Mapper. Extension functions.

Again, it's up to you to decide. Let's imagine that we have a very simple query like this:

SELECT* FROM users where ID = ? And I will show you code snippets with this particular query, but do not think that I'm that basic. This is queries I usually have in my applications. Yeah, and then something like this, because it's just a simple query from my pet project. It's a small library and I want to query some data about my books. But if I would put it in my code Snippets down the line it wouldn't be readable, so, yeah, we will stick to the simple one, right? Just imagine that instead of Select* here you see this fat query. So, let's look at our usual flow when we work with JBDC template. We have some function which returns a list of people, findByID, we have this JBDC template query, then we have our query itself, then we pass your own Mapper in there, and then we pass id. What is our Mapper? It's a class like this. Also, can be a lambda, we'll look at it a little bit later. It has only one function, mapRow, that takes id from resultSet, String and Double, and creates a new person. Kind of basic, right? We can try to inline this stuff.

I didn't like the previous thing, because… here we have id our after our Mapper, but if we inline it, it becomes even worse somehow, but at least we have our Mapper inside our code, because otherwise we will have lots of Mappers all over our repository, and it will be really hard to navigate over them. I think you can read it, right? resultSet, then we extract everything from database and create a new person. I don't know why it's id1, sorry, it's a simple typo, and, yeah. And the last thing we pass to our function is userId.

And again, just imagine it's not SELECT * FROM users, it's those fat queries. So, fat query is in one place, and arguments of this fat query is in completely different place, like 10, 12, 13 lines later. And yeah, so we have to choose, either we have too many Mappers, or parameters are too far away from the query. I don't like neither of them, so why should it be so? Let's look at the signature of this function in Java.

It's a query. Then it accepts SQL, our line, our string, then rowMapper, and then variable arguments of nullable objects. Because in Java you cannot put variable arguments as the middle argument, or as the first argument. Java just wouldn't understand what's going on. Here's how the same thing works in Kotlin.

JdbcTemplate.query, then our query, then first argument, which is userId in our case, and only then we have our Mapper! Mapper does something obvious, the very same. By the way, did you notice, that in this line we use underscore, because we actually don't care about the row number we work with? Instead of trying to give it a meaningful name that we won't later use? And then the code is almost Java style. Well, we use val (3x) in it, so instead of string double whatever, but otherwise it's the same. The thing is, in Kotlin vararg doesn't have to be in the last position. and we can use unused parameter as "_".

How does it work? With the help of extension functions. Inside Spring it's just Kotlin extension for Spring,

we have this function, extension function query on JdbcOperations, which first accepts SQL, then it accepts our arguments, and then it accepts the actual Mapper. And it works! I don't know why it can't be this way in Java. But, anyways. And the last argument in the first part of the code is Mapper.

When it's Lambda, we can use it like here. We can just extract the curly braces out of usual parents.

You can do the same for your own code, so imagine you have like function called transformString, and you can... in Java you have you have to pass some Mapper, how should we transform strings, and then you pass the argument strings. In Kotlin it will be better, In Kotlin it will be more logical, you can pass multiple strings you want to transform, and then Lambda with which you can transform the thing. So, it's not Spring-only, you can actually use it on top of your own libraries, on top of third-party libraries, because there are no limitations on what you can put your extension function. There are many Kotlin extensions for Spring: spring-beans, spring-context, many of them. We won't D dive in all of them, because it will take, I don't know, 3 hours more, But some of them are very interesting, and we will dive in some of them. In particular I want to dive a little bit into reactive persistence.

Reactive persistence. Coroutines. Spring R2DBC

Who is familiar with reactive persistence with Spring? Okay. Who read about it? It's not the same as used it. OK. So, there is a concept called... yeah... By the way, the image on the right is the best thing midjourney could come up for me to illustrate your active programming. I kinda agree. So, what is reactive? Reactive is non-blocking things. So, we do some call, and then our thread is not waiting for response. It might be reused for something else. It's asynchronous, so we operate on things like futures

that will eventually give us results. In JavaScript it will be called "promise", something like that, and it handles back-pressure. It's very important thing. So, it's like if we handle, if we can process only one item per second, backend shouldn't send us more than one item per second. It's very cool and very important it means that our clients are never overloaded, that all load is on the server. It should keep a lot of data inside. The result is a monad. Yeah, just kidding, sorry. The result is either mono... mono produces zero-to-one elements. Like exactly one person, or exactly zero person if nothing matches our query.

And a Flow which can produce from zero to infinity items. For example, if you will try to select asterisk from your database, it will produce infinity. Okay. Very limited, very small infinity probably in some cases. Anyways. It's the generic functions, they are like generic classes which behave a little bit like collections, but they return your result later. Kotlin works a little bit different. Kotlin operates with an entity called coroutine. And yeah, and on Suspend functions. Suspend functions can be called from coroutine context or from another suspend functions. Who uses suspend functions in their code? One third of the audience. I can agree, sometimes it's hard, but sometimes it's interesting. Contrary to reactive programming with the reactor in Spring, usually suspend functions return very simple types like Lists, Strings, and so on.

Let's look at the example. We have suspend function random number that returns integer. What it does? It calls one service to obtain first random number, it calls second service to obtain second random number,

and then it sums this random numbers. What is nice: internally JVM can stop execution after each of the statements, like after this or after this, and give... because we don't know how the services obtain their random numbers. Maybe it takes a long time, but for us, we can utilize threads better. This thread, which otherwise would just wait for the result, will give its resources to another thread to do something else, useful. And yeah, when you work with some libraries, you end up with suspend functions everywhere. Yeah, I know I'm not Buzz Lightyear, he is much more beautiful, but we have what we have, right? So, let's look at Spring R2DBC. Usually everything starts with a very simple repository,

we can create it ourselves, we should pass connectionFactory inside it, and then we create a client like databaseClient.create(connectionFactory). At this point we are ready to create R2DBC requests. Just in case, to work with R2DBC we have to add more one more dependency. For example: Postgresql R2DBC, MySQL R2DBC, whatever. Several databases are supported. Let's write some real code, some function for example. It will be a suspend function. We don't want to deal with the Flux and Mono in our Kotlin, and Spring has extension functions to help to help us fight them. So, for example, createUserAndReturnId, we use client then we call our query. You can see that I have argument called email which I can later bind. It will be incorrect email, I can promise you. Well, I can't, eventually it will be a correct email, but most probably it won't be. Then we call Fetch, and it's a part of Spring, and then we call this .awaitSingle function, which actually isn't part of the core R2DBC implementation, it's a part of R2DBC extension functions which looks like this. It fetches the first entity which returns Mono, and then it calls awaitSingleOrNull, and it wraps everything in a suspend function, so we don't think about this Monos, Fluxes, and so on. And eventually our function returns our Id from the Map, which has been returned by Spring Data R2DBC. Let's talk about transactions a bit. Transactions are a tricky topic. They were not implemented for some time, but they are already implemented in all this reactive world, and for us it actually looks exactly like it would look without any reactive stuff. We have this transaction... We have this transactional annotation (sorry, it's invisible). We can create a user, get its Id, and we can try to create a new user with the same id. It will throw an exception and because function is transactional, the first user won't be saved, which is usually the result we want to have. If we don't want to have this result, we just remove the transaction annotations, and everything is good. Look at this: from the client, from the consumer standpoint nothing changes. You could as well use Spring R2DBC. The semantics would be exactly the same, the only thing that actually changes for you is it's now suspend function, and this is a good thing, because your controllers suspend, because you don't want to your clients to actually use the whole thread to wait for the response. Your services suspend and your repositories suspend too. Everything is reactive, everything is synchronous in this pipeline, and, well, you just don't think about it!

Let's talk about repositories. You probably know Spring Data JPA repositories, I won't talk about them.

There are coroutine repositories too, and they are used in the very same way. For example: this example's from the documentation. interface user, but I have this example in my code too. It implements CoroutineCrudRepository from User and Long. If we look at CoroutineCrudRepository, it looks like other Spring repositories, but signature functions are slightly different. First of all, it has, for example, Save,

and this is a suspend function, which is cool. It means that we shouldn't actively wait until our entity will be saved. When it will be saved, it will return the Id of the saved entity. Or, for example, findAll.

This is something new, we see Flow here. Flow is like Flux in Reactive, it's an async unbounded collection. And since it's implemented on top of Reactor project, it also handles back-pressure and all these kinds of stuff for you. Now let's add some functions to our repository, for example: findAll (oh, sorry) find One. It will just return the user, but it's a suspend function, we don't have to write any magic for it to work, it will just work. Or findByFirstname. There can be multiple users with the same first name, obviously.

And it will return a Flow of users and we can call it only from coroutine context, because, well, Flow is also an asynchronous String. Or, for example, FindAllByFirstName. This will work too because well if we believe in ourselves everything is possible. For example, returning List instead of Flow, because probably you don't want to deal with all this asynchronal stuff at all, and you are fine with collecting everything in your memory. I am this kind of human. Function is either suspend or it returns Flow. Obviously. And transactions work exactly the same way! For example, we instantiate this user,

then we save it to our repository, then we create a copy of this user. We are not bound to by any JPA limitations. User can be actually a data class, it's perfectly fine. This line will throw an exception and everything will be rolled back. Nothing will be saved. Now, configuration. It is actually my favorite part, because nobody knows it.

Configuration. Beans. Arbitrary logic

But I will ask just in case. Who uses Kotlin Bean Definition Configuration DSL? Nice! Nobody knows it. Or nobody uses it, I don't care. So, in Java we should have these configuration classes. In Java with Spring we should have these configuration classes, we should define beans in them, each bean is a function that is notated with notation bean, I don't like it. Here is how we do this stuff in Kotlin. We have this value beans, and we can just write bean, and we can write Lambda that will produce our bean.

That's all. I mean, in Java it'll be 4 lines of code! @Bean, then we should return something, (and in Kotlin without bean configuration DSL too obviously), and the very function signature. So, 4 lines instead of 1!

Or let's say we need a custom bean, for example, this Json logger. It accepts object Mapper as its argument, and it does some stupid logics. I just wanted to write something in here. And for it to work inside our bean configuration DSL we can write it this way: bean(::JsonLogger) We put here a reference to constructor of our JsonLogger, and it just works even though JsonLogger actually requires an argument of type of object Mapper. Spring will find this object Mapper inside context and provide it to the constructor, which is to me very nice! Or we can use any arbitrary logic here. For example, this again: very stupid example, but it will be virtually impossible to write in Java with this annotation bean. We have all this smart conditional missing bean and so on, but here I write an actual code whereas my bean should be lazy or not. I mean, can you do it in Java? I'm not sure. I'm not even sure if you can do it in XML.

You can do it in Groovy, but I think nobody here uses Groovy. Don't tell me, I don't want to know.

So yeah, if Random.nextBoolean(), then our bean will be "Norway", else "Well". Whatever. Okay, how do we use beans initialized this way? Because when we use Java bean configuration DSL or Java bean configuration, it's been injected automatically, right? We should return to our main function here, run application with sample application, something.

And we also have our beans defined. Let's change it this way: actually, runApplication accepts lambda,

with which we can customize something inside spring-context. For example, we can addInitializers our beans. Yeah, like this. And we will run our application, and we will see our usual started Spring application in 1.7 seconds, which is very nice. It kind of means it works. Woo! But let's test it. We'll create a random component for the sake of test. I'll call it MyBean, and it accepts our Json logger we created earlier. And it has very dumb test again, because I just wanted to write some code. I like writing code. And now we write our usual test: SpringBootTest, we @Autowired our bean, then we create test, assertEquals something, launching and? What? No qualifying bean of type JsonLogger. But why? We have initialized our application. We explicitly said add initializers! I mean, what's going on? That is because our tests do not actually know anything about our main function, right? They do not call main function, they initialize spring-context in their own sophisticated way. Which is almost the same as our main function but who cares. So, to actually make this bean configuration work we need to do a little something. I don't like that we should do it, but what we going to do, it's Spring. There are workarounds for everything. We should create a class extending ApplicationContextClassInitializer of GenericApplicationContext. Welcome to the Java World! Well, in C++ it would be worse probably. And we have to overwrite one function "initialize", that again accepts generic application-context, and then we call beans.initialize(context). It's not that much of a code and you pretty much can just copy-paste it into your application, if you use Kotlin Bean Definition DSL. And after that you put into application.yml the small line context.initializer.classes. Here you cannot just copy-paste it, you should replace my package with your package, and put Beans.Initializer there. But shouldn't be that complex, I believe.

And after that we remove this line there, and we launch our application as it was launched before.

Just run application runApplication(*args) and it just works. After that it just works. I mean, no downsides here: much less code, arbitrary logic in your bean definition.

Beautiful! Now to the most painful and the last, I think, topic of our talk today:

Security in Spring

Spring Security. Who works with Spring Security? Who likes to work with Spring security?

I feel you! I mean, well we don't have anything else, right? They force us. But even Spring Security is better, even Spring Security is better with Kotlin Bean Definition DSL! Look at this. It's the same beans we defined earlier, but now we want to add a security bean. Here is how it looks. Here is how it starts. We have val http = ref(). I know it's hard to understand this line, took me some digging, so I will save it for you.

What it does: at initialization time it finds the bean of type httpSecurity in context, if it's not initialized yet it initializes it with all the dependencies and so on,and after that http, the very http we just created on line 8, on line 9, we can use it as a receiver of our lambda, so it has a function "invoke" defined on it,

and it which accepts one lambda, so we can write it like this: http. And then we set up our http all the way we want. We put csrf {disable ()}...Do not do it in production! I mean, seriously. httpBasic, securityMatchers, all this dark magic of Spring Security, authorizeRequests, and so on. And after that we say: http.build() And let me say: to me is just significantly more beautiful than what we have in Java and in regular DSL, where we put "lalala", and then something complex is going on, like with this authorized request, and it's barely readable. And when you run auto formatter on it, which I do all the time,

it makes it even less readable! In Kotlin auto formatter won't make anything worse. And I love it! So, what did I learn and probably you learned something with me today? First of all, always generate the project with start.spring.io Well, it's a joke. If you have your own template, that's fine. Reified generics might make an API better. It's the way, it's the thing you can use in your own code when you use any third-party libraries. Validation is better with Kotlin. You shouldn't put NOT NULL on every argument you use,

I wanted to put a word there, but I will skip it. data classes should not be used with JPA. That's a bad idea it won't make any good, really, even though data classes

are nice by themselves. JDBC is also simpler with Kotlin, because it's just more readable, especially for big queries, because you can put Mappers at the end of your function call, not at the beginning.

R2DBC becomes simpler with coroutines, routines because you don't have to mess with all this Flux and Mono monads! You just use suspend functions and that's all. It becomes more or less transparent.

Bean definition DSL is really awesome. I mean, just so much readable, so much more efficient. I didn't compare timing, like how it affects your startup. I don't know. It shouldn't be that important, but if it's critical for you, please benchmark. But with security it makes even more sense, because with security we can see how nicely we can embed one DSL into another DSL. Which is how it should be in Kotlin. DSL is actually a Kotlin thing.

Summary

As a seasoned developer, you're likely already familiar with Spring. But Kotlin can take your developer experience with Spring to the next level! Join this session and learn how to add new functionality to existing classes with Kotlin extension functions, use Kotlin bean definition DSL, improve third-party libraries with varargs, and leverage coroutines with Spring idiomatically. By the end of this talk, you'll have a deeper understanding of the advanced Kotlin techniques that are available to you as a Spring developer and be able to use them effectively in your projects.

Social Media

Tags

Videos
card image
Nov 1, 2024
An Overview of Java Garbage Collectors

Java provides multiple garbage collectors (GCs) tailored to different performance needs. Serial GC is ideal for single-threaded apps but pauses all threads, while Parallel GC uses multiple threads to prioritize throughput.

Videos
card image
Oct 24, 2024
5 Tips for Optimizing Java Performance on Kubernetes

If your Java apps in the cloud struggle with high resource consumption, frequent container restarts, or slow response times, these five tips can help enhance their performance. First, set CPU and RAM limits properly based on load testing and account for Kubernetes overhead.

Further watching

Videos
card image
Nov 29, 2024
OpenJDK Projects That We Anticipate

OpenJDK is actively evolving, with projects like Leyden, Valhalla, Babylon, and Lilliput aiming to enhance Java's performance and capabilities. Leyden focuses on faster startup and warmup by reusing precompiled code, while Valhalla introduces value objects, primitive classes, and specialized generics for better memory and runtime efficiency.

Videos
card image
Nov 22, 2024
Reducing Java Startup Time: 4 Approaches

Java application startup can be significantly accelerated using modern tools. AppCDS stores preloaded classes in a shared archive, cutting startup time by up to 50%, while Project Leyden shifts optimizations to earlier stages with ahead-of-time compilation. GraalVM Native Image creates standalone executables for sub-second startup, and CRaC restores pre-warmed application states for instant readiness.

Videos
card image
Nov 15, 2024
Boost The Performance and Security of Your Spring Boot App with Alpaquita Containers

Alpaquita Containers offer a secure, high-performance solution for running Spring Boot applications in the cloud. These lightweight containers, built on Liberica JDK Lite and Alpaquita Linux, optimize memory and disk usage, reducing resource consumption by up to 30%.