Running tasks concurrently or asynchronously in Java microservices

duration 20 minutes
New

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:

git clone https://github.com/openliberty/guide-jakarta-concurrency.git
cd guide-jakarta-concurrency

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:

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:

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:

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

 1// tag::copyright[]
 2/*******************************************************************************
 3 * Copyright (c) 2025 IBM Corporation and others.
 4 * All rights reserved. This program and the accompanying materials
 5 * are made available under the terms of the Eclipse Public License 2.0
 6 * which accompanies this distribution, and is available at
 7 * http://www.eclipse.org/legal/epl-2.0/
 8 *
 9 * SPDX-License-Identifier: EPL-2.0
10 *******************************************************************************/
11// end::copyright[]
12package io.openliberty.guides.system;
13
14import java.util.Map;
15import java.util.concurrent.ExecutionException;
16
17import jakarta.enterprise.context.ApplicationScoped;
18import jakarta.inject.Inject;
19import jakarta.ws.rs.GET;
20import jakarta.ws.rs.Path;
21import jakarta.ws.rs.PathParam;
22import jakarta.ws.rs.Produces;
23import jakarta.ws.rs.core.MediaType;
24
25@ApplicationScoped
26@Path("/system")
27public class SystemResource {
28
29    @Inject
30    SystemProperties propertiesBean;
31
32    // tag::getProperties[]
33    @GET
34    @Path("/properties/{prefix}")
35    @Produces(MediaType.APPLICATION_JSON)
36    public Map<String, String> getProperties(@PathParam("prefix") String prefix)
37        throws InterruptedException, ExecutionException {
38        return propertiesBean.getProperties(prefix);
39    }
40    // end::getProperties[]
41
42}

start/SystemProperties.java

 1// tag::copyright[]
 2/*******************************************************************************
 3 * Copyright (c) 2025 IBM Corporation and others.
 4 * All rights reserved. This program and the accompanying materials
 5 * are made available under the terms of the Eclipse Public License 2.0
 6 * which accompanies this distribution, and is available at
 7 * http://www.eclipse.org/legal/epl-2.0/
 8 *
 9 * SPDX-License-Identifier: EPL-2.0
10 *******************************************************************************/
11// end::copyright[]
12package io.openliberty.guides.system;
13
14import java.util.HashMap;
15import java.util.List;
16import java.util.Map;
17import java.util.concurrent.ExecutionException;
18import java.util.logging.Logger;
19import java.util.stream.Collectors;
20
21import jakarta.enterprise.context.ApplicationScoped;
22
23@ApplicationScoped
24public class SystemProperties {
25
26    private static Logger logger = Logger.getLogger(SystemProperties.class.getName());
27
28    // tag::getSystemPropertyTask[]
29    private String getSystemPropertyTask(String key) throws InterruptedException {
30        logger.info("Getting the " + key + " property...");
31        Thread.sleep(1000);
32        return System.getProperty(key);
33    }
34    // end::getSystemPropertyTask[]
35
36    // tag::getProperties[]
37    public Map<String, String> getProperties(String prefix)
38           throws InterruptedException, ExecutionException {
39
40        Map<String, String> properties = new HashMap<String, String>();
41        List<String> keys = System.getProperties().stringPropertyNames().stream()
42                                  .filter(k -> k.startsWith(prefix + "."))
43                                  .collect(Collectors.toList());
44        for (String k : keys) {
45            // tag::callGetSystemPropertyTask[]
46            properties.put(k, getSystemPropertyTask(k));
47            // end::callGetSystemPropertyTask[]
48        }
49        return properties;
50    }
51    // end::getProperties[]
52
53}

server.xml

 1<?xml version="1.0" encoding="UTF-8"?>
 2<server description="system">
 3
 4    <featureManager>
 5        <platform>jakartaee-11.0</platform>
 6        <feature>cdi</feature>
 7        <!-- tag::concurrent[] -->
 8        <feature>concurrent</feature>
 9        <!-- end::concurrent[] -->
10        <feature>jsonb</feature>
11        <feature>persistence</feature>
12        <feature>restfulWS</feature>
13    </featureManager>
14
15    <variable name="http.port" defaultValue="9080"/>
16    <variable name="https.port" defaultValue="9443"/>
17
18    <httpEndpoint id="defaultHttpEndpoint" host="*"
19                  httpPort="${http.port}"
20                  httpsPort="${https.port}" />
21
22    <cors domain="/api/system/sse"
23        allowedOrigins="*"
24        allowedMethods="GET"
25        allowedHeaders="accept"
26        exposeHeaders=""
27        allowCredentials="true"
28        maxAge="3600" />
29      
30    <webApplication contextRoot="/" location="guide-jakarta-concurrency.war"/>
31
32    <logging consoleLogLevel="INFO"/>
33
34    <library id="derbyJDBCLib">
35        <fileset dir="${shared.resource.dir}/" includes="derby*.jar" />
36    </library>
37
38    <dataSource id="SystemLoadDataSource" jndiName="jdbc/SystemLoadDataSource">
39        <jdbcDriver libraryRef="derbyJDBCLib" />
40        <properties.derby.embedded databaseName="SystemLoadDB" createDatabase="create" />
41    </dataSource>
42
43</server>

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

 1// tag::copyright[]
 2/*******************************************************************************
 3 * Copyright (c) 2025 IBM Corporation and others.
 4 * All rights reserved. This program and the accompanying materials
 5 * are made available under the terms of the Eclipse Public License 2.0
 6 * which accompanies this distribution, and is available at
 7 * http://www.eclipse.org/legal/epl-2.0/
 8 *
 9 * SPDX-License-Identifier: EPL-2.0
10 *******************************************************************************/
11// end::copyright[]
12package io.openliberty.guides.system;
13
14import java.util.ArrayList;
15import java.util.List;
16import java.util.Map;
17import java.util.concurrent.Callable;
18import java.util.concurrent.ConcurrentHashMap;
19import java.util.concurrent.ExecutionException;
20import java.util.logging.Logger;
21
22import jakarta.annotation.Resource;
23import jakarta.enterprise.concurrent.ManagedExecutorDefinition;
24import jakarta.enterprise.concurrent.ManagedExecutorService;
25import jakarta.enterprise.context.ApplicationScoped;
26
27
28//tag::annotateManagedExecutor[]
29@ManagedExecutorDefinition(
30    name = "java:module/concurrent/managed-executor")
31//end::annotateManagedExecutor[]
32@ApplicationScoped
33public class SystemProperties {
34
35    private static Logger logger = Logger.getLogger(SystemProperties.class.getName());
36
37    // tag::managedExecutorService[]
38    @Resource(lookup = "java:module/concurrent/managed-executor")
39    // tag::managedExecutor[]
40    ManagedExecutorService managedExecutor;
41    // end::managedExecutor[]
42    // end::managedExecutorService[]
43
44    private String getSystemPropertyTask(String key) throws InterruptedException {
45        logger.info("Getting the " + key + " property...");
46        Thread.sleep(1000);
47        return System.getProperty(key);
48    }
49
50    public Map<String, String> getProperties(String prefix)
51           throws InterruptedException, ExecutionException {
52
53        // tag::properties[]
54        ConcurrentHashMap<String, String> properties = new ConcurrentHashMap<>();
55        // end::properties[]
56
57        // tag::tasks[]
58        List<Callable<String>> tasks = new ArrayList<>();
59        for (String key : System.getProperties().stringPropertyNames()) {
60            if (key.startsWith(prefix + ".")) {
61                tasks.add(() -> {
62                    // tag::getSystemPropertyTask[]
63                    return properties.put(key, getSystemPropertyTask(key));
64                    // end::getSystemPropertyTask[]
65                });
66            }
67        }
68        // end::tasks[]
69
70        // tag::invokeAll[]
71        managedExecutor.invokeAll(tasks);
72        // end::invokeAll[]
73
74        return properties;
75    }
76
77}
Replace the SystemProperties.java file.
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

  1// tag::copyright[]
  2/*******************************************************************************
  3 * Copyright (c) 2025 IBM Corporation and others.
  4 * All rights reserved. This program and the accompanying materials
  5 * are made available under the terms of the Eclipse Public License 2.0
  6 * which accompanies this distribution, and is available at
  7 * http://www.eclipse.org/legal/epl-2.0/
  8 *
  9 * SPDX-License-Identifier: EPL-2.0
 10 *******************************************************************************/
 11// end::copyright[]
 12package io.openliberty.guides.system;
 13
 14import java.lang.management.ManagementFactory;
 15import java.lang.management.MemoryMXBean;
 16import java.time.LocalDateTime;
 17import java.util.List;
 18import java.util.concurrent.CompletableFuture;
 19import java.util.concurrent.TimeUnit;
 20import java.util.logging.Logger;
 21
 22import javax.naming.InitialContext;
 23
 24import com.sun.management.OperatingSystemMXBean;
 25
 26import io.openliberty.guides.system.model.SystemLoadData;
 27import jakarta.annotation.Resource;
 28import jakarta.enterprise.concurrent.Asynchronous;
 29import jakarta.enterprise.concurrent.ManagedScheduledExecutorDefinition;
 30import jakarta.enterprise.concurrent.ManagedScheduledExecutorService;
 31import jakarta.enterprise.concurrent.Schedule;
 32import jakarta.enterprise.context.ApplicationScoped;
 33import jakarta.persistence.EntityManager;
 34import jakarta.persistence.PersistenceContext;
 35import jakarta.transaction.Transactional;
 36import jakarta.transaction.Transactional.TxType;
 37import jakarta.transaction.UserTransaction;
 38
 39// tag::annotateManagedScheduledExecutor[]
 40@ManagedScheduledExecutorDefinition(
 41    name = "java:module/concurrent/managed-scheduled-executor")
 42// end::annotateManagedScheduledExecutor[]
 43@ApplicationScoped
 44public class SystemConcurrency {
 45
 46    private static final OperatingSystemMXBean OS =
 47        (OperatingSystemMXBean) ManagementFactory.getOperatingSystemMXBean();
 48    private static final MemoryMXBean MEM = ManagementFactory.getMemoryMXBean();
 49
 50    private static Logger logger = Logger.getLogger(SystemConcurrency.class.getName());
 51    private static boolean isScheduleStarted = false;
 52
 53    // tag::managedScheduledExecutorService[]
 54    @Resource(lookup = "java:module/concurrent/managed-scheduled-executor")
 55    // tag::managedExecutor[]
 56    ManagedScheduledExecutorService managedExecutor;
 57    // end::managedExecutor[]
 58    // end::managedScheduledExecutorService[]
 59
 60    // tag::entityManager[]
 61    // tag::persistenceContext[]
 62    @PersistenceContext(name = "jpa-unit")
 63    // end::persistenceContext[]
 64    private EntityManager em;
 65    // end::entityManager[]
 66
 67    private void doSomething(int t) {
 68        try {
 69            Thread.sleep(t * 1000);
 70        } catch (Exception e) {
 71            logger.warning(e.getMessage());
 72        }
 73    }
 74
 75    // tag::getCpuLoad[]
 76    public void getCpuLoad() {
 77        logger.info("New CPU load will be recorded after 5 seconds.");
 78        // tag::scheduleCall[]
 79        managedExecutor.schedule(() -> {
 80        // end::scheduleCall[]
 81            // tag::userTransaction[]
 82            UserTransaction ut = null;
 83            // end::userTransaction[]
 84            try {
 85                // tag::utLookup[]
 86                ut = (UserTransaction)
 87                     new InitialContext().lookup("java:comp/UserTransaction");
 88                // end::utLookup[]
 89                // tag::utBegin[]
 90                ut.begin();
 91                // end::utBegin[]
 92                // tag::calculateCPULoad[]
 93                SystemLoadData cpuLoadData  = new SystemLoadData();
 94                LocalDateTime current = LocalDateTime.now();
 95                cpuLoadData.setTime(current);
 96                Double cpuLoad = Double.valueOf(OS.getCpuLoad() * 100.0);
 97                cpuLoadData.setCpuLoad(cpuLoad);
 98                // end::calculateCPULoad[]
 99                // tag::persistCPULoad[]
100                em.persist(cpuLoadData);
101                // end::persistCPULoad[]
102                // tag::utCommit[]
103                ut.commit();
104                // end::utCommit[]
105                logger.info("CPU load at \"" + current + "\" was recorded.");
106            } catch (Exception e) {
107                logger.warning(e.getMessage());
108                // tag::utRollback[]
109                if (ut != null) {
110                    try {
111                        ut.rollback();
112                    } catch (Exception re) {
113                        logger.warning(re.getMessage());
114                    }
115                }
116                // end::utRollback[]
117            }
118        // tag::after[]
119        }, 5, TimeUnit.SECONDS);
120        // end::after[]
121    }
122    // end::getCpuLoad[]
123
124    // tag::asynchronous1[]
125    @Asynchronous
126    // end::asynchronous1[]
127    // tag::transactional1[]
128    @Transactional(value = TxType.REQUIRES_NEW)
129    // end::transactional1[]
130    // tag::getMemoryUsage[]
131    public void getMemoryUsage() {
132        logger.info("New memory usage will be recorded after 5 seconds.");
133        doSomething(5);
134        // tag::calculateMemoryUsage[]
135        SystemLoadData memoryUsageData  = new SystemLoadData();
136        LocalDateTime current = LocalDateTime.now();
137        memoryUsageData.setTime(current);
138        long heapMax = MEM.getHeapMemoryUsage().getMax();
139        long heapUsed = MEM.getHeapMemoryUsage().getUsed();
140        Double memoryUsage = Double.valueOf(heapUsed * 100.0 / heapMax);
141        memoryUsageData.setMemoryUsage(memoryUsage);
142        // end::calculateMemoryUsage[]
143        // tag::persistMemoryUsage[]
144        em.persist(memoryUsageData);
145        // end::persistMemoryUsage[]
146        logger.info("Memory usage at \"" + current + "\" was recorded.");
147    }
148    // end::getMemoryUsage[]
149
150    // tag::enableSchedule[]
151    public boolean isScheduleStarted() {
152        return isScheduleStarted;
153    }
154
155    public void startSchedule() {
156        isScheduleStarted = true;
157    }
158
159    public void stopSchedule() {
160        isScheduleStarted = false;
161    }
162    // end::enableSchedule[]
163
164    // tag::asynchronous2[]
165    @Asynchronous(runAt = { @Schedule(cron = "*/10 * * * * *")})
166    // end::asynchronous2[]
167    // tag::transactional2[]
168    @Transactional(value = TxType.REQUIRES_NEW)
169    // end::transactional2[]
170    // tag::schedule[]
171    // tag::completableFuture[]
172    public CompletableFuture<String> schedule() {
173    // end::completableFuture[]
174        if (isScheduleStarted()) {
175            // tag::calculateSystemLoad[]
176            SystemLoadData systemLoadData  = new SystemLoadData();
177            LocalDateTime current = LocalDateTime.now();
178            systemLoadData.setTime(current);
179            Double cpuLoad = Double.valueOf(OS.getCpuLoad() * 100.0);
180            systemLoadData.setCpuLoad(cpuLoad);
181            long heapMax = MEM.getHeapMemoryUsage().getMax();
182            long heapUsed = MEM.getHeapMemoryUsage().getUsed();
183            Double memoryUsage = Double.valueOf(heapUsed * 100.0 / heapMax);
184            systemLoadData.setMemoryUsage(memoryUsage);
185            // end::calculateSystemLoad[]
186            // tag::persistSystemLoad[]
187            em.persist(systemLoadData);
188            // end::persistSystemLoad[]
189            logger.info("System load at \"" + current + " was recorded.");
190            // tag::returnNull[]
191            return null;
192            // end::returnNull[]
193        } else {
194            logger.info("Schedule is stopped.");
195            // tag::returnCompletableFuture[]
196            return Asynchronous.Result.complete("Completed");
197            // end::returnCompletableFuture[]
198        }
199    }
200    // end::schedule[]
201
202    public List<SystemLoadData> getSystemLoads() {
203        return em.createNamedQuery(
204               "SystemLoadData.findAll", SystemLoadData.class).getResultList();
205    }
206
207}
Create the SystemConcurrency.java file.
src/main/java/io/openliberty/guides/system/SystemConcurrency.java

persistence.xml

 1<?xml version="1.0" encoding="UTF-8"?>
 2<persistence version="2.2"
 3    xmlns="http://xmlns.jcp.org/xml/ns/persistence" 
 4    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 5    xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/persistence 
 6                        http://xmlns.jcp.org/xml/ns/persistence/persistence_2_2.xsd">
 7    <!-- tag::jpa-unit[] -->
 8    <persistence-unit name="jpa-unit" transaction-type="JTA">
 9    <!-- end::jpa-unit[] -->
10        <jta-data-source>jdbc/SystemLoadDataSource</jta-data-source>
11        <properties>
12            <property name="jakarta.persistence.schema-generation.database.action"
13                      value="create"/>
14            <property name="jakarta.persistence.schema-generation.scripts.action"
15                      value="create"/>
16            <property name="jakarta.persistence.schema-generation.scripts.create-target"
17                      value="createDDL.ddl"/>
18        </properties>
19    </persistence-unit>
20</persistence>

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

 1// tag::copyright[]
 2/*******************************************************************************
 3 * Copyright (c) 2025 IBM Corporation and others.
 4 * All rights reserved. This program and the accompanying materials
 5 * are made available under the terms of the Eclipse Public License 2.0
 6 * which accompanies this distribution, and is available at
 7 * http://www.eclipse.org/legal/epl-2.0/
 8 *
 9 * SPDX-License-Identifier: EPL-2.0
10 *******************************************************************************/
11// end::copyright[]
12package io.openliberty.guides.system;
13
14import java.util.List;
15import java.util.Map;
16import java.util.concurrent.ExecutionException;
17
18import io.openliberty.guides.system.model.SystemLoadData;
19import jakarta.enterprise.context.ApplicationScoped;
20import jakarta.inject.Inject;
21import jakarta.ws.rs.GET;
22import jakarta.ws.rs.Path;
23import jakarta.ws.rs.PathParam;
24import jakarta.ws.rs.Produces;
25import jakarta.ws.rs.core.MediaType;
26
27@ApplicationScoped
28@Path("/system")
29public class SystemResource {
30
31    @Inject
32    SystemProperties propertiesBean;
33
34    @Inject
35    SystemConcurrency concurrencyBean;
36
37    @GET
38    @Path("/properties/{prefix}")
39    @Produces(MediaType.APPLICATION_JSON)
40    public Map<String, String> getProperties(@PathParam("prefix") String prefix)
41        throws InterruptedException, ExecutionException {
42        return propertiesBean.getProperties(prefix);
43    }
44
45    @GET
46    @Path("/systemLoad")
47    @Produces(MediaType.APPLICATION_JSON)
48    public List<SystemLoadData> getSystemLoads() {
49        return concurrencyBean.getSystemLoads();
50    }
51
52    // tag::getCpuLoad[]
53    @GET
54    @Path("/systemLoad/cpuLoad")
55    @Produces(MediaType.TEXT_PLAIN)
56    public String getCpuLoad() {
57        concurrencyBean.getCpuLoad();
58        return "Check CPU load after 5 seconds.";
59    }
60    // end::getCpuLoad[]
61
62    // tag::getMemoryUsage[]
63    @GET
64    @Path("/systemLoad/memoryUsage")
65    @Produces(MediaType.TEXT_PLAIN)
66    public String getMemoryUsage() {
67        concurrencyBean.getMemoryUsage();
68        return "Check memory usage after 5 seconds.";
69    }
70    // end::getMemoryUsage[]
71
72    // tag::schedule[]
73    @GET
74    @Path("/schedule")
75    @Produces(MediaType.TEXT_PLAIN)
76    public String schedule() {
77        return String.valueOf(concurrencyBean.isScheduleStarted());
78    }
79    // end::schedule[]
80
81    // tag::schedulToggle[]
82    @GET
83    @Path("/schedule/toggle")
84    @Produces(MediaType.TEXT_PLAIN)
85    public String schedulToggle() {
86        if (concurrencyBean.isScheduleStarted()) {
87            concurrencyBean.stopSchedule();
88            return "Disabling the schedule...";
89        } else {
90            concurrencyBean.startSchedule();
91            concurrencyBean.schedule();
92            return "Enabling the schedule...";
93        }
94    }
95    // end::schedulToggle[]
96}
Replace the SystemResource.java file.
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

  1// tag::copyright[]
  2/*******************************************************************************
  3 * Copyright (c) 2025 IBM Corporation and others.
  4 * All rights reserved. This program and the accompanying materials
  5 * are made available under the terms of the Eclipse Public License 2.0
  6 * which accompanies this distribution, and is available at
  7 * http://www.eclipse.org/legal/epl-2.0/
  8 *
  9 * SPDX-License-Identifier: EPL-2.0
 10 *******************************************************************************/
 11// end::copyright[]
 12package io.openliberty.guides.system;
 13
 14import java.lang.management.ManagementFactory;
 15import java.lang.management.MemoryMXBean;
 16import java.time.LocalDateTime;
 17import java.util.List;
 18import java.util.concurrent.CompletableFuture;
 19import java.util.concurrent.TimeUnit;
 20import java.util.logging.Logger;
 21
 22import javax.naming.InitialContext;
 23
 24import com.sun.management.OperatingSystemMXBean;
 25
 26import io.openliberty.guides.system.model.SystemLoadData;
 27import jakarta.annotation.Resource;
 28import jakarta.enterprise.concurrent.Asynchronous;
 29import jakarta.enterprise.concurrent.ManagedScheduledExecutorDefinition;
 30import jakarta.enterprise.concurrent.ManagedScheduledExecutorService;
 31import jakarta.enterprise.concurrent.Schedule;
 32import jakarta.enterprise.context.ApplicationScoped;
 33import jakarta.persistence.EntityManager;
 34import jakarta.persistence.PersistenceContext;
 35import jakarta.transaction.Transactional;
 36import jakarta.transaction.Transactional.TxType;
 37import jakarta.transaction.UserTransaction;
 38
 39// tag::annotateManagedScheduledExecutor[]
 40@ManagedScheduledExecutorDefinition(
 41    name = "java:module/concurrent/managed-scheduled-executor")
 42// end::annotateManagedScheduledExecutor[]
 43@ApplicationScoped
 44public class SystemConcurrency {
 45
 46    private static final OperatingSystemMXBean OS =
 47        (OperatingSystemMXBean) ManagementFactory.getOperatingSystemMXBean();
 48    private static final MemoryMXBean MEM = ManagementFactory.getMemoryMXBean();
 49
 50    private static Logger logger = Logger.getLogger(SystemConcurrency.class.getName());
 51    private static boolean isScheduleStarted = false;
 52
 53    // tag::managedScheduledExecutorService[]
 54    @Resource(lookup = "java:module/concurrent/managed-scheduled-executor")
 55    // tag::managedExecutor[]
 56    ManagedScheduledExecutorService managedExecutor;
 57    // end::managedExecutor[]
 58    // end::managedScheduledExecutorService[]
 59
 60    // tag::entityManager[]
 61    // tag::persistenceContext[]
 62    @PersistenceContext(name = "jpa-unit")
 63    // end::persistenceContext[]
 64    private EntityManager em;
 65    // end::entityManager[]
 66
 67    private void doSomething(int t) {
 68        try {
 69            Thread.sleep(t * 1000);
 70        } catch (Exception e) {
 71            logger.warning(e.getMessage());
 72        }
 73    }
 74
 75    // tag::getCpuLoad[]
 76    public void getCpuLoad() {
 77        logger.info("New CPU load will be recorded after 5 seconds.");
 78        // tag::scheduleCall[]
 79        managedExecutor.schedule(() -> {
 80        // end::scheduleCall[]
 81            // tag::userTransaction[]
 82            UserTransaction ut = null;
 83            // end::userTransaction[]
 84            try {
 85                // tag::utLookup[]
 86                ut = (UserTransaction)
 87                     new InitialContext().lookup("java:comp/UserTransaction");
 88                // end::utLookup[]
 89                // tag::utBegin[]
 90                ut.begin();
 91                // end::utBegin[]
 92                // tag::calculateCPULoad[]
 93                SystemLoadData cpuLoadData  = new SystemLoadData();
 94                LocalDateTime current = LocalDateTime.now();
 95                cpuLoadData.setTime(current);
 96                Double cpuLoad = Double.valueOf(OS.getCpuLoad() * 100.0);
 97                cpuLoadData.setCpuLoad(cpuLoad);
 98                // end::calculateCPULoad[]
 99                // tag::persistCPULoad[]
100                em.persist(cpuLoadData);
101                // end::persistCPULoad[]
102                // tag::utCommit[]
103                ut.commit();
104                // end::utCommit[]
105                logger.info("CPU load at \"" + current + "\" was recorded.");
106            } catch (Exception e) {
107                logger.warning(e.getMessage());
108                // tag::utRollback[]
109                if (ut != null) {
110                    try {
111                        ut.rollback();
112                    } catch (Exception re) {
113                        logger.warning(re.getMessage());
114                    }
115                }
116                // end::utRollback[]
117            }
118        // tag::after[]
119        }, 5, TimeUnit.SECONDS);
120        // end::after[]
121    }
122    // end::getCpuLoad[]
123
124    // tag::asynchronous1[]
125    @Asynchronous
126    // end::asynchronous1[]
127    // tag::transactional1[]
128    @Transactional(value = TxType.REQUIRES_NEW)
129    // end::transactional1[]
130    // tag::getMemoryUsage[]
131    public void getMemoryUsage() {
132        logger.info("New memory usage will be recorded after 5 seconds.");
133        doSomething(5);
134        // tag::calculateMemoryUsage[]
135        SystemLoadData memoryUsageData  = new SystemLoadData();
136        LocalDateTime current = LocalDateTime.now();
137        memoryUsageData.setTime(current);
138        long heapMax = MEM.getHeapMemoryUsage().getMax();
139        long heapUsed = MEM.getHeapMemoryUsage().getUsed();
140        Double memoryUsage = Double.valueOf(heapUsed * 100.0 / heapMax);
141        memoryUsageData.setMemoryUsage(memoryUsage);
142        // end::calculateMemoryUsage[]
143        // tag::persistMemoryUsage[]
144        em.persist(memoryUsageData);
145        // end::persistMemoryUsage[]
146        logger.info("Memory usage at \"" + current + "\" was recorded.");
147    }
148    // end::getMemoryUsage[]
149
150    // tag::enableSchedule[]
151    public boolean isScheduleStarted() {
152        return isScheduleStarted;
153    }
154
155    public void startSchedule() {
156        isScheduleStarted = true;
157    }
158
159    public void stopSchedule() {
160        isScheduleStarted = false;
161    }
162    // end::enableSchedule[]
163
164    // tag::asynchronous2[]
165    @Asynchronous(runAt = { @Schedule(cron = "*/10 * * * * *")})
166    // end::asynchronous2[]
167    // tag::transactional2[]
168    @Transactional(value = TxType.REQUIRES_NEW)
169    // end::transactional2[]
170    // tag::schedule[]
171    // tag::completableFuture[]
172    public CompletableFuture<String> schedule() {
173    // end::completableFuture[]
174        if (isScheduleStarted()) {
175            // tag::calculateSystemLoad[]
176            SystemLoadData systemLoadData  = new SystemLoadData();
177            LocalDateTime current = LocalDateTime.now();
178            systemLoadData.setTime(current);
179            Double cpuLoad = Double.valueOf(OS.getCpuLoad() * 100.0);
180            systemLoadData.setCpuLoad(cpuLoad);
181            long heapMax = MEM.getHeapMemoryUsage().getMax();
182            long heapUsed = MEM.getHeapMemoryUsage().getUsed();
183            Double memoryUsage = Double.valueOf(heapUsed * 100.0 / heapMax);
184            systemLoadData.setMemoryUsage(memoryUsage);
185            // end::calculateSystemLoad[]
186            // tag::persistSystemLoad[]
187            em.persist(systemLoadData);
188            // end::persistSystemLoad[]
189            logger.info("System load at \"" + current + " was recorded.");
190            // tag::returnNull[]
191            return null;
192            // end::returnNull[]
193        } else {
194            logger.info("Schedule is stopped.");
195            // tag::returnCompletableFuture[]
196            return Asynchronous.Result.complete("Completed");
197            // end::returnCompletableFuture[]
198        }
199    }
200    // end::schedule[]
201
202    public List<SystemLoadData> getSystemLoads() {
203        return em.createNamedQuery(
204               "SystemLoadData.findAll", SystemLoadData.class).getResultList();
205    }
206
207}
Replace the SystemConcurrency.java file.
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

 1// tag::copyright[]
 2/*******************************************************************************
 3 * Copyright (c) 2025 IBM Corporation and others.
 4 * All rights reserved. This program and the accompanying materials
 5 * are made available under the terms of the Eclipse Public License 2.0
 6 * which accompanies this distribution, and is available at
 7 * http://www.eclipse.org/legal/epl-2.0/
 8 *
 9 * SPDX-License-Identifier: EPL-2.0
10 *******************************************************************************/
11// end::copyright[]
12package io.openliberty.guides.system;
13
14import java.util.List;
15import java.util.Map;
16import java.util.concurrent.ExecutionException;
17
18import io.openliberty.guides.system.model.SystemLoadData;
19import jakarta.enterprise.context.ApplicationScoped;
20import jakarta.inject.Inject;
21import jakarta.ws.rs.GET;
22import jakarta.ws.rs.Path;
23import jakarta.ws.rs.PathParam;
24import jakarta.ws.rs.Produces;
25import jakarta.ws.rs.core.MediaType;
26
27@ApplicationScoped
28@Path("/system")
29public class SystemResource {
30
31    @Inject
32    SystemProperties propertiesBean;
33
34    @Inject
35    SystemConcurrency concurrencyBean;
36
37    @GET
38    @Path("/properties/{prefix}")
39    @Produces(MediaType.APPLICATION_JSON)
40    public Map<String, String> getProperties(@PathParam("prefix") String prefix)
41        throws InterruptedException, ExecutionException {
42        return propertiesBean.getProperties(prefix);
43    }
44
45    @GET
46    @Path("/systemLoad")
47    @Produces(MediaType.APPLICATION_JSON)
48    public List<SystemLoadData> getSystemLoads() {
49        return concurrencyBean.getSystemLoads();
50    }
51
52    // tag::getCpuLoad[]
53    @GET
54    @Path("/systemLoad/cpuLoad")
55    @Produces(MediaType.TEXT_PLAIN)
56    public String getCpuLoad() {
57        concurrencyBean.getCpuLoad();
58        return "Check CPU load after 5 seconds.";
59    }
60    // end::getCpuLoad[]
61
62    // tag::getMemoryUsage[]
63    @GET
64    @Path("/systemLoad/memoryUsage")
65    @Produces(MediaType.TEXT_PLAIN)
66    public String getMemoryUsage() {
67        concurrencyBean.getMemoryUsage();
68        return "Check memory usage after 5 seconds.";
69    }
70    // end::getMemoryUsage[]
71
72    // tag::schedule[]
73    @GET
74    @Path("/schedule")
75    @Produces(MediaType.TEXT_PLAIN)
76    public String schedule() {
77        return String.valueOf(concurrencyBean.isScheduleStarted());
78    }
79    // end::schedule[]
80
81    // tag::schedulToggle[]
82    @GET
83    @Path("/schedule/toggle")
84    @Produces(MediaType.TEXT_PLAIN)
85    public String schedulToggle() {
86        if (concurrencyBean.isScheduleStarted()) {
87            concurrencyBean.stopSchedule();
88            return "Disabling the schedule...";
89        } else {
90            concurrencyBean.startSchedule();
91            concurrencyBean.schedule();
92            return "Enabling the schedule...";
93        }
94    }
95    // end::schedulToggle[]
96}
Replace the SystemResource.java file.
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

  1// tag::copyright[]
  2/*******************************************************************************
  3 * Copyright (c) 2025 IBM Corporation and others.
  4 * All rights reserved. This program and the accompanying materials
  5 * are made available under the terms of the Eclipse Public License 2.0
  6 * which accompanies this distribution, and is available at
  7 * http://www.eclipse.org/legal/epl-2.0/
  8 *
  9 * SPDX-License-Identifier: EPL-2.0
 10 *******************************************************************************/
 11// end::copyright[]
 12package it.io.openliberty.guides.system;
 13
 14import static org.junit.jupiter.api.Assertions.assertEquals;
 15import static org.junit.jupiter.api.Assertions.assertNotNull;
 16import static org.junit.jupiter.api.Assertions.assertNull;
 17
 18import java.util.ArrayList;
 19import java.util.List;
 20import java.util.Map;
 21
 22import org.junit.jupiter.api.AfterAll;
 23import org.junit.jupiter.api.AfterEach;
 24import org.junit.jupiter.api.BeforeAll;
 25import org.junit.jupiter.api.BeforeEach;
 26import org.junit.jupiter.api.MethodOrderer;
 27import org.junit.jupiter.api.Order;
 28import org.junit.jupiter.api.Test;
 29import org.junit.jupiter.api.TestMethodOrder;
 30
 31import jakarta.json.JsonArray;
 32import jakarta.json.JsonObject;
 33import jakarta.json.JsonValue;
 34import jakarta.ws.rs.client.Client;
 35import jakarta.ws.rs.client.ClientBuilder;
 36import jakarta.ws.rs.client.WebTarget;
 37import jakarta.ws.rs.core.Response;
 38
 39@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
 40public class SystemEndpointIT {
 41
 42    private static final String PORT = System.getProperty("http.port");
 43    private static final String URL = "http://localhost:" + PORT + "/api/system";
 44
 45    private static boolean isScheduleEnabled = false;
 46
 47    private Client client;
 48
 49    private static void checkSchedule() {
 50        Client client = ClientBuilder.newClient();
 51        WebTarget target = client.target(URL + "/schedule");
 52        Response response = target.request().get();
 53        assertEquals(200, response.getStatus(),
 54            "Incorrect response code from " + target.getUri().getPath());
 55        String r = response.readEntity(String.class);
 56        isScheduleEnabled = r.equalsIgnoreCase("true");
 57        client.close();
 58    }
 59
 60    @BeforeAll
 61    public static void beforeAll() throws InterruptedException {
 62        checkSchedule();
 63        if (isScheduleEnabled) {
 64            toggleSchedule();
 65        }
 66    }
 67
 68    @BeforeEach
 69    public void beforeEach() {
 70      client = ClientBuilder.newClient();
 71    }
 72
 73    @AfterEach
 74    public void afterEach() {
 75      client.close();
 76    }
 77
 78    @AfterAll
 79    public static void afterAll() throws InterruptedException {
 80        if (!isScheduleEnabled) {
 81            toggleSchedule();
 82        }
 83    }
 84
 85    private static void toggleSchedule() throws InterruptedException {
 86        Client client = ClientBuilder.newClient();
 87        WebTarget target = client.target(URL + "/schedule/toggle");
 88        Response response = target.request().get();
 89        assertEquals(200, response.getStatus(),
 90            "Incorrect response code from " + target.getUri().getPath());
 91        client.close();
 92        Thread.sleep(11000);
 93    }
 94
 95    // tag::testGetProperties[]
 96    @Test
 97    @Order(1)
 98    public void testGetProperties() {
 99        WebTarget target = client.target(URL + "/properties/os");
100        Response response = target.request().get();
101        assertEquals(200, response.getStatus(),
102            "Incorrect response code from " + target.getUri().getPath());
103        JsonObject properties = response.readEntity(JsonObject.class);
104        assertEquals(4, properties.size());
105        assertEquals(System.getProperty("os.name"), properties.getString("os.name"));
106        response.close();
107    }
108    // end::testGetProperties[]
109
110    private JsonArray getSystemLoads() {
111        WebTarget target = client.target(URL + "/systemLoad");
112        Response response = target.request().get();
113        assertEquals(200, response.getStatus(),
114            "Incorrect response code from " + target.getUri().getPath());
115        return response.readEntity(JsonArray.class);
116    }
117
118    private List<JsonObject> removeAll(JsonArray before, JsonArray after) {
119        List<JsonObject> diff = new ArrayList<JsonObject>();
120        after.forEach(o -> {
121            if (!before.contains(o)) {
122                diff.add((JsonObject) o);
123            }
124        });
125        return diff;
126    }
127
128    private Map<String, JsonValue> testEndpoint(String endpoint) throws Exception {
129        JsonArray before = getSystemLoads();
130        WebTarget target = client.target(URL + endpoint);
131        Response response = target.request().get();
132        assertEquals(200, response.getStatus(),
133            "Incorrect response code from " + target.getUri().getPath());
134        Thread.sleep(6000);
135        JsonArray after = getSystemLoads();
136        assertEquals(before.size() + 1, after.size());
137        List<JsonObject>  diff = removeAll(before, after);
138        assertEquals(1, diff.size());
139        return diff.get(0);
140    }
141
142    // tag::testGetCpuLoad[]
143    @Test
144    @Order(2)
145    public void testGetCpuLoad() throws Exception {
146        Map<String, JsonValue> systemLoad = testEndpoint("/systemLoad/cpuLoad");
147        assertNotNull(systemLoad.get("cpuLoad"));
148        assertNull(systemLoad.get("memoryUsage"));
149    }
150    // end::testGetCpuLoad[]
151
152    // tag::testGetMemoryUsage[]
153    @Test
154    @Order(3)
155    public void testGetMemoryUsage() throws Exception {
156        Map<String, JsonValue> systemLoad = testEndpoint("/systemLoad/memoryUsage");
157        assertNotNull(systemLoad.get("memoryUsage"));
158        assertNull(systemLoad.get("cpuLoad"));
159    }
160    // end::testGetMemoryUsage[]
161
162    // tag::testToggleSchedule[]
163    @Test
164    @Order(4)
165    public void testToggleSchedule() throws Exception {
166        toggleSchedule();
167        JsonArray before = getSystemLoads();
168        Thread.sleep(20000);
169        JsonArray after = getSystemLoads();
170        assertEquals(before.size() + 2, after.size());
171        List<JsonObject>  diff = removeAll(before, after);
172        assertEquals(2, diff.size());
173        Map<String, JsonValue> systemLoad = diff.get(0);
174        assertNotNull(systemLoad.get("cpuLoad"));
175        assertNotNull(systemLoad.get("memoryUsage"));
176    }
177    // end::testToggleSchedule[]
178
179}
Replace the SystemEndpointIT.java file.
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

Copy file contents
Copied to clipboard

Prerequisites:

Great work! You're done!

What did you think of this guide?

Extreme Dislike Dislike Like Extreme Like

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

Like Open Liberty? Star our repo on GitHub.

Star

Guide license

Where to next?