git clone https://github.com/openliberty/guide-jakarta-concurrency.git
cd guide-jakarta-concurrency
Running tasks concurrently or asynchronously in Java microservices
Prerequisites:
Learn how to run tasks concurrently or asynchronously in Java microservices by using Jakarta Concurrency.
What you’ll learn
Jakarta Concurrency enables the creation of managed executors to run tasks in separate threads with access to the context of the submitter. It also enables the creation of managed thread factories to create threads that run with the thread context of the component that looks up the managed thread factory. The threads are managed by the application server and safely interact in the Enterprise Java Beans and Web containers. To learn more about Jakarta Concurrency, see the Jakarta Concurrency specification.
The application that you will build provides REST APIs to retrieve system properties and write information about system loads, including CPU load and memory usage, to the database.
You will update the application to learn how to run tasks in parallel by enhancing the GET /api/system/properties/{prefix} endpoint that retrieves system properties with the provided prefix. You will learn two different ways to create asynchronous task by implementing the GET /api/system/systemLoad/cpuLoad and /api/system/systemLoad/memoryUsage endpoints that calculate the CPU load and memory usage and persist the data to the database. Also, you will learn how to create a scheduled asynchronous task that calculates and records the current system load to the database for every 10 seconds.
Getting started
The fastest way to work through this guide is to clone the Git repository and use the projects that are provided inside:
The start directory contains the starting project that you will build upon.
The finish directory contains the finished project that you will build.
Try what you’ll build
The finish directory in the root of this guide contains the finished application. Give it a try before you proceed.
To try out the application, first go to the finish directory and run the following Maven goal to build the application and deploy it to Open Liberty:
WINDOWS
MAC
LINUX
cd finish
mvnw.cmd liberty:run
cd finish
./mvnw liberty:run
cd finish
./mvnw liberty:run
After you see the following message, your Liberty instance is ready:
The defaultServer server is ready to run a smarter planet.
Go to the http://localhost:9080/api/system/properties/os URL to see all the os.* properties. You will see an output similar to the following example:
{
"os.arch": "aarch64",
"os.name": "Mac OS X",
"os.version": "15.4.1",
"os.encoding": "UTF-8"
}
If you are interested, you can check out the GET /api/system/properties/{prefix} endpoint by the http://localhost:9080/api/system/properties/user and http://localhost:9080/api/system/properties/java URLs.
Check out the application with the http://localhost:9080/api/system/systemLoad/cpuLoad and http://localhost:9080/api/system/systemLoad/memoryUsage URLs. Both requests return to you immediately with a message. After 5 seconds, go to the http://localhost:9080/api/system/systemLoad URL to see all the system loads. You will see an output similar to the following example:
[
{
"id": 1,
"time": "2025-05-06T15:30:06.308192",
"cpuLoad": 5.074851387343854e-06
},
{
"id": 2,
"time": "2025-05-06T15:30:08.732892",
"memoryUsage": 0.7126722484827042
}
]
Go to the http://localhost:9080/api/system/schedule/toggle URL to toggle the schedule that records the current system load in every 10 seconds. After 10 seconds, go to the http://localhost:9080/api/system/systemLoad URL to see all the system loads.
[
...
{
"id": 3,
"time": "2025-05-06T15:34:30.001636",
"cpuLoad": 1.8270538029075587e-06,
"memoryUsage": 0.6712260656058788
},
...
]
After you are finished checking out the application, stop the Liberty instance by pressing CTRL+C in the command-line session where you ran Liberty. Alternatively, you can run the liberty:stop goal from the finish directory in another shell session:
WINDOWS
MAC
LINUX
mvnw.cmd liberty:stop
./mvnw liberty:stop
./mvnw liberty:stop
Running tasks in parallel
Starting to learn Jakarta Concurrency, you will learn how to use the ManagedExecutorService to run tasks in parallel.
Navigate to the start directory to begin.
When you run Open Liberty in dev mode, dev mode listens for file changes and automatically recompiles and deploys your updates whenever you save a new change. Run the following goal to start Open Liberty in dev mode:
WINDOWS
MAC
LINUX
mvnw.cmd liberty:dev
./mvnw liberty:dev
./mvnw liberty:dev
After you see the following message, your Liberty instance is ready in dev mode:
************************************************************** * Liberty is running in dev mode.
Dev mode holds your command-line session to listen for file changes. Open another command-line session to continue, or open the project in your editor.
SystemResource.java
start/SystemProperties.java
server.xml
The starting application provides the /properties/{prefix} endpoint. Go to the http://localhost:9080/api/system/properties/java URL to see all the java.* properties. Notice that the URL takes several seconds to get the result. Each getSystemPropertyTask() call takes at least a second.
To improve the performance, enhance the getProperties() method to call the getSystemPropertyTask() method in parallel.
The Jakarta Concurrency feature is enabled for you in the Liberty server.xml configuration file.
Update the SystemProperties class to use Jakarta Concurrency managed executor service to provide a way to start asynchronous tasks within an application server environment. It propagates various thread contexts that are relevant to Jakarta EE applications.
SystemProperties.java
Replace theSystemProperties.javafile.src/main/java/io/openliberty/guides/system/SystemProperties.java
The SystemProperties class is annotated by the @ManagedExecutorDefinition annotation to provide a managed executor instance through the ManagedExecutorService injection point.
The getProperties() method constructs all tasks for collecting different property values. Each task calls the getSystemPropertyTask() method. Then, the getProperties() method uses the managedExecutor instance to invoke all tasks in parallel. The invokeAll() method waits for all tasks to complete.
You started the Open Liberty in dev mode at the beginning of this section, so all the changes are automatically picked up.
Go to the http://localhost:9080/api/system/properties/java URL to see all the java.* properties. Notice that the URL takes a second to get the result.
Creating asynchronous tasks
Implement asynchronous tasks to get the current CPU load and memory usage.
SystemConcurrency.java
Create theSystemConcurrency.javafile.src/main/java/io/openliberty/guides/system/SystemConcurrency.java
persistence.xml
The SystemConcurrency class is annotated by the @ManagedScheduledExecutorDefinition annotation to provide a managed scheduled executor instance through the ManagedScheduledExecutorService injection point.
The @ManagedScheduledExecutorDefinition annotation and the ManagedScheduledExecutorService interface are used in this bean because you will implement a scheduled task in the following section. For more information about different mananged services, see their annotations and classes in the jakarta.enterprise.concurrent JavaDoc.
The getCpuLoad() method uses the managedExecutor service to create and schedule an asynchronous task that delays the execution with 5 seconds. The task calculates the cpuLoadData CPU load. Then, it calls the persist() method to persist the data into the database through the EntityManager interface that interacts with the jpa-unit persistence context in the Liberty runtime.
A transaction is required on a cascading PERSIST operation. Inside the asynchronous task, it retrieves the named UserTransaction object from the Liberty runtime. Then, it calls the begin() method to create a transaction and the commit() method to complete the transaction. If there is any exception, it calls the rollback() method to roll back the transaction that associates to the thread.
An alternative to create a similar asynchronous task is using @Asynchronous annotation. The getMemoryUsage() method is annotated with the @Asynchronous annotation to make it run asynchronously. The task calculates the memoryUsageData memory usage and calls the persist() method to persist the data into the database. To create a new transaction, annotate the getMemoryUsage() method by the @Transactional annotation.
Jakarta Concurrency feature plays an important role to manage the application context in order to make the jpa-unit persistence context available to the asynchronous task.
Implement two REST endpoints to run the asynchronous tasks.
SystemResource.java
Replace theSystemResource.javafile.src/main/java/io/openliberty/guides/system/SystemResource.java
The GET /systemLoad/cpuLoad endpoint is added to call the getCpuLoad() asynchronous method and returns a message. The GET /systemLoad/memoryUsage endpoint is added to call the getMemoryUsage() asynchronous method and returns a message.
Try out the http://localhost:9080/api/system/systemLoad/cpuLoad and http://localhost:9080/api/system/systemLoad/memoryUsage URLs. Both requests return to you immediately with a message.
To check the system loads after 5 seconds, go to the http://localhost:9080/api/system/systemLoad URL.
[
{
"id": 1,
"time": "2025-05-06T15:30:06.308192",
"cpuLoad": 5.074851387343854e-06
},
{
"id": 2,
"time": "2025-05-06T15:30:08.732892",
"memoryUsage": 0.7126722484827042
}
]
Scheduling asynchronous task
Starting from Jakarta Concurrency 3.1, Scheduled asynchronous methods replaces the @Schedule annotation of Jakarta Entperprise Beans.
In this section, you will implement a schedule task to get the current system load for every 10 seconds by using a scheduled asynchronous method.
SystemConcurrency.java
Replace theSystemConcurrency.javafile.src/main/java/io/openliberty/guides/system/SystemConcurrency.java
Similarly, the schedule() method calculates the systemLoadData system load data and calls the persist() method to persist the data into the database. To create a new transaction, annotate the schedule() method by the @Transactional annotation.
The schedule() method is annotated with the @Asynchronous annotation that configures the schedule with the "*/10 * * * * *" cron expression to make the task run asynchronously every 10 seconds (after the seconds of each minute that are divisible by 10). For more information about CRON syntax, see the jakarta.enterprise.concurrent.CronTrigger JavaDoc. A null value is returned to make the schedule to keep running. A CompletableFuture<> object is returned to complete the task and stop the schedule.
Implement a REST endpoint to toggle the schedule.
SystemResource.java
Replace theSystemResource.javafile.src/main/java/io/openliberty/guides/system/SystemResource.java
The added GET /schedule/toggle endpoint calls the schedule() scheduled asynchronous method if the schedule is not started.
Go to the http://localhost:9080/api/system/schedule/toggle URL to start the schedule. After 10 seconds, go to the http://localhost:9080/api/system/systemLoad URL to check out the system loads.
[
...
{
"id": 3,
"time": "2025-05-06T15:34:30.001636",
"cpuLoad": 1.8270538029075587e-06,
"memoryUsage": 0.6712260656058788
},
...
]
After you are finished checking out the schedule, go to the http://localhost:9080/api/system/schedule/toggle URL again to stop the schedule.
Testing the application
Although you can test your application manually, automated tests make sure consistent code quality by triggering a failure whenever a code change introduces a defect. Now, create integration tests for the endpoints.
SystemEndpointIT.java
Replace theSystemEndpointIT.javafile.src/test/java/it/io/openliberty/guides/system/SystemEndpointIT.java
The testGetProperties() tests the GET /api/system/properties/os endpoint and confirms that 4 os.* properties are returned.
The testGetCpuLoad() tests the GET /api/system/systemLoad/cpuLoad endpoint and confirms that the CPU load is not null after several seconds.
The testGetMemoryUsage() tests the GET /api/system/systemLoad/memoryUsage endpoint and confirms that the memory usage is not null after several seconds.
The testToggleSchedule() tests the GET /api/systems/schedule/toggle endpoint and confirms that 2 system loads are recorded after 20 seconds.
Running the tests
Because you started Open Liberty in dev mode, you can run the tests by pressing the enter/return key from the command-line session where you started dev mode.
If the tests pass, you see an output similar to the following example:
Running it.io.openliberty.guides.system.SystemEndpointIT
[INFO ] Getting the os.arch property...
[INFO ] Getting the os.version property...
[INFO ] Getting the os.encoding property...
[INFO ] Getting the os.name property...
...
Tests run: 4, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 57.06 s -- in it.io.openliberty.guides.system.SystemEndpointIT
Results:
Tests run: 4, Failures: 0, Errors: 0, Skipped: 0
When you are done checking out the service, exit dev mode by pressing CTRL+C in the command-line session where you ran Liberty, or by typing q and then pressing the enter/return key.
To learn more about asynchronous and reactive programming, check out the related reactive guides.
Great work! You’re done!
You just developed tasks that run concurrently or asynchronously in a Java microservice by using Jakarta Concurrency in Open Liberty.
Guide Attribution
Running tasks concurrently or asynchronously in Java microservices by Open Liberty is licensed under CC BY-ND 4.0
Prerequisites:
Great work! You're done!
What did you think of this guide?
Thank you for your feedback!
What could make this guide better?
Raise an issue to share feedback
Create a pull request to contribute to this guide
Need help?
Ask a question on Stack Overflow