Nearly all SQL Database access goes through it and it won't be rewritten in any practical time scale. It can't be meaningfully async without a rewrite and kotlin's coroutines can't change that.
With virtual threads that synchronous api can be used at the performance level of what a hypothetical JDBC rewite would give.
(Not that you want to hit your DB that much, but you know what I mean. You don't need to specially wrap or bundle apis to get into an async call juggle.)
I do think there is something great about being able to continue to leverage existing JDBC-related tooling, given that it already exists.
[1]: With one caveat: Libraries which call directly into blocking native calls do not become async automatically. But they are very rare on the Java platform.
If you are already using co-routines, there's very little you will need to do other than updating the library when they add the support for this. Other than that, it indeed already has essentially all of the feature set that Loom adds to Java. So, do several other frameworks like Spring's flux, vert.x, rx java, etc. Kotlin's co-routine framework actually has extension functions for all of these as well as things like threadpools, worker threads, etc. in Java.
The value of Loom is mainly adding low level JVM support for this. Other frameworks will evolve to use that as well. The Java API to this is probably not that interesting; even if you do use Java. You probably should be using something a bit higher level. There are quite many frameworks for Java implementing all sorts of ways to do the same things. All Loom does is add a few useful things for these frameworks to do their thing a bit faster.
Kotlin's co-routine library is actually a multiplatform library that works in natively compiled kotlin on IOS/linux/windows/etc. (and soon wasm), in kotlin-js in a browser or node.js, and in the JVM. On each of these platforms, it uses a platform specific implementation as well as ways to seamlessly interact with platform specific things (e.g. promises in a browser, futures, threadpools, spring fluxes, etc. on the JVM). So, how co-routines are executed and scheduled is platform specific. The resulting code tends to be very portable and look very similar. I actually maintain a few multi platform kotlin things that support co-routines and they work great on both the jvm and in a browser.
When Loom releases, it will just be yet another thing that co-routines can be executed with. On the JVM, you can trivially create a co-routine dispatcher from a thread pool. A co-routine dispatcher is basically where you schedule your suspend functions to run. Loom basically just provides alternate threadpool and thread implementations that follow the same API that use the underlying jvm functionality (which is the really interesting thing it adds). So, the changes for Kotlin's co-routine library to support this are going to be pretty minimal. In fact, a lot of its current extension functions should just work against the Loom API without any changes.
There's nothing in the Loom apis that is particularly interesting if you use co-routines but the low level JVM optimizations are of course interesting from a performance point of view. Also interesting is the improved ease of dealing with stack traces; which can be a bit of a pain point with co-routines on the jvm. So, lots of good reasons for Kotlin users to look forward to this but no big reason to wait for it. You can use co-routines right now and the same code will continue to work great when Loom gets there in an LTS version in a few years. But it might run a little faster and be a bit easier to debug.
There's only 1 co-routine related keyword in Kotlin: 'suspend'. The rest of co-routines ships in the form of a normal kotlin multiplatform library that you just add to your project. Java has several keywords that you don't need in Kotlin (e.g. synchronized, volatile). You can mark things as synchronized with a simple kotlin function. And likewise there's a Volatile annotation. Mostly that's just useful for kotlin code that needs to be used from Java.
The function coloring bit seems to obsess a lot of people. I just don't see the issue with it. For me the boiler plate that Java makes you use instead is a much bigger issue.
While there's nothing wrong in using something higher level, the main reason for using higher level libraries are because using threads doesn't scale past a certain point.
The problem with libraries that try to work around the scalability issue is one of compatibility. Using co-routines is great, but you need workarounds when targetting JDBC or a Http client. You have to be using coroutines, or coroutine friendly code, everywhere, or you risk reducing performance.
For this reason, we just use regular threads and threadpools in our projects. Once Loom lands we'll change the executors to spawn virtual threads and then we'll be done.
That said, coroutines shine if you can't wait for Loom to land or if you're targetting something other than the JVM (like Android).
I've used co-routines with:
- spring flux
- hibernate & jdbc (not a problem, you just use your connection pool as usual and use a threadpool based coroutine context to ensure you don't block your asynchronous request handling main thread. You might prefer switching to some more modern versions of that that support asynchronous IO though. But that stuff is still relatively new and many people stick with blocking IO for this for now. Not in any way a show stopper for using co-routines.
- apache httpclient with synchronous IO and a connection pool (same thing basically).
- apache httpclient with asynchronous IO (very easy to integrate it's callbacks with suspendCoroutine { ... })
- redis (both synchronous and asynchronous)
- mongodb
- Java stream API (there are loads of Java libraries that use that)
I get it, some people don't want to switch language. So, stick with Java. But do it for the right reasons. Performance really isn't one. Anything Java does, Kotlin can do too. And generally about as fast by virtue of simply using the exact same things in the standard library. And generally without the boilerplate and a lot of syntactic sugar (i.e. it's nicer). Probably if the Kotlin version of something is slower than the Java version, it's because you are doing it wrong. I can't really think of a lot of cases where that would not be easy to fix.
I've seen my fair share of buggy Thread based Java logic over the years; trust me co-routines are way nicer than that. You can still get into trouble with them of course (and mostly for the same reasons). But it covers all the bases pretty well and generally does a better job of keeping you out of trouble.
What I’m referring to is this:
> you just use your connection pool as usual and use a threadpool based coroutine context to ensure you don't block your asynchronous request handling main thread.
For everything that is not coroutine compatible out of the box, you need to do something like what is described above.
When porting an existing application to coroutines, you also have to identify the places you need to introduce thread pool backed contexts and use them approprietly. You also need to setup these thread pools correctly so that you don’t end up using more threads than if you didn’t target coroutines.
For us, this extra complexity wasn’t worth it. If we were making an app from scratch and could choose coroutine friendly solutions for everything from the start, that would be different, but porting a legacy app to coroutines hasn’t been a good experience.
Fortunetly, project loom means all our current thread-based code will be just as scalable as coroutines in the not-to-distant future. That also means we can spawn more (virtual) threads and see a nice speed increase as well.
Btw, we’re already using kotlin for most code, just not coroutines.
But just so you know, what you are describing is code that is currently boiler plate heavy in Java and essentially making it a lot simpler and easier to reason about with Kotlin. I think you are confusing problem and solution here.
In any case, this is how you create a CoRoutineContext backed by a thread pool.
val myContext = newFixedThreadPoolContext(100,"db-context")
That's it. There's nothing else to do but "use it". There's no need to call shutDown. Or have some thingy to intercept uncaught exceptions. Or have a lot of boiler plate around using it. a simple launch(myContext) { ... } does the job.Most of the time, we do calls to .execute and then join the different threads at the end. The code running within the threads represent the majority of our code, so the boilerplate you're talking about might 10 out of 2000 lines.
Our only problem is the number of threads we can spawn safely, and that limit is removed with loom.