I used Java primarily for my development. I tried a few different languages (yes including C# 😬) but none of those languages were able to take the place of Java. Even though I am good at writing Java applications, but deep down I felt bored, and I am doing the same thing over and over again. Yes, Java 8 was able to change it a little bit, new stream API and lambda are cool, and it felt fun… for a while.
Things got worse when I started to work on microservices. I can say Ok for a monolithic Java servlet application taking 4 or 8 GB of memory. And startup time is also not a big concern. Those were meant to be run continuously for months or maybe years. But a microservice taking 2 GB and taking more than 2 seconds to start is unacceptable. Is it truly a microservice(it can be a microservice from the domain perspective)?
There are many other concerns. And I am not the only one having these concerns.
Even though I mainly work on business-related applications, I wanted to write CLI tools, libraries, and network applications. But Java is an obstacle for me to do what I really love.
Rust
There are many things I like about Rust. And some of these things are not related to writing business applications.
Great for CLIs
Yes, no one is writing CLI tools using Java right? Do we want to use a CLI that takes more than 60 MB on the disk(including the JRE)? With Rust, you can write small, fast, efficient CLIs.
Better resource utilization
We all know Java is a memory-hungry beast. Memory management in Java has improved a lot compared to what we had in Java 8. But that is not good enough.
As a POC, we converted one of our simple applications to Rust. There we did not care about writing the best-performing code. And we ran a perf-test with 4X load. The results were impressive. We were able to run the application under 8 MB, compared to Java (850MB). This is a huge improvement. The funny thing about the test was our Rust application was not the one that used most of the memory; it was the Dynatrace agent.
We observed the same for the CPU also. During the test, our Java application took 161 mili cors and the Rust application took 11 mili cors.
Easy Concurrency
With Rust, you can write concurrent applications more easily than Java. You have two options, Message passing and Shared concurrency(one we love to hate). Normally, shared concurrency is difficult to implement. In Java, there is no way to know whether that object is shared between threads or not just by looking at the variable and there are no compiler restrictions to share a variable between threads also. These two things alone make it hard to write concurrent applications. You have synchronized blocks, and lock objects ( in the concurrency package), But these things are sitting outside of the object that you want to share and protecting access to the object.
The type system of Rust makes it easy to write concurrent applications. If you want to share the memory, you can use Arc; if you want to mutate, you can use Mutex with Arc. Just by looking at the type of the variable you know whether the memory associated with the variable is shared or not.
I can easily say that I can write concurrent applications way more confidently in Rust than in Java. This is 9 months of experience overtaking the 10 years of experience.
Easy Asynchronous programming
Asynchronous is built into the Rust. We have async(syntax sugar) and await keywords that make it easy to write asynchronous code. There are very few little things we have to worry about when doing asynchronous programming. Tokio is the de facto standard async runtime and it comes with many supporting tools. Actix-web is the most stable and most popular web framework for Rust. It has been designed for Asynchronous programming.
Why Java is still alive?
I have kept hearing, “Java is dead” for many years. But Java is still very much alive and one of the most popular programming languages. It is widely used in business applications. There are many good reasons for that as well.
Easy to learn and understand
Java is very easy to understand, it has a simple type system. Anyone with basic programming knowledge can understand most of the complex codes. The only way you can write complicated code is by creating a deep inheritance hierarchy and doing work in the constructors. But that is bad programming and unfortunately, it is common. “Easy to learn and understand” is important because it helps us to onboard new developers to the project and deploy new features quickly. When we can deploy new features quickly to the customer we get competitive advantages over others.
There are three things that I consider when writing an application.
- How quickly we can deploy new features
- How easily we can make changes to the existing code
- Performance
I am more concerned about the first two. If we can deploy new features quickly and make changes more easily we can make our customers happy and ultimately it will benefit the business.
I’m planning to write a complete article about these three.
On the other hand, Rust is not that easy because it is different. It takes time to understand ownership, borrow checker, and lifetimes. The type system of Rust is excellent, that is one thing that I like. But sometimes it is too overwhelming. Newcomers will find it challenging to understand the code.
The compiler is annoying at the beginning. You will get different kinds of compiler errors. When you become mature, you will get to know why you got those errors. But newcomers will not like this.
Ecosystem and Libraries
There are more than 11 million libraries available in the Maven. Most of those libraries are well-mature and easy to use. You can find a library for all of your requirements.
If you are using Spring you know how easy to write a Spring boot application. Spring has a great ecosystem, you have spring-web, spring-security, spring-data, spring-cloud, spring-integration, etc… There are many other libraries designed to work with Spring. Spring Boot makes it easy to write Microservices; you just add a few Annotations and your service is ready to go. Spring Data works like magic(though I hate magic in my code ). You rarely have to write a custom query, you can define a method in the repository interface and Spring Data will generate a query by looking at the method signature.
These are a few examples of Java libraries and ecosystems.
Rust has just over 118K libraries(at the time of writing). Some of those libraries are not mature enough or can not be used directly to write business applications.
Actix-web is the most stable and popular web framework for Rust. The performance of Actix-web was at another level when we compared it to the same Java Spring Boot application. But the amount of initial effort we have to put in is huge compared to Spring Boot. Spring Boot provides the auto-configuration; we just have to define the configurations in the YML and Spring Boot can use that to configure itself. This reduces the amount of work we have to put into setting up the services and helps developers to focus more on the business logic.
The rdkafka is the most popular Kafka client in Rust. This library is a wrapper on top of the Kafka C library. On Linux, we simply have to install the Kafka C library, but on Windows, it does not work like that; we have to enable a feature to compile and link the Kafka C library. Sametime rdkafka does not convert the message to Rust struct automatically. We have to use serde for message passing in the Kafka consumer, also we have to write the loop to get new messages.
Since I’m more into IDAM I have to work with LDAP. Rust ldap3 is the most stable LDAP client library. But that is also too low-level to use in a business application. I had to create a wrapper on top of the ldap3 library. For me, that was fun work but not for everyone.
I believe these two are the main reasons that keep Java alive. That is why most of them select Java for business applications. Ultimately these two boil down to one thing. With Java, we can release new features and make changes quickly. That is what businesses want.
Are these really big concerns
Most of these things are valid concerns when you are creating a new service. Because at this point you are spending most of the time doing pure technical things like creating DB connections, converting Kafka messages to objects, connecting Redis, implementing custom circuit breakers for your requirements, etc… Once you are done with dealing with these technical things then most of the time what you are going is working on business logic. After that, the challenging thing you will face is the borrow checker and lifetimes, a trick that you can do to avoid these two until you are familiar with these concepts, you can pass the ownership to the method and return it from the method again.
For those technical things, there are a few things we can do. One way is to create wrappers around low-level libraries that can give high-level features. For example, Spring Boot directly maps messages in Kafka into Java objects and we don’t have to poll messages. We can do the same thing for Rust, create a high-level easy-to-use wrapper around rdkafka. So developers can easily use the library without worrying about low-level things.
Same time we can create a set of libraries for common tasks that developers have to do in every project. This is not common for Rust we did the same for Java as well.
Even though we have these concerns I already make up my mind to become a full-time Rust developer. Because Rust is fun to use, the things I can do with Rust are huge. This may be the last year that I’m doing any kind of development using Java.