The Java programming language is well into its third decade, and the language and its byte code have found a home in everything from embedded chips to massive server farms.
Java's combination of a rock-solid virtual machine and a large collection of libraries make a fertile ecosystem for writing code that runs everywhere.
One area where Java has struggled, however, is the world of servers, which often must juggle connections from thousands or even millions of users. In the early years, Java tools were among the best for creating server-side applications that enforced business logic for all users.
Java frameworks like J2EE, Hibernate, Spring, and the basic Java servlets model made it relatively easy to create strong web applications.
There were generally two reasons: First, developers welcomed the opportunity to run the same code on the server and a browser client. Second, Node.js servers often delivered dramatically faster throughput, thanks to their reactive model.
Early Java frameworks for the server had one limitation: each incoming request was given its own thread. This was a clean way to organise incoming and outgoing data, but it was also taxing.
Creating a thread takes thousands of bytes of overhead, which could limit the number of users each server could handle. Node.js used a different model that allowed it to juggle many more users without this overhead.
More recently, Java developers have brought innovations from Node.js to the Java stack, particularly cloud-native Java frameworks. These frameworks imitate Node.js's approach and support lightweight functions that run on cloud machines and can start and stop quickly. They dispense with extra libraries to support rapid deployment on the thinnest server instances available.
Cloud-native Java frameworks are designed to support constellations of microservices that can be installed and restarted independently. They typically ship in containers like Docker or Podman for the fastest possible builds and installations.
Modern Java developers seeking a cloud-native experience have an array of options. An ideal cloud-native Java framework leverages the deep experience invested in the Java platform and its third-party libraries while adapting them to run faster and lighter in the cloud. Here are eight Java frameworks built from the ground up for cloud-native development and deployment.
The creators of Micronaut wanted to take the best parts of classic Java frameworks like Spring and Grails — such as flexible configuration and dependency injection — but strip away the heavy memory footprint and slow startup that made them less desirable for developing microservices.
They carefully designed annotations that provide enough information for dependency injections without the memory-filling reflection used in older frameworks. Getting more of Micronaut's configuration done at compile time means the code runs faster and lighter.
The framework is built to support a variety of JVM-based languages (currently, Java, Kotlin, and Groovy) and run them across various clouds. Predefined configuration files simplify deploying the server or serverless functions on all the major clouds, and there are well-written documentation pages for all the major database connections.
Micronaut's developers also want the framework to support good development teamwork. An HttpClient implementation is bundled with the project to simplify writing unit tests without leaving Micronaut or adding more work. These tests are often simpler and more comprehensive than the tests required for dynamic frameworks. This is, again, thanks to the work done at compile time.
Micronaut is not only for developing applications with cloud functions. The framework is general enough to support traditional roles and some desktop applications. Its tight integration with GraalVM makes it possible to use Micronaut to generate native applications.
Developers who want to use a well-understood mix of imperative and reactive code can turn to Quarkus. The Quarkus team started by anticipating the most common use cases for cloud-native development, then built the framework with examples that support those use cases with as little as zero configuration. The result is easily rolled into a container and deployed in a Kubernetes cluster.
The development team paid particular attention to ensuring fast boot times so Kubernetes clusters can scale up quickly. This is an ideal feature for functions that run sporadically because they can be left cold until they’re invoked.
One of the project's goals is to embrace and extend many existing standards and libraries that are common in the Java community. For example, JAX-RS annotations define the REST endpoints. The configuration begins with Eclipse MicroProfile. Quarkus's development team also integrated more than 50 standard libraries, so there’s a good chance you’ll recognise the design patterns in certain cases.
You can use the basic Quarkus framework for a variety of services. Starting with Quarkus 2.8, Quarkus' developers are gently encouraging the RESTeasy Reactive model. It is the standard option if you are starting a new project, but you don’t have to use it.
RESTeasy Reactive offers a simpler, non-blocking structure and patterns. Instead of assigning one thread to each request, a set of non-blocking threads handles all the I/O and invokes your code when needed.
Quarkus also embraces a wide range of deployment options. While it's said to be “container first," it can run on bare metal. There’s also a built-in configuration option called Funqy that simplifies creating the functions accepted by AWS Lambda, Azure Functions, Knative, and a few other options.
Spring Cloud Functions
Java developers are well-acquainted with the Spring framework because it’s been the foundation for many projects for around two decades. Spring's developers decide to create a new version that’s better suited for cloud deployment, as well as some other roles. The functions in Spring Cloud Functions are meant to be easily redeployed to a variety of tasks like web services, stream processing, or background work.
The Spring Cloud Functions framework continues many of the same philosophical traditions pioneered by Spring. Cloud functions in this framework support a reactive or imperative style, as well as a hybrid mixture of both.
Supporting a wide variety of options is a big goal for the project. There are adapters that shoehorn the functions into AWS Lambda, Microsoft Azure, Apache OpenWhisk, Google Cloud Platform, and a few other common cloud function environments.
There are also adapters for major streaming frameworks like Apache Kafka, Solace, and RabbitMQ, as well as the standalone option Spring Cloud Stream. Packaging and deployment is heavily automated so you can concentrate on developing the functions themselves.
The Spring Cloud Functions development team also worked hard to handle many of the common pitfalls and challenges of cloud deployment.
Spring Cloud Skipper can be used to juggle deployments across multiple clouds. Spring Cloud Sleuth helps with debugging by tracing data flows. Spring Cloud Security manages many of the chores for securing an application so that only the right people can execute the functions. There are several dozen different sub projects alone.
The project is a very good foundation for distributing business applications through a variety of platforms. Once your application logic is encapsulated into a Cloud Function POJO, it can find a home working in dozens of different roles.
The creators of Vert.x wanted to create a very fast framework by simplifying the event loop and optimising the connection with the database.
Vert.x has a single event loop like Node.js, which allows it to juggle multiple connections as the events arrive. It also takes advantage of Java’s threading model to process events with multiple threads in a pool, which may run on multiple cores if they’re available.
The structure is also planned to simplify creating the pipeline to process an event stream. It borrows constructs like promises and futures to avoid messy code with layered callbacks. The asynchronous options help produce clean, readable code filled with simple chains of method invocations as the events move along the event bus.
The Vert.x development team not dogmatic about their vision. They often say that Vert.x is a toolkit not a framework. The code is modular so you can pick and choose which features to use and assemble an architecture that fits your application. Programmers who want more of an imperative structure instead of a reactive one can find support for Kotlin’s coroutines.
This project is part of the Eclipse ecosystem. A variety of versions and options offer plenty of freedom. The Vert.x application generator, for instance, will produce either Java or Kotlin code with dozens of potential dependencies like template engines or API support.
The Eclipse team created the MicroProfile project as a way to adapt Jakarta EE to run smaller constellations of microservices. It strips out some of the larger platform's overhead while bundling together libraries that are pretty much standard for many microservice architectures.
The approach is most attractive for developers who might be migrating code from larger, older Java EE or Jakarta EE projects. Much of the configuration and architecture remain the same. In many cases, the adjustments are minor.
But the design encourages the kind of decisions that make it simpler to create lighter weight, faster code. Some developers use MicroProfile as a stepping stone on the way to more modern cloud-native frameworks.
Some developers have a natural affection for older, well-tested modules and they’ll enjoy Dropwizard. Dropwizard's development team has always emphasised words like stable and mature. They collected modules for database connections like Hibernate and mixed in frameworks for form and other standard web application components.
Dropwizard also streamlines dependency injection and runtime maintenance processes such as configuration and logging.
Dropwizard is a favourite for teams working to revise and extend an existing application. The structure is compatible with the older, mature approaches because it is built upon them.
Starter frameworks for cloud platforms
Sometimes, there’s no need for something complex or elaborate. All of the clouds maintain basic examples that are good places to start writing simple functions. They’re mainly designed to support very simple decisions and help developers start up quickly.
As an example, Google Cloud Platform's development team open-sourced their basic framework for Java functions that run in its function-as-a-service (FaaS). The code built using it is meant to integrate quickly with GCP's standard triggers although it can also run successfully on any local machine.
Microsoft also open-sourced its framework for Java. The model includes several routines for simplifying data transfers like a library for translating JSON data into and out of Java POJOs. If the function trigger supplies metadata with the invocation, the framework handles it directly.
Both of these frameworks let you accomplish many simple chores by just writing a single class with a single function. More complicated projects may want to merge this basic tool with some of the other frameworks I've described. These are just starting points, but sometimes that’s enough.