Error while passing generic Object type to Async.function() as argument

Hello,

I have a use case where I am looping through a list of ActivityInterface instances and asynchronously calling a common method they inherit from a parent interface but that can accept multiple types of input.

I notice that when I use the Async.function method and pass in an Object class object I get a compile time error as shown.

Why is the passing of generic Object type not allowed in the Async.function? In my use case I could cast this to the appropriate type inside each activity method for the different activity interfaces.

To demonstrate the issue I have modified the getGreeting workflow from the HelloActivity java examples: samples-java/HelloActivity.java at master · temporalio/samples-java · GitHub

@Override
    public String getGreeting(String name) {
      Object greeting = "hello";

      Promise<String> promise = Async.function(activities::composeGreeting, greeting, name);

      return promise.get();
    }

The error I see in the IDE is

In that example the Activity composeGreeting method takes in first argument of type String. If you change to like:

String composeGreeting(Object greeting, String name);

and cast it to specific type expected in your activity impl

your Async.function call should work fine.

You can use generics, for example lets say you have the method as:

String composeGreeting(List<?> greetings, String name);

in that case you can pass it List<String>, List<Integer>, etc.

Hope this helps.

Thanks! Yes it definitely works that way.

The example I was working with actually has multiple interfaces extending from a base interface but each of them were using their own class type instead of Object across all of their activity methods.

I now changed all of them to use Object and the IDE doesn’t show errors.

The issue was me using Object in some places and actual custom classes in other cases.

Thanks.

I don’t think you can use Object as an activity argument unless you modify the DataConverter to support polymorphic types which is not the default.

This seems to be working even in the Polymorphic Activity example too but I am curious where do you think it might break maxim.

This only works for Strings and some other simple types that correspond to Json types. As soon as you use a class as an activity argument it is going to break.

For example this doesn’t work:

public class GreetingsArgs {
    private final String greeting;
    private final String name;

    public GreetingsArgs(String greeting, String name) {
      this.greeting = greeting;
      this.name = name;
    }

    public GreetingsArgs() {
      greeting = null;
      name = null;
    }

    public String getGreeting() {
      return greeting;
    }

    public String getName() {
      return name;
    }
  }

  @ActivityInterface
  public interface GreetingActivities {
    String composeGreeting(Object args);
  }

  public class GreetingActivitiesImpl implements GreetingActivities {
    @Override
    public String composeGreeting(Object argsO) {
      GreetingsArgs args = (GreetingsArgs) argsO;
      return args.getGreeting() + " " + args.getName() + "!";
    }
  }

  public class GreetingWorkflowImpl implements GreetingWorkflow {

    private final GreetingActivities activities =
        Workflow.newActivityStub(
            GreetingActivities.class,
            ActivityOptions.newBuilder().setStartToCloseTimeout(Duration.ofSeconds(2)).build());

    @Override
    public String getGreeting(String name) {
      return activities.composeGreeting(new GreetingsArgs("Hello", name));
    }
  }

Here is the failure:

11:44:21.698 [Activity Executor taskQueue="HelloActivityTaskQueue", namespace="default": 1] WARN  i.t.i.sync.POJOActivityTaskHandler - Activity failure. ActivityId=796d71ed-fb6b-33e0-94c2-46529b4ce0d0, activityType=greet, attempt=2
java.lang.ClassCastException: java.util.LinkedHashMap cannot be cast to io.temporal.samples.hello.HelloActivity$GreetingsArgs
	at io.temporal.samples.hello.HelloActivity$GreetingActivitiesImpl.composeGreeting(HelloActivity.java:140)
	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
	at java.lang.reflect.Method.invoke(Method.java:498)
	at io.temporal.internal.sync.POJOActivityTaskHandler$POJOActivityInboundCallsInterceptor.execute(POJOActivityTaskHandler.java:297)
	at io.temporal.internal.sync.POJOActivityTaskHandler$POJOActivityImplementation.execute(POJOActivityTaskHandler.java:254)
	at io.temporal.internal.sync.POJOActivityTaskHandler.handle(POJOActivityTaskHandler.java:212)
	at io.temporal.internal.worker.ActivityWorker$TaskHandlerImpl.handle(ActivityWorker.java:192)
	at io.temporal.internal.worker.ActivityWorker$TaskHandlerImpl.handle(ActivityWorker.java:151)
	at io.temporal.internal.worker.PollTaskExecutor.lambda$process$0(PollTaskExecutor.java:73)
	at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
	at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
	at java.lang.Thread.run(Thread.java:748)

Thank you so much for that example. I was definitely planning to use it that way but seems like it’s not possible right now.

@maxim I would like to know if there is any possible way to make this work?

My use case requires this flexibility to get a certain Class object from one Activity stub’s activity method and pass it to another using a generic piece of orchestration code inside the Workflow.

You mentioned that modifying the DataConverter is an option but of course that’s a framework/library file.

As far as DataConverter goes, you can create your own or extend existing ones as well. See for example samples-java/CryptDataConverter.java at master · temporalio/samples-java · GitHub

You can register your data converter via WorkflowClientOptions.

You can also replace payload converters for specific encoding type with for example:

DefaultDataConverter ddc =
        DefaultDataConverter.newDefaultInstance()
            .withPayloadConverterOverrides(myJacksonJsonPayloadConverter);
...
WorkflowClientOptions wco = WorkflowClientOptions.newBuilder().setDataConverter(ddc).build();
...

where myJacksonJsonPayloadConverter is your custom payload converter that you want to use the overwrite the default one.

Thank you! I will check it out!