Skip to content
Advertisement

How to write async POST/GET in Java Microprofile correctly

I’m a C developer who needs to create a basic async REST server (synchronous version works fine) in Java using the Microprofile (Helidon MP) framework. Here is my strategy to do this:

The client should do the POST call and provide JSON objects that the POST endpoint will receive. Then, the POST Endpoint method will call a business logic that should do stuff with the received JSON objects. This logic must be run asynchronously. POST should immediately return 202 Accepted. The client should check for async task completion status using a GET request (simple pooling style).

Should POST return a URI that the GET call will use? How? This GET should also provide the percentage of the task completion if the task is in progress. Finally, if the business logic is done, the GET should return the result.

I have a little previous experience with async Java, but no experience with async in this Microprofile/Java EE/Jakarta or whatever it is. I tried several different approaches (AsyncResponse, CompletitionStatus, etc.) to write this code (async POST Method) but nothing seems to be working. The skeleton of the POST functions looks like this:

@Path("/logic")
@POST
@Consumes(MediaType.APPLICATION_JSON)
@Produces(MediaType.APPLICATION_JSON)
public Response logic(JsonObject jsonObject){

  BusinessLogic bl = new BusinessLogic();

  // This should be done asynchronously.
  //How to do it correctly in Microprofile?
  bl.doSomethingWithInput(jsonObject); 

  return Response.status(Response.Status.ACCEPTED).build(); //Return ACCEPTED and the task URI???
}

The GET Handler:

@Path("/logicstatus")
@GET
@@Produces(MediaType.APPLICATION_JSON)
public Response logicStatus(URI as query param???) {

  // Magic here?

  retrun Response.status(Response.Status.OK).entity(resultJson).build();
}

Thanks a lot. Java reminds me how I love writing device drivers in C :D.

Advertisement

Answer

So, first things first. To run things asynchronously Jakarta EE has the annotation @Asynchronous. So, create a CDI bean with @RequestScoped, and put there your business method annotated with @Asynchronous.

Add @ApplicationScoped CDI annotation to your JAXRS service so you can inject ypur business bean.

Then, for monitoring the task, you have a couple of possibilities.

  1. If you keep the state in a database you just make the @GET method to check the state in the database. But I asume this is not your case.

  2. If the first approach doesn’t fit then you have to keep the state of the different inProgress tasks somehow. I would have another @ApplicationScoped CDI bean for that. This would contain a ConcurrentHashMap, with a UUID as the key, and as the value an Object of your own that contains the current state of any specific async job.

The UUID has to be generated in the @POST method, and sent as a parameter to the @Asynchronous, method. This way the @POST method can return the UUID, which will be used then in the @GET to request the state of the task (querying the @ApplicationScoped bean).

So the application-wide async tasks state holder should be something like this

@ApplocationScoped
public class AsyncTasksStateHolder {
     private Map<UUID, MyCustomState> tasksState = new ConcurrentHashMap<>();

// here go methods to add and get from the map

}

Then, the async business logic may be something like:

@RequestScoped
public class AsyncTaskExecutor {

    @Inject
    AsyncTasksStateHolder asyncTasksStateHolder;

    @Asynchronous
    public void doAsyncStuff(UUID uuid, JsonObject jsonObject) {
         // create the initial state and save it to asyncTasksStateHolder
         // Do your dirty deeds
         // Keep updatinf the state in asyncTasksStateHolder
    }
}

And now the JAX-RS part:

@Inject
AsyncTasksStateHolder asyncTasksStateHolder

@Inject
AsyncTaskExecutor asyncTasksExecutor;

@Path("/logic")
@POST
@Consumes(MediaType.APPLICATION_JSON)
@Produces(MediaType.TEXT_PLAIN)
public Response logic(JsonObject jsonObject){

  UUID uuid = UUID.randomUUID();
  asyncTasksExecutor.doAsyncStuff(uuid, jsonObiect); // this returns immediately because of the @Asynchronous
  return Response
      .status(Response.Status.OK)
      .entity(uuid.toString())
      .build();
}

@Path("/logicstatus/{uuid}")
@GET
@Produces(MediaType.APPLICATION_JSON)
public Response logicStatus(@PathParam("uuid") String uuidAsString) {
    // And here you just query the asyncTasksStateHolder
   // with the UUID to retreive the state.
}

Some thing to note here:

  1. You will need a mechanism to remove stale state, or the map will grow indefinately.

  2. I used PLAIN_TEXT for simplocity, vut feel freento model the @POST response as you please.

  3. Depending on your MicroProfile version you will nees to create the beans.xml so CDI gets activated.

And I think that’s all. Any problems or doubts do not hesitate to write a comment.

User contributions licensed under: CC BY-SA
2 People found this is helpful
Advertisement