<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0">
<channel>
<title>Coder Legion - Recent posts tagged graalvm</title>
<link>https://coderlegion.com/tag/graalvm</link>
<description>Powered by Question2Answer</description>
<item>
<title>actor-IaC: POJO-actor Workflow Based Infrastructure as Code for Cluster Management (Part 1)</title>
<link>https://coderlegion.com/10616/actor-iac-pojo-actor-workflow-based-infrastructure-as-code-for-cluster-management-part-1</link>
<description>&lt;p&gt;actor-IaC is an Infrastructure as Code (IaC) tool built on top of the POJO-actor workflow engine.&lt;br&gt;
It uses actor-based workflows written in Java and YAML to manage and operate compute clusters in a scalable and traceable way.&lt;/p&gt;
&lt;p&gt;In this article (Part 1), we introduce actor-IaC through a practical example: collecting system information from multiple compute nodes in parallel.&lt;br&gt;
This tutorial demonstrates how POJO-actor workflows can be applied to real-world cluster management tasks.&lt;/p&gt;
&amp;lt;!-- truncate --&amp;gt;
&lt;h3 id=&quot;why-use-pojo-actor-workflow-for-iac-&quot;&gt;Why Use POJO-actor Workflow for IaC?&lt;/h3&gt;
&lt;p&gt;In previous posts, we explained the design of a framework that easily enables asynchronous parallel processing by turning any POJO into an actor, and introduced the Turing Machine-based workflow engine built on top of it.&lt;/p&gt;
&lt;p&gt;In this post, we demonstrate that this workflow mechanism is effective even for practical, real-world applications. As a concrete example, we built a full-featured Infrastructure as Code tool called &quot;actor-IaC&quot; using the POJO-actor workflow engine.&lt;/p&gt;
&lt;h4 id=&quot;consistent-notation&quot;&gt;Consistent Notation&lt;/h4&gt;
&lt;p&gt;In POJO-actor Workflow, all operations are written in a consistent format: &quot;call a method on a POJO.&quot;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-yaml&quot;&gt;actions:
  - actor: this
    method: executeCommand
    arguments: [&quot;hostname -f&quot;]
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The granularity of operations depends on your POJO design. You can freely define methods ranging from executing a single shell command to complex deployment procedures. You're not constrained by DSL limitations.&lt;/p&gt;
&lt;h4 id=&quot;natural-conditional-branching&quot;&gt;Natural Conditional Branching&lt;/h4&gt;
&lt;p&gt;POJO-actor Workflow is based on state machines (Turing machines). Each step is defined as a pair of &quot;current state&quot; and &quot;next state&quot;, and conditional branching is expressed as steps with multiple possible transitions.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-yaml&quot;&gt;steps:
  - states: [&quot;check&quot;, &quot;success&quot;]
    when: &quot;result.exitCode == 0&quot;
    actions: [...]
  - states: [&quot;check&quot;, &quot;failure&quot;]
    when: &quot;result.exitCode != 0&quot;
    actions: [...]
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;No special control syntax is needed. Branching can be described naturally as state transitions.&lt;/p&gt;
&lt;h3 id=&quot;the-actor-tree-a-mental-model-for-cluster-management&quot;&gt;The Actor Tree: A Mental Model for Cluster Management&lt;/h3&gt;
&lt;p&gt;In actor-IaC, actors corresponding to each node defined in the inventory file are automatically generated.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;IIActorSystem
├── nodeGroup                # Group actor that manages all nodes
│   ├── node-node13          #   Corresponds to each node in inventory
│   ├── node-node14          #   (Number of nodes depends on inventory.ini)
│   └── ...
├── outputMultiplexer        # Aggregates output from all nodes
├── loader                   # Dynamic plugin loading
└── logStore                 # Log DB writing (when --log-db specified)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The advantage of this structure is that &lt;strong&gt;when writing workflows, you only need to think about sequential processing on a single machine&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;Sub-workflows describe only &quot;what to do on this one node.&quot; Parallelization is handled by &lt;code&gt;nodeGroup.apply()&lt;/code&gt;. You don't need to worry about synchronization or mutual exclusion across multiple nodes.&lt;/p&gt;
&lt;p&gt;Because the mental model is simple, workflow design, debugging, and maintenance all become easier.&lt;/p&gt;
&lt;h3 id=&quot;complete-traceability-of-execution-logs-cluster-operations-&quot;&gt;Complete Traceability of Execution Logs (Cluster Operations)&lt;/h3&gt;
&lt;p&gt;When the mental model is simple, checking logs also becomes easier.&lt;/p&gt;
&lt;p&gt;actor-IaC automatically saves execution logs from all nodes to an H2 database. Even logs from parallel execution can be filtered later to extract specific nodes.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;# Show logs for node13 only
./actor_iac.java log-search --db ./actor-iac-logs --node node-node13
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Even in situations like &quot;1 out of 6 nodes failed. What happened?&quot;, you can immediately check the logs for that specific node.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;hands-on-collecting-cluster-information&quot;&gt;Hands-on: Collecting Cluster Information&lt;/h3&gt;
&lt;p&gt;Enough theory—let's actually use actor-IaC.&lt;/p&gt;
&lt;p&gt;When managing a cluster, you need to understand the configuration of each node:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;CPU: How many cores? What model?&lt;/li&gt;
&lt;li&gt;Memory: How many GB?&lt;/li&gt;
&lt;li&gt;Disk: Capacity and usage?&lt;/li&gt;
&lt;li&gt;GPU: NVIDIA? AMD?&lt;/li&gt;
&lt;li&gt;Network: IP addresses?&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Manually checking this for 6 nodes is tedious. With actor-IaC, you can collect information from all nodes in parallel with a single command.&lt;/p&gt;
&lt;h4 id=&quot;1-inventory-file&quot;&gt;1. Inventory File&lt;/h4&gt;
&lt;p&gt;Define the target nodes. It uses the same INI format as Ansible.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-ini&quot;&gt;[compute]
node13 actoriac_host=192.168.5.13
node14 actoriac_host=192.168.5.14
node15 actoriac_host=192.168.5.15
node21 actoriac_host=192.168.5.21
node22 actoriac_host=192.168.5.22
node23 actoriac_host=192.168.5.23

[compute:vars]
actoriac_user=youruser
&lt;/code&gt;&lt;/pre&gt;
&lt;h4 id=&quot;2-sub-workflow-processing-executed-on-each-node-&quot;&gt;2. Sub-workflow (Processing Executed on Each Node)&lt;/h4&gt;
&lt;p&gt;Create &lt;code&gt;collect-sysinfo.yaml&lt;/code&gt;. This is the workflow executed on each node.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-yaml&quot;&gt;name: collect-sysinfo

steps:
  - states: [&quot;0&quot;, &quot;1&quot;]
    note: Get hostname and OS info
    actions:
      - actor: this
        method: executeCommand
        arguments:
          - |
            echo &quot;===== HOSTNAME =====&quot;
            hostname -f
            echo &quot;===== OS INFO =====&quot;
            cat /etc/os-release | grep -E &quot;^(NAME|VERSION)=&quot;

  - states: [&quot;1&quot;, &quot;2&quot;]
    note: Get CPU info
    actions:
      - actor: this
        method: executeCommand
        arguments:
          - |
            echo &quot;===== CPU INFO =====&quot;
            lscpu | grep -E &quot;^(Architecture|CPU\(s\)|Model name)&quot;

  - states: [&quot;2&quot;, &quot;3&quot;]
    note: Get memory info
    actions:
      - actor: this
        method: executeCommand
        arguments:
          - |
            echo &quot;===== MEMORY INFO =====&quot;
            free -h

  - states: [&quot;3&quot;, &quot;4&quot;]
    note: Get disk info
    actions:
      - actor: this
        method: executeCommand
        arguments:
          - |
            echo &quot;===== DISK INFO =====&quot;
            df -h | grep -E &quot;^(/dev|Filesystem)&quot;

  - states: [&quot;4&quot;, &quot;5&quot;]
    note: Get GPU info
    actions:
      - actor: this
        method: executeCommand
        arguments:
          - |
            echo &quot;===== GPU INFO =====&quot;
            if command -v nvidia-smi &amp;amp;&amp;gt; /dev/null; then
                nvidia-smi --query-gpu=name,memory.total,driver_version --format=csv,noheader
            else
                lspci | grep -i -E &quot;(vga|3d)&quot; || echo &quot;No GPU detected&quot;
            fi

  - states: [&quot;5&quot;, &quot;end&quot;]
    note: Get network info
    actions:
      - actor: this
        method: executeCommand
        arguments:
          - |
            echo &quot;===== NETWORK INFO =====&quot;
            ip -4 addr show | grep -E &quot;(^[0-9]+:|inet )&quot; | head -20
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Note that this workflow is written as &lt;strong&gt;sequential processing for a single node&lt;/strong&gt;. There's no consideration for parallel execution.&lt;/p&gt;
&lt;h4 id=&quot;3-main-workflow-applying-to-all-nodes-&quot;&gt;3. Main Workflow (Applying to All Nodes)&lt;/h4&gt;
&lt;p&gt;Create &lt;code&gt;main-collect-sysinfo.yaml&lt;/code&gt;.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-yaml&quot;&gt;name: main-collect-sysinfo

steps:
  - states: [&quot;0&quot;, &quot;end&quot;]
    note: Execute sub-workflow in parallel on all nodes
    actions:
      - actor: nodeGroup
        method: apply
        arguments:
          actor: &quot;node-*&quot;
          method: runWorkflow
          arguments: [&quot;collect-sysinfo.yaml&quot;]
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Just one step. &lt;code&gt;nodeGroup.apply()&lt;/code&gt; executes the sub-workflow in parallel on all nodes matching the pattern &lt;code&gt;node-*&lt;/code&gt;.&lt;/p&gt;
&lt;h4 id=&quot;execution&quot;&gt;Execution&lt;/h4&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;./actor_iac.java run -w sysinfo/main-collect-sysinfo.yaml -i inventory.ini -g compute
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;When executed, it simultaneously connects via SSH to all 6 nodes and collects system information.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;[node-node13] ===== HOSTNAME =====
[node-node13] node13.local
[node-node14] ===== HOSTNAME =====
[node-node14] node14.local
[node-node13] ===== CPU INFO =====
[node-node13] Architecture:          x86_64
[node-node13] CPU(s):                32
[node-node13] Model name:            AMD Ryzen 9 5950X
...
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The prefix like &lt;code&gt;[node-node13]&lt;/code&gt; at the beginning of each line indicates which node the output came from. Since execution is parallel, outputs arrive in mixed order, but you can distinguish them by the prefix.&lt;/p&gt;
&lt;h4 id=&quot;checking-results&quot;&gt;Checking Results&lt;/h4&gt;
&lt;p&gt;All logs are automatically saved to an H2 database (&lt;code&gt;actor-iac-logs.mv.db&lt;/code&gt;).&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;# Show session list
./actor_iac.java log-search --db ./actor-iac-logs --list

# Show logs for a specific node
./actor_iac.java log-search --db ./actor-iac-logs --node node-node13
&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;h3 id=&quot;actor-iac-features&quot;&gt;actor-IaC Features&lt;/h3&gt;
&lt;p&gt;actor-IaC is not an experimental tool—it already has production-ready features.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;&lt;th&gt;Command &lt;/th&gt;&lt;th&gt;Description&lt;/th&gt;&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;run&lt;/code&gt; &lt;/td&gt;&lt;td&gt;Execute a workflow&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;list&lt;/code&gt; &lt;/td&gt;&lt;td&gt;List available workflows&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;describe&lt;/code&gt; &lt;/td&gt;&lt;td&gt;Show workflow details&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;log-search&lt;/code&gt; &lt;/td&gt;&lt;td&gt;Search and display execution logs&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;log-merge&lt;/code&gt; &lt;/td&gt;&lt;td&gt;Merge distributed log DBs&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;db-clear&lt;/code&gt; &lt;/td&gt;&lt;td&gt;Delete log DB&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;The overlay feature allows you to use the same workflow across development, staging, and production environments.&lt;/p&gt;
&lt;p&gt;Plugin-based extensions are planned for the future.&lt;/p&gt;
&lt;h3 id=&quot;summary&quot;&gt;Summary&lt;/h3&gt;
&lt;p&gt;actor-IaC is the answer to &quot;Can POJO-actor Workflow express complex workflows?&quot;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Consistent notation&lt;/strong&gt;: A unified format of POJO method calls&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Natural conditional branching&lt;/strong&gt;: State machine-based, no special syntax needed&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Simple mental model&lt;/strong&gt;: Just think about sequential processing on a single node&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Complete traceability&lt;/strong&gt;: All logs saved to DB, searchable anytime&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;In Part 2, we'll introduce how to format and analyze the collected information.&lt;/p&gt;
&lt;h3 id=&quot;try-it-yourself&quot;&gt;Try It Yourself&lt;/h3&gt;
&lt;p&gt;You don't need a cluster to try actor-IaC. The &lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/scivicslab/actor-IaC-examples&quot;&gt;actor-IaC-examples&lt;/a&gt; repository includes examples that run on localhost:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;# Install dependencies
git clone https://github.com/scivicslab/POJO-actor &amp;amp;&amp;amp; cd POJO-actor &amp;amp;&amp;amp; mvn install &amp;amp;&amp;amp; cd ..
git clone https://github.com/scivicslab/actor-IaC &amp;amp;&amp;amp; cd actor-IaC &amp;amp;&amp;amp; mvn install &amp;amp;&amp;amp; cd ..

# Clone examples and run
git clone https://github.com/scivicslab/actor-IaC-examples
cd actor-IaC-examples

# Hello World example
./actor_iac.java run -d ./hello -w main-hello -i localhost.ini -g all

# Collect system info from localhost
./actor_iac.java run -d ./sysinfo -w main-collect-sysinfo -i localhost.ini -g all
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;See the repository's README for more examples and detailed instructions.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;references&quot;&gt;References&lt;/h3&gt;
&lt;p&gt;This article is part of the official actor-IaC documentation and tutorial series.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Official Website&lt;/strong&gt;: &lt;a rel=&quot;nofollow&quot; href=&quot;https://scivicslab.com&quot;&gt;https://scivicslab.com&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Original Article&lt;/strong&gt;: &lt;a rel=&quot;nofollow&quot; href=&quot;https://scivicslab.com/blog/2026-01-28-actor-iac-cluster-inventory&quot;&gt;https://scivicslab.com/blog/2026-01-28-actor-iac-cluster-inventory&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Documentation&lt;/strong&gt;: &lt;a rel=&quot;nofollow&quot; href=&quot;https://scivicslab.com/docs/actor-iac/introduction&quot;&gt;https://scivicslab.com/docs/actor-iac/introduction&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;GitHub Repository&lt;/strong&gt;: &lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/scivicslab/actor-iac&quot;&gt;https://github.com/scivicslab/actor-iac&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;API Reference (Javadoc)&lt;/strong&gt;: &lt;a rel=&quot;nofollow&quot; href=&quot;https://scivicslab.github.io/actor-IaC/&quot;&gt;https://scivicslab.github.io/actor-IaC/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;POJO-actor v1.0 Introduction (blog)&lt;/strong&gt;: &lt;a rel=&quot;nofollow&quot; href=&quot;https://coderlegion.com/8748/pojo-actor-v1-0-a-lightweight-actor-model-library-for-java?show=8748#q8748&quot;&gt;A Lightweight Actor Model Library for Java&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Tutorial Part 2-1 (blog)&lt;/strong&gt;: &lt;a rel=&quot;nofollow&quot; href=&quot;https://coderlegion.com/9131/pojo-actor-tutorial-part-2-first-half-workflow-language-basics?show=9131#q9131&quot;&gt;Workflow Language Basics&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Tutorial Part 2-2 (blog)&lt;/strong&gt;: &lt;a rel=&quot;nofollow&quot; href=&quot;https://coderlegion.com/9132/pojo-actor-tutorial-part-2-second-half-creating-workflows?show=9132#q9132&quot;&gt;Creating Workflows&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Tutorial Part 2-3 (blog)&lt;/strong&gt;: &lt;a rel=&quot;nofollow&quot; href=&quot;https://coderlegion.com/10613/pojo-actor-tutorial-part-improving-the-workflow-api-introducing-the-action-annotation&quot;&gt;Improving the Workflow API — Introducing the @Action Annotation&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/10616/actor-iac-pojo-actor-workflow-based-infrastructure-as-code-for-cluster-management-part-1</guid>
<pubDate>Wed, 28 Jan 2026 06:47:58 +0000</pubDate>
</item>
<item>
<title>POJO-actor Tutorial Part 2-3: Improving the Workflow API — Introducing the @Action Annotation</title>
<link>https://coderlegion.com/10613/pojo-actor-tutorial-part-improving-the-workflow-api-introducing-the-action-annotation</link>
<description>&lt;p&gt;This article is a hands-on tutorial for the POJO-actor workflow API, focusing on the &lt;code&gt;@Action&lt;/code&gt; annotation introduced in POJO-actor v2.14.&lt;br&gt;
We explain how to define workflow-callable actions in Java actors without overriding &lt;code&gt;callByActionName()&lt;/code&gt;, using a practical example with conditional branching.&lt;/p&gt;
&amp;lt;!-- truncate --&amp;gt;
&lt;p&gt;In Part 2-2, we implemented workflow actions by overriding callByActionName() and dispatching actions with a switch-case statement.&lt;br&gt;
While this approach works, the method grows quickly as the number of actions increases and becomes difficult to maintain.&lt;/p&gt;
&lt;p&gt;To solve this problem, POJO-actor v2.14 introduces the @Action annotation.&lt;br&gt;
With this annotation, workflow actions can be defined declaratively on actor methods, eliminating the need for explicit dispatch logic.&lt;/p&gt;
&lt;p&gt;In this tutorial, we reimplement the Turing machine example (turing87), which outputs an irrational binary sequence (0.001011011101111...), using the @Action-based workflow API.&lt;br&gt;
This example includes conditional branching and represents a more practical workflow scenario.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;introduction&quot;&gt;Introduction&lt;/h3&gt;
&lt;p&gt;In &lt;a rel=&quot;nofollow&quot; href=&quot;/blog/2025-12-31-TutorialPart2-2&quot;&gt;Part 2-2&lt;/a&gt;, we explained how to extend &lt;code&gt;IIActorRef&lt;/code&gt; and override the &lt;code&gt;callByActionName()&lt;/code&gt; method.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;@Override
public ActionResult callByActionName(String actionName, String args) {
    switch (actionName) {
        case &quot;initMachine&quot;: ...
        case &quot;put&quot;: ...
        case &quot;move&quot;: ...
        default: return new ActionResult(false, &quot;Unknown action: &quot; + actionName);
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This works, but the &lt;code&gt;callByActionName()&lt;/code&gt; method grows larger as you add more workflow-callable actions.&lt;/p&gt;
&lt;p&gt;To solve this problem, POJO-actor v2.14 introduces the &lt;strong&gt;&lt;code&gt;@Action&lt;/code&gt; annotation&lt;/strong&gt;. You no longer need to explicitly write &lt;code&gt;callByActionName()&lt;/code&gt; — just annotate your methods.&lt;/p&gt;
&lt;p&gt;In this tutorial, we implement turing87 (a Turing machine that outputs the irrational number 0.001011011101111...) introduced in &lt;a rel=&quot;nofollow&quot; href=&quot;/blog/2025-12-30-TutorialPart2-1&quot;&gt;Part 2-1&lt;/a&gt; using the &lt;code&gt;@Action&lt;/code&gt; annotation. This is a more practical example that includes conditional branching.&lt;/p&gt;
&amp;lt;!-- truncate --&amp;gt;
&lt;h3 id=&quot;step-1-create-the-pojo-same-as-part-2-2-&quot;&gt;Step 1: Create the POJO (Same as Part 2-2)&lt;/h3&gt;
&lt;p&gt;The POJO requires no changes. It's a plain Java class that knows nothing about the workflow.&lt;/p&gt;
&lt;h4 id=&quot;turing-java&quot;&gt;Turing.java&lt;/h4&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;public class Turing {
    int currentPos = 0;
    Tape tape = new Tape();

    /** Initialize the machine */
    public void initMachine() {
        this.currentPos = 0;
        this.tape = new Tape();
    }

    /** Write a value to the tape */
    public void put(String value) {
        this.tape.setWithResizing(this.currentPos, value);
    }

    /** Move the head (&quot;L&quot;=left, &quot;R&quot;=right) */
    public void move(String direction) {
        if (direction.equalsIgnoreCase(&quot;L&quot;)) {
            this.currentPos--;
        } else if (direction.equalsIgnoreCase(&quot;R&quot;)) {
            this.currentPos++;
        }
    }

    /** Print the tape contents */
    public void printTape() {
        System.out.println(&quot;TAPE\t&quot; + this.tape.toString());
    }

    // Methods for conditional branching (used in turing87)

    /** Check if current position value matches expected */
    public boolean matchCurrentValue(String expected) {
        String current = this.tape.get(this.currentPos);
        return expected.equals(current);
    }

    /** Check if current position has any value */
    public boolean isAny() {
        String current = this.tape.get(this.currentPos);
        return current != null &amp;amp;&amp;amp; !current.isEmpty();
    }

    /** Check if current position is empty */
    public boolean isNone() {
        String current = this.tape.get(this.currentPos);
        return current == null || current.isEmpty();
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;step-2-create-the-iiactorref-action-annotation-version&quot;&gt;Step 2: Create the IIActorRef — @Action Annotation Version&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;This is the main topic.&lt;/strong&gt; Let's first review the code from Part 2-2.&lt;/p&gt;
&lt;h4 id=&quot;part-2-2-code-switch-statement-version-&quot;&gt;Part 2-2 Code (switch statement version)&lt;/h4&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;public class TuringIIAR extends IIActorRef&amp;lt;Turing&amp;gt; {

    public TuringIIAR(String actorName, Turing turing, IIActorSystem system) {
        super(actorName, turing, system);
    }

    private String parseFirstArg(String args) {
        if (args == null || args.isEmpty() || args.equals(&quot;[]&quot;)) {
            return &quot;&quot;;
        }
        JSONArray array = new JSONArray(args);
        return array.length() &amp;gt; 0 ? array.getString(0) : &quot;&quot;;
    }

    @Override
    public ActionResult callByActionName(String actionName, String args) {
        try {
            switch (actionName) {
                case &quot;initMachine&quot;:
                    this.tell(t -&amp;gt; t.initMachine()).get();
                    return new ActionResult(true, &quot;initialized&quot;);

                case &quot;put&quot;:
                    String putValue = parseFirstArg(args);
                    this.tell(t -&amp;gt; t.put(putValue)).get();
                    return new ActionResult(true, &quot;put &quot; + putValue);

                case &quot;move&quot;:
                    String direction = parseFirstArg(args);
                    this.tell(t -&amp;gt; t.move(direction)).get();
                    return new ActionResult(true, &quot;moved &quot; + direction);

                case &quot;printTape&quot;:
                    this.tell(t -&amp;gt; t.printTape()).get();
                    return new ActionResult(true, &quot;printed&quot;);

                case &quot;matchCurrentValue&quot;:
                    String expected = parseFirstArg(args);
                    boolean match = this.ask(t -&amp;gt; t.matchCurrentValue(expected)).get();
                    return new ActionResult(match, &quot;match=&quot; + match);

                case &quot;isAny&quot;:
                    boolean any = this.ask(t -&amp;gt; t.isAny()).get();
                    return new ActionResult(any, &quot;isAny=&quot; + any);

                case &quot;isNone&quot;:
                    boolean none = this.ask(t -&amp;gt; t.isNone()).get();
                    return new ActionResult(none, &quot;isNone=&quot; + none);

                default:
                    return new ActionResult(false, &quot;Unknown action: &quot; + actionName);
            }
        } catch (Exception e) {
            return new ActionResult(false, &quot;Error: &quot; + e.getMessage());
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;To connect action names written in YAML workflow files to the Java program, we override &lt;code&gt;callByActionName()&lt;/code&gt; and dispatch actions using a switch statement.&lt;br&gt;
In turing87, we also need conditional branching actions (&lt;code&gt;matchCurrentValue&lt;/code&gt;, &lt;code&gt;isAny&lt;/code&gt;, &lt;code&gt;isNone&lt;/code&gt;), making the switch statement quite long.&lt;/p&gt;
&lt;h4 id=&quot;part-2-3-code-action-version-&quot;&gt;Part 2-3 Code (@Action version)&lt;/h4&gt;
&lt;p&gt;Rewriting the same functionality with the &lt;code&gt;@Action&lt;/code&gt; annotation looks like this:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;import static com.scivicslab.pojoactor.core.ActionArgs.*;
import com.scivicslab.pojoactor.core.Action;
import com.scivicslab.pojoactor.core.ActionResult;
import com.scivicslab.pojoactor.workflow.IIActorRef;
import com.scivicslab.pojoactor.workflow.IIActorSystem;

public class TuringIIAR extends IIActorRef&amp;lt;Turing&amp;gt; {

    public TuringIIAR(String actorName, Turing turing, IIActorSystem system) {
        super(actorName, turing, system);
    }

    @Action(&quot;initMachine&quot;)
    public ActionResult initMachine(String args) {
        this.object.initMachine();
        return new ActionResult(true, &quot;Machine initialized&quot;);
    }

    @Action(&quot;put&quot;)
    public ActionResult put(String args) {
        String value = getFirst(args);
        this.object.put(value);
        return new ActionResult(true, &quot;Put &quot; + value);
    }

    @Action(&quot;move&quot;)
    public ActionResult move(String args) {
        String direction = getFirst(args);
        this.object.move(direction);
        return new ActionResult(true, &quot;Moved &quot; + direction);
    }

    @Action(&quot;printTape&quot;)
    public ActionResult printTape(String args) {
        this.object.printTape();
        return new ActionResult(true, &quot;Tape printed&quot;);
    }

    // Conditional branching actions (used in turing87)

    @Action(&quot;matchCurrentValue&quot;)
    public ActionResult matchCurrentValue(String args) {
        String expected = getFirst(args);
        boolean match = this.object.matchCurrentValue(expected);
        return new ActionResult(match, &quot;match=&quot; + match);
    }

    @Action(&quot;isAny&quot;)
    public ActionResult isAny(String args) {
        boolean any = this.object.isAny();
        return new ActionResult(any, &quot;isAny=&quot; + any);
    }

    @Action(&quot;isNone&quot;)
    public ActionResult isNone(String args) {
        boolean none = this.object.isNone();
        return new ActionResult(none, &quot;isNone=&quot; + none);
    }

    // No need to override callByActionName()!
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;You no longer need to write the &lt;code&gt;callByActionName()&lt;/code&gt; method.&lt;/strong&gt; Each action becomes an independent method, linked to the YAML via &lt;code&gt;@Action(&quot;actionName&quot;)&lt;/code&gt;. The base class uses reflection to discover &lt;code&gt;@Action&lt;/code&gt;-annotated methods and dispatches automatically.&lt;/p&gt;
&lt;h4 id=&quot;-action-method-requirements&quot;&gt;@Action Method Requirements&lt;/h4&gt;
&lt;p&gt;Methods annotated with &lt;code&gt;@Action&lt;/code&gt; must follow this format:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;@Action(&quot;actionName&quot;)
public ActionResult methodName(String args) {
    // processing
    return new ActionResult(success/failure, message);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Return type&lt;/strong&gt;: &lt;code&gt;ActionResult&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Parameter&lt;/strong&gt;: Single &lt;code&gt;String&lt;/code&gt; (YAML &lt;code&gt;arguments&lt;/code&gt; passed as JSON string)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Visibility&lt;/strong&gt;: &lt;code&gt;public&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h5 id=&quot;argument-formats&quot;&gt;Argument Formats&lt;/h5&gt;
&lt;p&gt;YAML &lt;code&gt;arguments&lt;/code&gt; can be written in three formats:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;&lt;th&gt;YAML notation &lt;/th&gt;&lt;th&gt;Value passed to &lt;code&gt;args&lt;/code&gt; &lt;/th&gt;&lt;th&gt;Parse example&lt;/th&gt;&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;arguments: &quot;value&quot;&lt;/code&gt; &lt;/td&gt;&lt;td&gt;&lt;code&gt;[&quot;value&quot;]&lt;/code&gt; &lt;/td&gt;&lt;td&gt;&lt;code&gt;new JSONArray(args).getString(0)&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;arguments: [&quot;a&quot;, &quot;b&quot;]&lt;/code&gt; &lt;/td&gt;&lt;td&gt;&lt;code&gt;[&quot;a&quot;, &quot;b&quot;]&lt;/code&gt; &lt;/td&gt;&lt;td&gt;&lt;code&gt;new JSONArray(args)&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;arguments: {key: &quot;value&quot;}&lt;/code&gt; &lt;/td&gt;&lt;td&gt;&lt;code&gt;{&quot;key&quot;: &quot;value&quot;}&lt;/code&gt; &lt;/td&gt;&lt;td&gt;&lt;code&gt;new JSONObject(args)&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;The &lt;code&gt;args&lt;/code&gt; parameter receives a JSON string. Use &lt;code&gt;ActionArgs.parse()&lt;/code&gt; for unified access.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;import com.scivicslab.pojoactor.core.ActionArgs;

@Action(&quot;example&quot;)
public ActionResult example(String args) {
    var p = ActionArgs.parse(args);  // Parse in one line

    // For array format (arguments: &quot;value&quot; or arguments: [&quot;a&quot;, &quot;b&quot;])
    String first = p.get(0);
    String second = p.get(1);

    // For object format (arguments: {host: &quot;server&quot;, port: 8080})
    String host = p.get(&quot;host&quot;);
    int port = p.getInt(&quot;port&quot;, 80);  // With default value
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;step-3-create-the-yaml-workflow&quot;&gt;Step 3: Create the YAML Workflow&lt;/h3&gt;
&lt;p&gt;Define the workflow in YAML. POJO-actor v2.14 allows you to write actions concisely using &lt;strong&gt;array format&lt;/strong&gt;.&lt;/p&gt;
&lt;h4 id=&quot;array-format-vs-object-format&quot;&gt;Array Format vs Object Format&lt;/h4&gt;
&lt;p&gt;Traditional object format:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-yaml&quot;&gt;- actor: turing
  method: put
  arguments: &quot;e&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Array format (introduced in v2.14):&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-yaml&quot;&gt;- [turing, put, &quot;e&quot;]
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Array elements are in order: &lt;code&gt;[actorName, methodName, arguments]&lt;/code&gt;. If there are no arguments, just &lt;code&gt;[actorName, methodName]&lt;/code&gt; is fine.&lt;/p&gt;
&lt;h4 id=&quot;turing87-yaml&quot;&gt;turing87.yaml&lt;/h4&gt;
&lt;p&gt;turing87 uses conditional branching to output the irrational number 0.001011011101111... Here is the state transition diagram:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./Turing87.png&quot; alt=&quot;Turing87 State Diagram&quot;&gt;&lt;/p&gt;
&lt;p&gt;Using array format makes it very concise:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-yaml&quot;&gt;name: turing87
steps:
- states: [&quot;0&quot;, &quot;100&quot;]
  actions:
  - [turing, initMachine]
- states: [&quot;100&quot;, &quot;1&quot;]
  actions:
  - [turing, printTape]
- states: [&quot;1&quot;, &quot;2&quot;]
  actions:
  - [turing, put, &quot;e&quot;]
  - [turing, move, &quot;R&quot;]
  - [turing, put, &quot;e&quot;]
  - [turing, move, &quot;R&quot;]
  - [turing, put, &quot;0&quot;]
  - [turing, move, &quot;R&quot;]
  - [turing, move, &quot;R&quot;]
  - [turing, put, &quot;0&quot;]
  - [turing, move, &quot;L&quot;]
  - [turing, move, &quot;L&quot;]
- states: [&quot;101&quot;, &quot;2&quot;]
  actions:
  - [turing, printTape]
- states: [&quot;2&quot;, &quot;2&quot;]
  actions:
  - [turing, matchCurrentValue, &quot;1&quot;]
  - [turing, move, &quot;R&quot;]
  - [turing, put, &quot;x&quot;]
  - [turing, move, &quot;L&quot;]
  - [turing, move, &quot;L&quot;]
  - [turing, move, &quot;L&quot;]
- states: [&quot;2&quot;, &quot;3&quot;]
  actions:
  - [turing, matchCurrentValue, &quot;0&quot;]
- states: [&quot;3&quot;, &quot;3&quot;]
  actions:
  - [turing, isAny]
  - [turing, move, &quot;R&quot;]
  - [turing, move, &quot;R&quot;]
- states: [&quot;3&quot;, &quot;4&quot;]
  actions:
  - [turing, isNone]
  - [turing, put, &quot;1&quot;]
  - [turing, move, &quot;L&quot;]
- states: [&quot;4&quot;, &quot;3&quot;]
  actions:
  - [turing, matchCurrentValue, &quot;x&quot;]
  - [turing, put, &quot; &quot;]
  - [turing, move, &quot;R&quot;]
- states: [&quot;4&quot;, &quot;5&quot;]
  actions:
  - [turing, matchCurrentValue, &quot;e&quot;]
  - [turing, move, &quot;R&quot;]
- states: [&quot;4&quot;, &quot;4&quot;]
  actions:
  - [turing, isNone]
  - [turing, move, &quot;L&quot;]
  - [turing, move, &quot;L&quot;]
- states: [&quot;5&quot;, &quot;5&quot;]
  actions:
  - [turing, isAny]
  - [turing, move, &quot;R&quot;]
  - [turing, move, &quot;R&quot;]
- states: [&quot;5&quot;, &quot;101&quot;]
  actions:
  - [turing, isNone]
  - [turing, put, &quot;0&quot;]
  - [turing, move, &quot;L&quot;]
  - [turing, move, &quot;L&quot;]
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Approximately 170 lines reduced to about 70 lines.&lt;/strong&gt; Array format greatly improves workflow readability.&lt;/p&gt;
&lt;h3 id=&quot;step-4-create-the-application-same-as-part-2-2-&quot;&gt;Step 4: Create the Application (Same as Part 2-2)&lt;/h3&gt;
&lt;p&gt;The application code is exactly the same. Just change the YAML filename.&lt;/p&gt;
&lt;h4 id=&quot;turingworkflowapp-java&quot;&gt;TuringWorkflowApp.java&lt;/h4&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;import com.scivicslab.pojoactor.core.ActionResult;
import com.scivicslab.pojoactor.workflow.IIActorSystem;
import com.scivicslab.pojoactor.workflow.Interpreter;
import java.io.InputStream;

public class TuringWorkflowApp {

    public static void main(String[] args) {
        String yamlPath = &quot;/code/turing87.yaml&quot;;
        new TuringWorkflowApp().runWorkflow(yamlPath);
    }

    public void runWorkflow(String yamlPath) {
        // 1. Create IIActorSystem
        IIActorSystem system = new IIActorSystem(&quot;turing-system&quot;);

        try {
            // 2. Create POJO
            Turing turing = new Turing();

            // 3. Wrap with IIActorRef and register with system
            TuringIIAR turingActor = new TuringIIAR(&quot;turing&quot;, turing, system);
            system.addIIActor(turingActor);

            // 4. Create Interpreter
            Interpreter interpreter = new Interpreter.Builder()
                    .loggerName(&quot;TuringWorkflow&quot;)
                    .team(system)
                    .build();

            // 5. Load YAML
            InputStream yamlStream = getClass().getResourceAsStream(yamlPath);
            interpreter.readYaml(yamlStream);

            // 6. Execute workflow (max 100 iterations)
            ActionResult result = interpreter.runUntilEnd(100);

            // 7. Display result
            System.out.println(&quot;Result: &quot; + result.getResult());

        } finally {
            // 8. Cleanup
            system.terminateIIActors();
            system.terminate();
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;execution-result-same-as-part-2-2-&quot;&gt;Execution Result (Same as Part 2-2)&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;Loading workflow from: /code/turing87.yaml
Workflow loaded successfully
Executing workflow...

TAPE    C 0 1
TAPE    C C 0 1 1
TAPE    C C C 0 1 1 1
TAPE    C C C C 0 1 1 1 1
...

Workflow finished: Maximum iterations (100) exceeded
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The output pattern shows &quot;0 followed by an increasing number of 1s&quot; (C is a counter). The output is &lt;strong&gt;exactly the same&lt;/strong&gt; as Part 2-2. The only difference is the &lt;strong&gt;implementation method&lt;/strong&gt; of the IIActorRef.&lt;/p&gt;
&lt;h3 id=&quot;internal-mechanism-how-action-works&quot;&gt;Internal Mechanism: How @Action Works&lt;/h3&gt;
&lt;p&gt;When &lt;code&gt;callByActionName&lt;/code&gt; is called, the &lt;code&gt;IIActorRef&lt;/code&gt; base class processes in this order:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Search for @Action methods&lt;/strong&gt; — Scans the wrapped object's (&lt;code&gt;this.object&lt;/code&gt;) class for methods annotated with &lt;code&gt;@Action&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;If found, invoke it&lt;/strong&gt; — Calls the method via reflection and returns the result&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;If not found, fallback&lt;/strong&gt; — Tries subclass switch statements or built-in actions (like &lt;code&gt;putJson&lt;/code&gt;)&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;This allows &lt;strong&gt;switch-based code and @Action-based code to coexist&lt;/strong&gt;. Gradual migration is possible.&lt;/p&gt;
&lt;h3 id=&quot;summary-1&quot;&gt;Summary&lt;/h3&gt;
&lt;p&gt;The difference between Part 2-2 and Part 2-3 is &lt;strong&gt;only Step 2&lt;/strong&gt; (IIActorRef creation):&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;&lt;th&gt;Step &lt;/th&gt;&lt;th&gt;Content &lt;/th&gt;&lt;th&gt;Part 2-2 &lt;/th&gt;&lt;th&gt;Part 2-3&lt;/th&gt;&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;1 &lt;/td&gt;&lt;td&gt;Create POJO &lt;/td&gt;&lt;td&gt;Same &lt;/td&gt;&lt;td&gt;Same&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;2 &lt;/td&gt;&lt;td&gt;Create IIActorRef &lt;/td&gt;&lt;td&gt;&lt;strong&gt;Override &lt;code&gt;callByActionName()&lt;/code&gt;&lt;/strong&gt; &lt;/td&gt;&lt;td&gt;&lt;strong&gt;Just add &lt;code&gt;@Action&lt;/code&gt;&lt;/strong&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;3 &lt;/td&gt;&lt;td&gt;Create YAML &lt;/td&gt;&lt;td&gt;Same &lt;/td&gt;&lt;td&gt;Same&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;4 &lt;/td&gt;&lt;td&gt;Create Application &lt;/td&gt;&lt;td&gt;Same &lt;/td&gt;&lt;td&gt;Same&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;With the &lt;code&gt;@Action&lt;/code&gt; annotation:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;No need to override &lt;code&gt;callByActionName()&lt;/code&gt;&lt;/strong&gt; — Boilerplate disappears&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Manage by method&lt;/strong&gt; — Easy to add/remove actions&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Coexists with existing code&lt;/strong&gt; — Gradual migration possible&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Less boilerplate means cleaner, more readable code.&lt;/p&gt;
&lt;h3 id=&quot;try-it-yourself-1&quot;&gt;Try It Yourself&lt;/h3&gt;
&lt;p&gt;All examples in this tutorial are available in the &lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/scivicslab/actor-WF-examples&quot;&gt;actor-WF-examples&lt;/a&gt; repository. To run the &lt;code&gt;@Action&lt;/code&gt; annotation version:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;git clone https://github.com/scivicslab/POJO-actor
cd POJO-actor
mvn install

cd ..
git clone https://github.com/scivicslab/actor-WF-examples
cd actor-WF-examples
mvn compile
mvn exec:java -Dexec.mainClass=&quot;com.scivicslab.turing.TuringWorkflowApp&quot; -Dexec.args=&quot;turing87-array --action&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;See the repository's README for detailed setup instructions and project structure.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;references-1&quot;&gt;References&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Official Manual&lt;/strong&gt;: &lt;a rel=&quot;nofollow&quot; href=&quot;https://scivicslab.com/docs/pojo-actor/introduction&quot;&gt;https://scivicslab.com/docs/pojo-actor/introduction&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;GitHub (POJO-actor)&lt;/strong&gt;: &lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/scivicslab/POJO-actor&quot;&gt;https://github.com/scivicslab/POJO-actor&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;GitHub (Workflow examples)&lt;/strong&gt;: &lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/scivicslab/actor-WF-examples&quot;&gt;https://github.com/scivicslab/actor-WF-examples&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Maven Repository&lt;/strong&gt;: &lt;a rel=&quot;nofollow&quot; href=&quot;https://mvnrepository.com/search?q=com.scivicslab&quot;&gt;https://mvnrepository.com/search?q=com.scivicslab&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;POJO-actor v1.0 Introduction (blog)&lt;/strong&gt;: &lt;a rel=&quot;nofollow&quot; href=&quot;https://coderlegion.com/8748/pojo-actor-v1-0-a-lightweight-actor-model-library-for-java?show=8748#q8748&quot;&gt;A Lightweight Actor Model Library for Java&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Tutorial Part 2-1 (blog)&lt;/strong&gt;: &lt;a rel=&quot;nofollow&quot; href=&quot;https://coderlegion.com/9131/pojo-actor-tutorial-part-2-first-half-workflow-language-basics&quot;&gt;Workflow Language Basics&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Tutorial Part 2-2 (blog)&lt;/strong&gt;: &lt;a rel=&quot;nofollow&quot; href=&quot;https://coderlegion.com/9132/pojo-actor-tutorial-part-2-second-half-creating-workflows&quot;&gt;Creating Workflows&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/10613/pojo-actor-tutorial-part-improving-the-workflow-api-introducing-the-action-annotation</guid>
<pubDate>Wed, 28 Jan 2026 06:29:19 +0000</pubDate>
</item>
<item>
<title>POJO-actor Tutorial Part 2 (Second Half): Creating Workflows</title>
<link>https://coderlegion.com/9132/pojo-actor-tutorial-part-2-second-half-creating-workflows</link>
<description>&lt;h3 id=&quot;recap-of-the-first-half&quot;&gt;Recap of the First Half&lt;/h3&gt;
&lt;p&gt;In the &lt;a rel=&quot;nofollow&quot; href=&quot;../010_WorkflowBasics_241001_oo01/010_WorkflowBasics_241001_oo01_en.md&quot;&gt;first half&lt;/a&gt;, we introduced the basic concepts of the workflow engine (actor-WF) added in POJO-actor v2.x. actor-WF is described using a simple model of &quot;send this message to this actor,&quot; where all steps can be expressed uniformly with just three elements: actor, method, and arguments. Workflow behavior, conditional branching, and loops are expressed as state transitions, essentially having the same expressive power as a Turing machine.&lt;/p&gt;
&lt;p&gt;In the first half, we examined YAML workflow descriptions and state transition-based conditional branching using turing83 (a Turing machine that outputs the binary representation of 1/3) and turing87 (a Turing machine that outputs an irrational number) as examples. In this second half, we introduce how to implement IIActorRef, which is necessary for creating your own workflows.&lt;/p&gt;
&lt;h3 id=&quot;overview&quot;&gt;Overview&lt;/h3&gt;
&lt;p&gt;This tutorial explains the complete process of making ordinary Java classes (POJOs) callable from workflows.&lt;/p&gt;
&lt;p&gt;Using turing83 (a Turing machine that outputs the binary representation of 1/3) as an example, we proceed through the following four steps:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Create POJO&lt;/strong&gt;: An ordinary Java class with business logic&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Create IIActorRef&lt;/strong&gt;: An adapter to call the POJO from workflows&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Create YAML&lt;/strong&gt;: The workflow definition&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Execute&lt;/strong&gt;: Run the workflow with IIActorSystem&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;[YAML] → [Interpreter] → [IIActorRef] → [POJO]
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;step-1-creating-the-pojo&quot;&gt;Step 1: Creating the POJO&lt;/h3&gt;
&lt;p&gt;First, create an ordinary Java class containing the business logic you want to manipulate in the workflow. This class knows nothing about workflows. It's just a plain POJO.&lt;/p&gt;
&lt;h4 id=&quot;turing-java-excerpt-&quot;&gt;Turing.java (excerpt)&lt;/h4&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;public class Turing {
    int currentPos = 0;
    Tape tape = new Tape();

    /** Initialize the machine */
    public void initMachine() {
        this.currentPos = 0;
        this.tape = new Tape();
    }

    /** Write a value to the tape */
    public void put(String value) {
        this.tape.setWithResizing(this.currentPos, value);
    }

    /** Move the head (&quot;L&quot;=left, &quot;R&quot;=right) */
    public void move(String direction) {
        if (direction.equalsIgnoreCase(&quot;L&quot;)) {
            this.currentPos--;
        } else if (direction.equalsIgnoreCase(&quot;R&quot;)) {
            this.currentPos++;
        }
    }

    /** Print the tape contents */
    public void printTape() {
        System.out.println(&quot;TAPE\t&quot; + this.tape.toString());
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Key points&lt;/strong&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;No knowledge of workflows or actors required&lt;/li&gt;
&lt;li&gt;Testable as an ordinary Java class&lt;/li&gt;
&lt;li&gt;Methods have simple inputs and outputs&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;step-2-creating-the-iiactorref&quot;&gt;Step 2: Creating the IIActorRef&lt;/h3&gt;
&lt;p&gt;Next, create an adapter (&lt;code&gt;IIActorRef&lt;/code&gt;) to call the POJO from workflows. This class receives method names and arguments specified in YAML and invokes the actual POJO methods.&lt;/p&gt;
&lt;h4 id=&quot;turingiiar-java&quot;&gt;TuringIIAR.java&lt;/h4&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;import com.scivicslab.pojoactor.core.ActionResult;
import com.scivicslab.pojoactor.workflow.IIActorRef;
import com.scivicslab.pojoactor.workflow.IIActorSystem;
import org.json.JSONArray;

public class TuringIIAR extends IIActorRef&amp;lt;Turing&amp;gt; {

    public TuringIIAR(String actorName, Turing turing, IIActorSystem system) {
        super(actorName, turing, system);
    }

    // Parse arguments from JSON array: [&quot;0&quot;] → &quot;0&quot;
    private String parseFirstArg(String args) {
        if (args == null || args.isEmpty() || args.equals(&quot;[]&quot;)) {
            return &quot;&quot;;
        }
        JSONArray array = new JSONArray(args);
        return array.length() &amp;gt; 0 ? array.getString(0) : &quot;&quot;;
    }

    @Override
    public ActionResult callByActionName(String actionName, String args) {
        try {
            switch (actionName) {
                case &quot;initMachine&quot;:
                    this.tell(t -&amp;gt; t.initMachine()).get();
                    return new ActionResult(true, &quot;Machine initialized&quot;);

                case &quot;put&quot;:
                    String putValue = parseFirstArg(args);
                    this.tell(t -&amp;gt; t.put(putValue)).get();
                    return new ActionResult(true, &quot;Put &quot; + putValue);

                case &quot;move&quot;:
                    String direction = parseFirstArg(args);
                    this.tell(t -&amp;gt; t.move(direction)).get();
                    return new ActionResult(true, &quot;Moved &quot; + direction);

                case &quot;printTape&quot;:
                    this.tell(t -&amp;gt; t.printTape()).get();
                    return new ActionResult(true, &quot;Tape printed&quot;);

                default:
                    return new ActionResult(false, &quot;Unknown action: &quot; + actionName);
            }
        } catch (Exception e) {
            return new ActionResult(false, &quot;Error: &quot; + e.getMessage());
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h4 id=&quot;key-concepts&quot;&gt;Key Concepts&lt;/h4&gt;
&lt;h5 id=&quot;the-callbyactionname-method&quot;&gt;The callByActionName Method&lt;/h5&gt;
&lt;p&gt;The core part to implement when extending &lt;code&gt;IIActorRef&lt;/code&gt; is the &lt;code&gt;callByActionName&lt;/code&gt; method. The first argument &lt;code&gt;actionName&lt;/code&gt; receives the name specified in YAML's &lt;code&gt;method:&lt;/code&gt; (e.g., &lt;code&gt;&quot;put&quot;&lt;/code&gt;, &lt;code&gt;&quot;move&quot;&lt;/code&gt;), and the second argument &lt;code&gt;args&lt;/code&gt; receives the YAML's &lt;code&gt;arguments:&lt;/code&gt; converted to a JSON array string (e.g., &lt;code&gt;[&quot;0&quot;]&lt;/code&gt;, &lt;code&gt;[&quot;R&quot;]&lt;/code&gt;). Within this method, you call the appropriate POJO method based on actionName and return the result as an &lt;code&gt;ActionResult&lt;/code&gt;.&lt;/p&gt;
&lt;h5 id=&quot;the-role-of-actionresult&quot;&gt;The Role of ActionResult&lt;/h5&gt;
&lt;p&gt;The return value &lt;code&gt;ActionResult&lt;/code&gt; controls workflow branching. Returning &lt;code&gt;ActionResult(true, message)&lt;/code&gt; is considered success, and the next action is executed, or if all actions succeed, transition to the to-state occurs. On the other hand, returning &lt;code&gt;ActionResult(false, message)&lt;/code&gt; is considered failure, and this Row is immediately aborted, and the next Row with the same from-state is tried. This mechanism enables conditional branching by listing multiple Rows with the same from-state.&lt;/p&gt;
&lt;h5 id=&quot;tell-and-ask-&quot;&gt;tell() and ask()&lt;/h5&gt;
&lt;p&gt;&lt;code&gt;tell()&lt;/code&gt; is used for method calls that don't use the return value, and &lt;code&gt;ask()&lt;/code&gt; is used for method calls that use the return value (such as condition checking).&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;// tell: wait for result but don't use the value
this.tell(t -&amp;gt; t.put(&quot;0&quot;)).get();

// ask: use the result value
boolean result = this.ask(t -&amp;gt; t.matchCurrentValue(&quot;1&quot;)).get();
return new ActionResult(result, &quot;match=&quot; + result);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Within IIActorRef, simply write &lt;code&gt;tell(action).get()&lt;/code&gt; or &lt;code&gt;ask(action).get()&lt;/code&gt;. You don't need to worry about which thread pool executes the action on the IIActorRef side. The Interpreter examines the Action class's &lt;code&gt;execution&lt;/code&gt; field (ExecutionMode) and selects the appropriate pool. ExecutionMode has two types: &lt;code&gt;POOL&lt;/code&gt; (execute on WorkStealingPool, default) and &lt;code&gt;DIRECT&lt;/code&gt; (direct call). Unless explicitly specified in YAML, POOL is used, so CPU-intensive processing is executed safely.&lt;/p&gt;
&lt;h3 id=&quot;step-3-creating-the-yaml-workflow&quot;&gt;Step 3: Creating the YAML Workflow&lt;/h3&gt;
&lt;p&gt;Once the POJO and IIActorRef are ready, describe the workflow in YAML.&lt;/p&gt;
&lt;h4 id=&quot;turing83-yaml&quot;&gt;turing83.yaml&lt;/h4&gt;
&lt;pre&gt;&lt;code class=&quot;language-yaml&quot;&gt;name: turing83
steps:
- states: [&quot;0&quot;, &quot;1&quot;]
  actions:
  - actor: turing
    method: initMachine
- states: [&quot;1&quot;, &quot;2&quot;]
  actions:
  - actor: turing
    method: printTape
- states: [&quot;2&quot;, &quot;3&quot;]
  actions:
  - actor: turing
    method: put
    arguments: &quot;0&quot;
  - actor: turing
    method: move
    arguments: &quot;R&quot;
- states: [&quot;3&quot;, &quot;4&quot;]
  actions:
  - actor: turing
    method: move
    arguments: &quot;R&quot;
- states: [&quot;4&quot;, &quot;5&quot;]
  actions:
  - actor: turing
    method: put
    arguments: &quot;1&quot;
  - actor: turing
    method: move
    arguments: &quot;R&quot;
- states: [&quot;5&quot;, &quot;1&quot;]
  actions:
  - actor: turing
    method: move
    arguments: &quot;R&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;h4 id=&quot;yaml-structure&quot;&gt;YAML Structure&lt;/h4&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;&lt;th&gt;Element &lt;/th&gt;&lt;th&gt;Description&lt;/th&gt;&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;name&lt;/code&gt; &lt;/td&gt;&lt;td&gt;Workflow name&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;steps&lt;/code&gt; &lt;/td&gt;&lt;td&gt;List of Rows&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;states&lt;/code&gt; &lt;/td&gt;&lt;td&gt;&lt;code&gt;[from-state, to-state]&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;actions&lt;/code&gt; &lt;/td&gt;&lt;td&gt;List of actions to execute&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;actor&lt;/code&gt; &lt;/td&gt;&lt;td&gt;IIActorRef name (the name registered in Step 4)&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;method&lt;/code&gt; &lt;/td&gt;&lt;td&gt;Action name passed to callByActionName&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;arguments&lt;/code&gt; &lt;/td&gt;&lt;td&gt;Arguments passed to callByActionName (converted to JSON array)&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3 id=&quot;step-4-creating-the-execution-application&quot;&gt;Step 4: Creating the Execution Application&lt;/h3&gt;
&lt;p&gt;Finally, create an application that combines everything and executes it.&lt;/p&gt;
&lt;h4 id=&quot;turingworkflowapp-java-1&quot;&gt;TuringWorkflowApp.java&lt;/h4&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;import com.scivicslab.pojoactor.core.ActionResult;
import com.scivicslab.pojoactor.workflow.IIActorSystem;
import com.scivicslab.pojoactor.workflow.Interpreter;
import java.io.InputStream;

public class TuringWorkflowApp {

    public static void main(String[] args) {
        String yamlPath = &quot;/code/turing83.yaml&quot;;
        new TuringWorkflowApp().runWorkflow(yamlPath);
    }

    public void runWorkflow(String yamlPath) {
        // 1. Create IIActorSystem
        IIActorSystem system = new IIActorSystem(&quot;turing-system&quot;);

        try {
            // 2. Create POJO
            Turing turing = new Turing();

            // 3. Wrap with IIActorRef and register with system
            TuringIIAR turingActor = new TuringIIAR(&quot;turing&quot;, turing, system);
            system.addIIActor(turingActor);

            // 4. Create Interpreter
            Interpreter interpreter = new Interpreter.Builder()
                    .loggerName(&quot;TuringWorkflow&quot;)
                    .team(system)
                    .build();

            // 5. Load YAML
            InputStream yamlStream = getClass().getResourceAsStream(yamlPath);
            interpreter.readYaml(yamlStream);

            // 6. Execute workflow (max 50 iterations)
            ActionResult result = interpreter.runUntilEnd(50);

            // 7. Display result
            System.out.println(&quot;Result: &quot; + result.getResult());

        } finally {
            // 8. Cleanup
            system.terminateIIActors();
            system.terminate();
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h4 id=&quot;execution-flow&quot;&gt;Execution Flow&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;1. Create IIActorSystem
       ↓
2. Create POJO (Turing)
       ↓
3. Wrap with IIActorRef (TuringIIAR)
   └─ Register with name &quot;turing&quot;
       ↓
4. Create Interpreter
   └─ Reference IIActorSystem
       ↓
5. Load YAML
   └─ actor: &quot;turing&quot; → Reference registered TuringIIAR
       ↓
6. Execute with runUntilEnd()
   └─ Repeat state transitions
       ↓
7. Finish
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;how-to-run&quot;&gt;How to Run&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;# Build POJO-actor
git clone https://github.com/scivicslab/POJO-actor
cd POJO-actor
mvn clean install

# Build and run actor-WF-examples
git clone https://github.com/scivicslab/actor-WF-examples
cd actor-WF-examples
mvn compile
mvn exec:java -Dexec.mainClass=&quot;com.scivicslab.turing.TuringWorkflowApp&quot; -Dexec.args=&quot;turing83&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Output:&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Loading workflow from: /code/turing83.yaml
Workflow loaded successfully
Executing workflow...

TAPE    0    value
TAPE    0    value    0 1
TAPE    0    value    0 1 0 1
TAPE    0    value    0 1 0 1 0 1
...

Workflow finished: Maximum iterations (50) exceeded
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;summary-2&quot;&gt;Summary&lt;/h3&gt;
&lt;p&gt;Steps to create a workflow:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;&lt;th&gt;Step &lt;/th&gt;&lt;th&gt;What to Create &lt;/th&gt;&lt;th&gt;Role&lt;/th&gt;&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;1 &lt;/td&gt;&lt;td&gt;POJO &lt;/td&gt;&lt;td&gt;Business logic (workflow-independent)&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;2 &lt;/td&gt;&lt;td&gt;IIActorRef &lt;/td&gt;&lt;td&gt;Bridge between YAML and POJO&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;3 &lt;/td&gt;&lt;td&gt;YAML &lt;/td&gt;&lt;td&gt;Workflow definition&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;4 &lt;/td&gt;&lt;td&gt;App &lt;/td&gt;&lt;td&gt;Assemble and execute everything&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;This structure separates business logic (POJO) from workflow control (YAML), allowing each to be tested and modified independently.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;strong&gt;Last updated&lt;/strong&gt;: 2025-12-31&lt;br&gt;
&lt;strong&gt;Related documents&lt;/strong&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;../010_WorkflowBasics_241001_oo01/010_WorkflowBasics_241001_oo01_en.md&quot;&gt;Workflow Basics&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;../../020_specs/020_workflow/025_IIActorRefSpecs_251231_oo01/&quot;&gt;IIActorRef Specification&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/9132/pojo-actor-tutorial-part-2-second-half-creating-workflows</guid>
<pubDate>Wed, 31 Dec 2025 10:51:18 +0000</pubDate>
</item>
<item>
<title>POJO-actor Tutorial Part 2 (First Half): Workflow Language Basics</title>
<link>https://coderlegion.com/9131/pojo-actor-tutorial-part-2-first-half-workflow-language-basics</link>
<description>&lt;h3 id=&quot;recap-of-part-1&quot;&gt;Recap of Part 1&lt;/h3&gt;
&lt;p&gt;In &lt;a rel=&quot;nofollow&quot; href=&quot;https://coderlegion.com/8748/pojo-actor-v1-0-a-lightweight-actor-model-library-for-java?show=8748#q8748&quot;&gt;Part 1&lt;/a&gt;, we introduced the basic features of POJO-actor ver 1.x: &lt;code&gt;POJO-actor&lt;/code&gt; is a library that allows you to convert ordinary POJOs into actors simply by wrapping them with &lt;code&gt;ActorRef&amp;lt;T&amp;gt;&lt;/code&gt;, without modifying existing code. With Virtual Threads available since JDK 21, even commodity hardware can run tens of thousands of actors.&lt;/p&gt;
&lt;p&gt;Since most of this is implemented using standard JDK 21+ features, POJO-actor ver 1.x is realized in &lt;strong&gt;under 1000 lines of code&lt;/strong&gt;. POJO-actor ver 1.x was designed to explain the core implementation by implementing only the fundamental concepts.&lt;/p&gt;
&lt;p&gt;Version 2.x adds features for practical use.&lt;/p&gt;
&lt;h3 id=&quot;from-actor-to-agent-introducing-the-workflow-engine&quot;&gt;From Actor to Agent: Introducing the Workflow Engine&lt;/h3&gt;
&lt;p&gt;In the actor model, each actor has a mailbox (message queue). Each actor retrieves messages from its queue one at a time and processes them sequentially. Meanwhile, multiple actors can operate independently and concurrently. This &quot;sequential internally, parallel externally&quot; structure allows programmers to achieve concurrent processing without worrying about locks.&lt;/p&gt;
&lt;p&gt;The actor model is intuitive for humans, so it inherently has various applications. However, traditionally, implementations where one actor occupies one OS thread were common. This constrained the number of actors to the number of CPU cores, making large-scale applications difficult. With Virtual Threads since JDK 21, this constraint is being lifted. Being able to handle tens of thousands of actors opens up applications such as Agent-Based Simulations with many agents, or infrastructure-as-code platforms that monitor and autonomously repair computing resources.&lt;/p&gt;
&lt;p&gt;As a foundation for enabling actors to behave autonomously—that is, to act as &lt;strong&gt;Agents that observe their environment and act according to their state&lt;/strong&gt;—a &lt;strong&gt;workflow engine&lt;/strong&gt; has been added in POJO-actor v2.x.&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;An agent is anything that can be viewed as perceiving its environment through sensors and acting upon that environment through actuators.&lt;br&gt;
— Russell &amp;amp; Norvig, &quot;Artificial Intelligence: A Modern Approach&quot;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 id=&quot;actor-wf-simplifying-workflows&quot;&gt;actor-WF: Simplifying Workflows&lt;/h3&gt;
&lt;p&gt;POJO-actor's workflow (actor-WF) is described using a simple model of &lt;strong&gt;&quot;send this message to this actor&quot;&lt;/strong&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-yaml&quot;&gt;  - states: [&quot;start&quot;, &quot;processed&quot;]
    actions:
      - actor: dataProcessor    # actor name
        method: process          # method name
        arguments: [&quot;data.csv&quot;]  # arguments
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;With just three elements—actor, method, and arguments—all steps can be expressed uniformly. This follows the same mental model as tell()/ask() in Java code, and since actors operate independently, parallel execution can be described naturally.&lt;/p&gt;
&lt;p&gt;Workflow behavior, conditional branching, and loops are expressed as state transitions. actor-WF is essentially a Turing machine, and complex conditional branching that traditional YAML/JSON-based workflow languages struggle with can be expressed without introducing custom syntax.&lt;/p&gt;
&lt;h3 id=&quot;how-actor-wf-works&quot;&gt;How actor-WF Works&lt;/h3&gt;
&lt;p&gt;actor-WF is essentially a Turing machine. Here we reproduce examples from Turing's original paper using actor-WF.&lt;/p&gt;
&lt;p&gt;The following workflow is a Turing machine that computes the binary representation of the rational number 1/3. It writes symbols &quot;0 1 0 1 0 1...&quot; alternately on the tape while cycling through states 1→2→3→4→5→1.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://coderlegion.com/?qa=blob&amp;amp;qa_blobid=1111576023169230765&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;— Charles Petzold, &quot;The Annotated Turing&quot;, Wiley Publishing, Inc. (2008)&lt;/p&gt;
&lt;/blockquote&gt;
&lt;pre&gt;&lt;code class=&quot;language-yaml&quot;&gt;name: turing83
steps:
- states: [&quot;0&quot;, &quot;1&quot;]
  actions:
  - actor: turing
    method: initMachine
- states: [&quot;1&quot;, &quot;2&quot;]
  actions:
  - actor: turing
    method: printTape
- states: [&quot;2&quot;, &quot;3&quot;]
  actions:
  - actor: turing
    method: put
    arguments: &quot;0&quot;
  - actor: turing
    method: move
    arguments: &quot;R&quot;
- states: [&quot;3&quot;, &quot;4&quot;]
  actions:
  - actor: turing
    method: move
    arguments: &quot;R&quot;
- states: [&quot;4&quot;, &quot;5&quot;]
  actions:
  - actor: turing
    method: put
    arguments: &quot;1&quot;
  - actor: turing
    method: move
    arguments: &quot;R&quot;
- states: [&quot;5&quot;, &quot;1&quot;]
  actions:
  - actor: turing
    method: move
    arguments: &quot;R&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;h4 id=&quot;workflow-behavior&quot;&gt;Workflow Behavior&lt;/h4&gt;
&lt;p&gt;A workflow has multiple &lt;strong&gt;Rows&lt;/strong&gt; (combinations of &lt;code&gt;states&lt;/code&gt; and &lt;code&gt;actions&lt;/code&gt;) under &lt;code&gt;steps&lt;/code&gt;. The Interpreter maintains a current state (initially &lt;code&gt;&quot;0&quot;&lt;/code&gt;) and operates as follows.&lt;/p&gt;
&lt;h5 id=&quot;state-matching-and-transitions&quot;&gt;State Matching and Transitions&lt;/h5&gt;
&lt;p&gt;Each Row's &lt;code&gt;states&lt;/code&gt; is in the format &lt;code&gt;[from-state, to-state]&lt;/code&gt;. The Interpreter searches from top to bottom for a Row with a from-state matching the current state.&lt;/p&gt;
&lt;p&gt;Let's look at the turing83 example:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Initial state&lt;/strong&gt;: current state = &lt;code&gt;&quot;0&quot;&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;First Row &lt;code&gt;states: [&quot;0&quot;, &quot;1&quot;]&lt;/code&gt; matches&lt;/li&gt;
&lt;li&gt;Execute &lt;code&gt;actions&lt;/code&gt; (&lt;code&gt;initMachine&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;On success, update current state to &lt;code&gt;&quot;1&quot;&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Search for next matching Row → &lt;code&gt;states: [&quot;1&quot;, &quot;2&quot;]&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Continue cycling: &lt;code&gt;&quot;1&quot;&lt;/code&gt;→&lt;code&gt;&quot;2&quot;&lt;/code&gt;→&lt;code&gt;&quot;3&quot;&lt;/code&gt;→&lt;code&gt;&quot;4&quot;&lt;/code&gt;→&lt;code&gt;&quot;5&quot;&lt;/code&gt;→&lt;code&gt;&quot;1&quot;&lt;/code&gt;→...&lt;/li&gt;
&lt;/ol&gt;
&lt;h5 id=&quot;action-execution-and-conditional-branching&quot;&gt;Action Execution and Conditional Branching&lt;/h5&gt;
&lt;p&gt;Each Row's &lt;code&gt;actions&lt;/code&gt; are executed from top to bottom. Actions return either success (true) or failure (false).&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;All succeed&lt;/strong&gt;: Transition to to-state&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Failure midway&lt;/strong&gt;: Abort this Row and try the next Row with the same from-state&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This mechanism allows conditional branching by listing multiple Rows with the same from-state.&lt;/p&gt;
&lt;h5 id=&quot;termination-conditions&quot;&gt;Termination Conditions&lt;/h5&gt;
&lt;p&gt;The workflow terminates under any of the following:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Current state becomes &lt;code&gt;&quot;end&quot;&lt;/code&gt; (success)&lt;/li&gt;
&lt;li&gt;No matching Row is found (failure)&lt;/li&gt;
&lt;li&gt;Maximum iteration count is reached (failure)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Since turing83 has no &lt;code&gt;&quot;end&quot;&lt;/code&gt; state and is an infinite loop, it terminates when the maximum iteration count is reached.&lt;/p&gt;
&lt;h4 id=&quot;running-the-example&quot;&gt;Running the Example&lt;/h4&gt;
&lt;p&gt;Clone &lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/scivicslab/actor-WF-examples&quot;&gt;https://github.com/scivicslab/actor-WF-examples&lt;/a&gt;, then build and run in the &lt;code&gt;actor-WF-examples&lt;/code&gt; directory (requires JDK 21 or later):&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;git clone https://github.com/scivicslab/POJO-actor
cd POJO-actor
mvn clean install

git clone https://github.com/scivicslab/actor-WF-examples
cd actor-WF-examples
mvn compile
mvn exec:java -Dexec.mainClass=&quot;com.scivicslab.turing.TuringWorkflowApp&quot; -Dexec.args=&quot;turing83&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Output:&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Loading workflow from: /code/turing83.yaml
Workflow loaded successfully
Executing workflow...

TAPE    0    value
TAPE    0    value    0 1
TAPE    0    value    0 1 0 1
TAPE    0    value    0 1 0 1 0 1
TAPE    0    value    0 1 0 1 0 1 0 1
...

Workflow finished: Maximum iterations (50) exceeded
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The pattern &quot;0 1 0 1...&quot; is generated on the tape. Since it's an infinite loop, it terminates when maxIterations (50) is reached.&lt;/p&gt;
&lt;h3 id=&quot;a-more-complex-example&quot;&gt;A More Complex Example&lt;/h3&gt;
&lt;p&gt;The following is a Turing machine that outputs an irrational number. It outputs 001011011101111011111...&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://coderlegion.com/?qa=blob&amp;amp;qa_blobid=9233200913215203828&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;— Charles Petzold, &quot;The Annotated Turing&quot;, Wiley Publishing, Inc. (2008)&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Written in actor-WF:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-yaml&quot;&gt;name: turing87
steps:
- states: [&quot;0&quot;, &quot;100&quot;]
  actions:
  - actor: turing
    method: initMachine
- states: [&quot;100&quot;, &quot;1&quot;]
  actions:
  - actor: turing
    method: printTape
- states: [&quot;1&quot;, &quot;2&quot;]
  actions:
  - actor: turing
    method: put
    arguments: &quot;e&quot;
  - actor: turing
    method: move
    arguments: &quot;R&quot;
  - actor: turing
    method: put
    arguments: &quot;e&quot;
  - actor: turing
    method: move
    arguments: &quot;R&quot;
  - actor: turing
    method: put
    arguments: &quot;0&quot;
  - actor: turing
    method: move
    arguments: &quot;R&quot;
  - actor: turing
    method: move
    arguments: &quot;R&quot;
  - actor: turing
    method: put
    arguments: &quot;0&quot;
  - actor: turing
    method: move
    arguments: &quot;L&quot;
  - actor: turing
    method: move
    arguments: &quot;L&quot;
- states: [&quot;101&quot;, &quot;2&quot;]
  actions:
  - actor: turing
    method: printTape
- states: [&quot;2&quot;, &quot;2&quot;]
  actions:
  - actor: turing
    method: matchCurrentValue
    arguments: &quot;1&quot;
  - actor: turing
    method: move
    arguments: &quot;R&quot;
  - actor: turing
    method: put
    arguments: &quot;x&quot;
  - actor: turing
    method: move
    arguments: &quot;L&quot;
  - actor: turing
    method: move
    arguments: &quot;L&quot;
  - actor: turing
    method: move
    arguments: &quot;L&quot;
- states: [&quot;2&quot;, &quot;3&quot;]
  actions:
  - actor: turing
    method: matchCurrentValue
    arguments: &quot;0&quot;
- states: [&quot;3&quot;, &quot;3&quot;]
  actions:
  - actor: turing
    method: isAny
  - actor: turing
    method: move
    arguments: &quot;R&quot;
  - actor: turing
    method: move
    arguments: &quot;R&quot;
- states: [&quot;3&quot;, &quot;4&quot;]
  actions:
  - actor: turing
    method: isNone
  - actor: turing
    method: put
    arguments: &quot;1&quot;
  - actor: turing
    method: move
    arguments: &quot;L&quot;
- states: [&quot;4&quot;, &quot;3&quot;]
  actions:
  - actor: turing
    method: matchCurrentValue
    arguments: &quot;x&quot;
  - actor: turing
    method: put
    arguments: &quot; &quot;
  - actor: turing
    method: move
    arguments: &quot;R&quot;
- states: [&quot;4&quot;, &quot;5&quot;]
  actions:
  - actor: turing
    method: matchCurrentValue
    arguments: &quot;e&quot;
  - actor: turing
    method: move
    arguments: &quot;R&quot;
- states: [&quot;4&quot;, &quot;4&quot;]
  actions:
  - actor: turing
    method: isNone
  - actor: turing
    method: move
    arguments: &quot;L&quot;
  - actor: turing
    method: move
    arguments: &quot;L&quot;
- states: [&quot;5&quot;, &quot;5&quot;]
  actions:
  - actor: turing
    method: isAny
  - actor: turing
    method: move
    arguments: &quot;R&quot;
  - actor: turing
    method: move
    arguments: &quot;R&quot;
- states: [&quot;5&quot;, &quot;101&quot;]
  actions:
  - actor: turing
    method: isNone
  - actor: turing
    method: put
    arguments: &quot;0&quot;
  - actor: turing
    method: move
    arguments: &quot;L&quot;
  - actor: turing
    method: move
    arguments: &quot;L&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This example includes &lt;strong&gt;conditional branching using multiple Rows with the same from-state&lt;/strong&gt;.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-yaml&quot;&gt;# From state 2: if current value is &quot;1&quot;, stay in state 2
- states: [&quot;2&quot;, &quot;2&quot;]
  actions:
    - actor: turing
      method: matchCurrentValue
      arguments: &quot;1&quot;
    # ... subsequent actions

# From state 2: if current value is &quot;0&quot;, go to state 3
- states: [&quot;2&quot;, &quot;3&quot;]
  actions:
    - actor: turing
      method: matchCurrentValue
      arguments: &quot;0&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;ol&gt;
&lt;li&gt;If &lt;code&gt;matchCurrentValue(&quot;1&quot;)&lt;/code&gt; returns &lt;code&gt;true&lt;/code&gt; → Execute first Row, remain in state 2&lt;/li&gt;
&lt;li&gt;If &lt;code&gt;matchCurrentValue(&quot;1&quot;)&lt;/code&gt; returns &lt;code&gt;false&lt;/code&gt; → Abort this Row, try next Row&lt;/li&gt;
&lt;li&gt;If &lt;code&gt;matchCurrentValue(&quot;0&quot;)&lt;/code&gt; returns &lt;code&gt;true&lt;/code&gt; → Transition to state 3&lt;/li&gt;
&lt;/ol&gt;
&lt;h5 id=&quot;running-the-example-1&quot;&gt;Running the Example&lt;/h5&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;git clone https://github.com/scivicslab/POJO-actor
cd POJO-actor
mvn clean install

git clone https://github.com/scivicslab/actor-WF-examples
cd actor-WF-examples
mvn compile
mvn exec:java -Dexec.mainClass=&quot;com.scivicslab.turing.TuringWorkflowApp&quot; -Dexec.args=&quot;turing87&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Output:&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Loading workflow from: /code/turing87.yaml
Workflow loaded successfully
Executing workflow...

TAPE    0    value
TAPE    0    value    ee0 0 1 0
TAPE    0    value    ee0 0 1 0 1 1 0
TAPE    0    value    ee0 0 1 0 1 1 0 1 1 1 0
TAPE    0    value    ee0 0 1 0 1 1 0 1 1 1 0 1 1 1 1 0

Workflow finished: Maximum iterations (200) exceeded
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;summary-3&quot;&gt;Summary&lt;/h3&gt;
&lt;p&gt;What we covered in this tutorial:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Basic workflow structure&lt;/strong&gt;: Combinations of &lt;code&gt;states&lt;/code&gt; and &lt;code&gt;actions&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;State transitions&lt;/strong&gt;: Control flow through transitions from from-state to to-state&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Conditional branching&lt;/strong&gt;: Achieved by listing multiple Rows with the same from-state&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Loops&lt;/strong&gt;: Achieved through state cycles (e.g., 1→2→3→4→5→1)&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;actor-WF is based on the same design philosophy as Turing machines. Any complexity of processing can be expressed through combinations of state transitions and actions.&lt;/p&gt;
&lt;h3 id=&quot;next-steps&quot;&gt;Next Steps&lt;/h3&gt;
&lt;p&gt;To create your own workflows, you need to implement an adapter (IIActorRef) to call POJOs from workflows. See the following for details:&lt;/p&gt;
&lt;h3 id=&quot;references-2&quot;&gt;References&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;POJO-actor&lt;/strong&gt;: &lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/scivicslab/POJO-actor&quot;&gt;https://github.com/scivicslab/POJO-actor&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;actor-WF-examples&lt;/strong&gt;: &lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/scivicslab/actor-WF-examples&quot;&gt;https://github.com/scivicslab/actor-WF-examples&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/9131/pojo-actor-tutorial-part-2-first-half-workflow-language-basics</guid>
<pubDate>Wed, 31 Dec 2025 10:48:21 +0000</pubDate>
</item>
<item>
<title>POJO-actor v1.0: A Lightweight Actor Model Library for Java</title>
<link>https://coderlegion.com/8748/pojo-actor-v1-0-a-lightweight-actor-model-library-for-java</link>
<description>&lt;p&gt;This article was originally published on Medium:&lt;br&gt;
&lt;a rel=&quot;nofollow&quot; href=&quot;https://medium.com/@devteam_58690/pojo-actor-v1-0-a-lightweight-actor-model-library-for-java-dc3227c17acb&quot;&gt;https://medium.com/@devteam_58690/pojo-actor-v1-0-a-lightweight-actor-model-library-for-java-dc3227c17acb&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;The actor model is a programming paradigm where independent entities (actors) communicate through message passing, eliminating the need for locks and avoiding the complexities of shared-state concurrency. Traditionally, using the actor model required specialized frameworks, and because these frameworks relied on real operating system threads, you could only create as many actors as you had CPU cores — typically just a handful. However, recent advancements in the JDK, particularly the introduction of virtual threads in Java 21, have changed everything: now even an ordinary laptop can handle tens of thousands of actors simultaneously.&lt;/p&gt;
&lt;p&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/scivicslab/POJO-actor&quot;&gt;https://github.com/scivicslab/POJO-actor&lt;/a&gt;&lt;/p&gt;
&lt;h3 id=&quot;architecture&quot;&gt;Architecture&lt;/h3&gt;
&lt;p&gt;POJO-actor implements a simplified actor model built on modern Java features. Built with just ~800 lines of code, POJO-actor delivers a practical actor model implementation without sacrificing functionality or performance.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;ActorSystem: Manages actor lifecycle and configurable work-stealing thread pools&lt;/li&gt;
&lt;li&gt;ActorRef: Reference to an actor that provides tell() and ask() messaging interface&lt;/li&gt;
&lt;li&gt;Virtual Threads: Each actor runs on its own virtual thread for lightweight message handling&lt;/li&gt;
&lt;li&gt;Work-Stealing Pools: Heavy computations are delegated to configurable thread pools&lt;/li&gt;
&lt;li&gt;Zero Reflection: Built entirely with standard JDK APIs, making it GraalVM Native Image ready&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;quick-start&quot;&gt;Quick Start&lt;/h3&gt;
&lt;h4 id=&quot;maven-dependency&quot;&gt;Maven Dependency&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;dependency&amp;gt;
    &amp;lt;groupId&amp;gt;com.scivicslab&amp;lt;/groupId&amp;gt;
    &amp;lt;artifactId&amp;gt;POJO-actor&amp;lt;/artifactId&amp;gt;
    &amp;lt;version&amp;gt;1.0.0&amp;lt;/version&amp;gt;
&amp;lt;/dependency&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Note: This library will be published to Maven Central soon. For now, please install it to your local repository first:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;git clone https://github.com/scivicslab/POJO-actor
cd POJO-actor
./mvnw install

&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;basic-usage&quot;&gt;Basic Usage&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;import com.scivicslab.pojoactor.ActorSystem;
import com.scivicslab.pojoactor.ActorRef;

// Define your POJO
class Counter {
    private int count = 0;

    public void increment() {
        count++;
    }

    public int getValue() {
        return count;
    }
}

// Create actor system and actors
ActorSystem system = new ActorSystem(&quot;mySystem&quot;, 4); // 4 threads for CPU-intensive tasks
ActorRef&amp;lt;Counter&amp;gt; counter = system.actorOf(&quot;counter&quot;, new Counter());

// Send messages to actors
counter.tell(c -&amp;gt; c.increment());  // Fire-and-forget
CompletableFuture&amp;lt;Integer&amp;gt; result = counter.ask(c -&amp;gt; c.getValue());  // Request-response

// Get the result
int value = result.get(); // Returns 1

// Cleanup
system.terminate();
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;any-pojo-can-become-an-actor&quot;&gt;Any POJO Can Become an Actor&lt;/h3&gt;
&lt;p&gt;One of POJO-actor’s biggest advantages is that you don’t need to design your code specifically for the actor model from the beginning. Any existing Java object can instantly become an actor, including standard library classes:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import java.util.ArrayList;
import java.util.concurrent.CompletableFuture;

// Turn a standard ArrayList into an actor - no modifications needed!
ActorSystem system = new ActorSystem(&quot;listSystem&quot;);
ActorRef&amp;lt;ArrayList&amp;lt;String&amp;gt;&amp;gt; listActor = system.actorOf(&quot;myList&quot;, new ArrayList&amp;lt;String&amp;gt;());

// Send messages to the ArrayList actor
listActor.tell(list -&amp;gt; list.add(&quot;Hello&quot;));
listActor.tell(list -&amp;gt; list.add(&quot;World&quot;));
listActor.tell(list -&amp;gt; list.add(&quot;from&quot;));
listActor.tell(list -&amp;gt; list.add(&quot;POJO-actor&quot;));

// Query the list size
CompletableFuture&amp;lt;Integer&amp;gt; sizeResult = listActor.ask(list -&amp;gt; list.size());
System.out.println(&quot;List size: &quot; + sizeResult.get()); // Prints: List size: 4

// Get specific elements
CompletableFuture&amp;lt;String&amp;gt; firstElement = listActor.ask(list -&amp;gt; list.get(0));
System.out.println(&quot;First element: &quot; + firstElement.get()); // Prints: First element: Hello

// Even complex operations work
CompletableFuture&amp;lt;String&amp;gt; joinedResult = listActor.ask(list -&amp;gt;
    String.join(&quot; &quot;, list));
System.out.println(joinedResult.get()); // Prints: Hello World from POJO-actor

system.terminate();
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This means you can:&lt;/p&gt;
&lt;p&gt;Retrofit existing codebases without architectural changes&lt;br&gt;
Protect any object with actor-based thread safety&lt;br&gt;
Scale incrementally by converting objects to actors as needed&lt;br&gt;
Reuse existing POJOs without any modifications&lt;/p&gt;
&lt;h3 id=&quot;massive-actor-scalability&quot;&gt;Massive Actor Scalability&lt;/h3&gt;
&lt;p&gt;Thanks to virtual threads, POJO-actor can handle thousands of actors efficiently. Here’s an example creating 10,000 counter actors:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;ActorSystem system = new ActorSystem(&quot;massiveSystem&quot;, 4); // Only 4 CPU threads for computation
List&amp;lt;ActorRef&amp;lt;Counter&amp;gt;&amp;gt; actors = new ArrayList&amp;lt;&amp;gt;();

// Create 10,000 actors - no problem with virtual threads!
for (int i = 0; i &amp;lt; 10000; i++) {
    ActorRef&amp;lt;Counter&amp;gt; actor = system.actorOf(&quot;counter&quot; + i, new Counter());
    actors.add(actor);
}

// Send messages to all actors concurrently
List&amp;lt;CompletableFuture&amp;lt;Void&amp;gt;&amp;gt; futures = new ArrayList&amp;lt;&amp;gt;();
for (ActorRef&amp;lt;Counter&amp;gt; actor : actors) {
    futures.add(actor.tell(c -&amp;gt; c.increment()));
}

// Wait for all messages to be processed
CompletableFuture.allOf(futures.toArray(new CompletableFuture[0])).get();

// Verify results from all 10,000 actors
for (ActorRef&amp;lt;Counter&amp;gt; actor : actors) {
    int value = actor.ask(c -&amp;gt; c.getValue()).get();
    assert value == 1; // Each counter was incremented once
}

System.out.println(&quot;Successfully processed messages for 10,000 actors!&quot;);
system.terminate();
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This demonstrates POJO-actor’s ability to:&lt;/p&gt;
&lt;p&gt;Create thousands of actors without thread exhaustion&lt;br&gt;
Control CPU usage (only 4 threads for heavy computation)&lt;br&gt;
Scale beyond traditional thread-per-actor limitations&lt;/p&gt;
&lt;h3 id=&quot;advanced-usage&quot;&gt;Advanced Usage&lt;/h3&gt;
&lt;h4 id=&quot;parallel-matrix-multiplication&quot;&gt;Parallel Matrix Multiplication&lt;/h4&gt;
&lt;p&gt;POJO-actor excels at parallel computation tasks. Here’s an example of distributed matrix multiplication that demonstrates the proper use of work-stealing pools for heavy computations:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// Create large matrices for multiplication
final int matrixSize = 400;
final int blockSize = 100;
double[][] matrixA = new double[matrixSize][matrixSize];
double[][] matrixB = new double[matrixSize][matrixSize];

// Create ActorSystem with 4 CPU threads for heavy computation
ActorSystem system = new ActorSystem(&quot;matrixSystem&quot;, 4);
List&amp;lt;CompletableFuture&amp;lt;Double&amp;gt;&amp;gt; futures = new ArrayList&amp;lt;&amp;gt;();

// Divide matrix into blocks and assign to different actors
for (int blockRow = 0; blockRow &amp;lt; 4; blockRow++) {
    for (int blockCol = 0; blockCol &amp;lt; 4; blockCol++) {
        MatrixCalculator calculator = new MatrixCalculator();
        ActorRef&amp;lt;MatrixCalculator&amp;gt; actor = system.actorOf(
            String.format(&quot;block_%d_%d&quot;, blockRow, blockCol), calculator);

        // Light operation: Initialize actor with block coordinates (uses virtual thread)
        actor.tell(calc -&amp;gt; calc.initBlock(matrixA, matrixB, blockRow, blockCol)).get();

        // Heavy computation: Matrix multiplication (uses work-stealing pool)
        CompletableFuture&amp;lt;Double&amp;gt; blockSum = actor.ask(
            calc -&amp;gt; calc.calculateBlock(), // CPU-intensive matrix multiplication
            system.getWorkStealingPool()   // Delegate to work-stealing pool
        );

        futures.add(blockSum);
    }
}

// Wait for all parallel calculations to complete
CompletableFuture.allOf(futures.toArray(new CompletableFuture[0])).get();
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Key Performance Points:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Light operations: initBlock() just sets references, so virtual threads are perfect&lt;/li&gt;
&lt;li&gt;Heavy computation: calculateBlock() does actual matrix multiplication, so work-stealing pool is essential&lt;/li&gt;
&lt;li&gt;Virtual threads: Handle only setter/getter level operations, almost no computation&lt;/li&gt;
&lt;li&gt;CPU control: Only 4 CPU threads handle all heavy computation, regardless of actor count&lt;/li&gt;
&lt;li&gt;Responsiveness: Actors remain responsive to light messages during heavy computation&lt;/li&gt;
&lt;li&gt;Scalability: Can create thousands of actors without exhausting system resources&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&quot;custom-thread-pools&quot;&gt;Custom Thread Pools&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;ActorSystem system = new ActorSystem(&quot;system&quot;);

// Add additional work-stealing pools
system.addWorkStealingPool(8);  // CPU-intensive tasks
system.addWorkStealingPool(2);  // I/O-bound tasks

// Use specific thread pools
counter.tell(c -&amp;gt; c.increment(), system.getWorkStealingPool(1));
&lt;/code&gt;&lt;/pre&gt;
&lt;h4 id=&quot;actor-hierarchies&quot;&gt;Actor Hierarchies&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;ActorRef&amp;lt;ParentActor&amp;gt; parent = system.actorOf(&quot;parent&quot;, new ParentActor());
ActorRef&amp;lt;ChildActor&amp;gt; child = parent.createChild(&quot;child&quot;, new ChildActor());
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;graalvm-native-image-support&quot;&gt;GraalVM Native Image Support&lt;/h3&gt;
&lt;p&gt;Traditional actor model frameworks rely heavily on reflection for message routing, serialization, and dynamic proxy generation, making them incompatible with GraalVM Native Image compilation. These frameworks require extensive configuration files and reflection hints to work with native compilation, if at all.&lt;/p&gt;
&lt;p&gt;POJO-actor is built using only modern JDK features, making the library itself very simple and easy to understand. It uses no reflection whatsoever. As a result, code written with POJO-actor compiles seamlessly to GraalVM Native Images without any obstacles:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# Compile to native image
native-image -jar target/POJO-actor-1.0.0-fat.jar -o pojo-actor-native

# Run native executable
./pojo-actor-native# Run native executable
./pojo-actor-native
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;No additional configuration files or reflection hints are required.&lt;/p&gt;
&lt;h4 id=&quot;performance&quot;&gt;Performance&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;Startup Time: Near-instant with native compilation&lt;/li&gt;
&lt;li&gt;Memory Usage: Minimal heap allocation due to POJO-based design&lt;/li&gt;
&lt;li&gt;Throughput: High message processing rates with virtual threads&lt;/li&gt;
&lt;li&gt;Scalability: Efficient work-stealing thread pools for parallel tasks&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;performance-best-practices&quot;&gt;Performance Best Practices&lt;/h3&gt;
&lt;p&gt;For optimal performance, it’s crucial to understand when to use virtual threads vs. work-stealing pools:&lt;/p&gt;
&lt;h4 id=&quot;light-operations-use-default-virtual-threads&quot;&gt;Light Operations — Use Default Virtual Threads&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;// Fast operations that don't block or consume much CPU
counter.tell(c -&amp;gt; c.increment());
counter.tell(c -&amp;gt; c.setName(&quot;newName&quot;));
listActor.tell(list -&amp;gt; list.add(&quot;item&quot;));

CompletableFuture&amp;lt;Integer&amp;gt; size = listActor.ask(list -&amp;gt; list.size());
&lt;/code&gt;&lt;/pre&gt;
&lt;h4 id=&quot;heavy-computations-delegate-to-work-stealing-pools&quot;&gt;Heavy Computations — Delegate to Work-Stealing Pools&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;ActorSystem system = new ActorSystem(&quot;system&quot;, 4); // 4 CPU threads for heavy work

// CPU-intensive calculations should use work-stealing pool
CompletableFuture&amp;lt;Double&amp;gt; result = calculator.ask(c -&amp;gt; c.performMatrixMultiplication(),
                                                 system.getWorkStealingPool());

// I/O operations or blocking calls should also use work-stealing pool
CompletableFuture&amp;lt;String&amp;gt; data = dataProcessor.ask(p -&amp;gt; p.readLargeFile(),
                                                   system.getWorkStealingPool());
&lt;/code&gt;&lt;/pre&gt;
&lt;h4 id=&quot;why-this-matters&quot;&gt;Why This Matters&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;Virtual threads are perfect for lightweight message passing and state changes&lt;/li&gt;
&lt;li&gt;Work-stealing pools handle CPU-intensive tasks without blocking virtual threads&lt;/li&gt;
&lt;li&gt;This separation prevents heavy computations from affecting the actor system’s responsiveness&lt;/li&gt;
&lt;li&gt;You can control CPU core usage by configuring the work-stealing pool size&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;// Example: Mixed workload with proper thread pool usage
ActorSystem system = new ActorSystem(&quot;mixedSystem&quot;, 4);
ActorRef&amp;lt;DataProcessor&amp;gt; processor = system.actorOf(&quot;processor&quot;, new DataProcessor());

// Light operation - uses virtual thread
processor.tell(p -&amp;gt; p.updateCounter());

// Heavy operation - uses work-stealing pool
CompletableFuture&amp;lt;ProcessResult&amp;gt; heavyResult = processor.ask(
    p -&amp;gt; p.performComplexAnalysis(largeDataset),
    system.getWorkStealingPool()
);

// The actor remains responsive to light messages while heavy computation runs in background
processor.tell(p -&amp;gt; p.logStatus()); // This won't be blocked by heavy computation
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;requirements&quot;&gt;Requirements&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Java 21 or higher&lt;/li&gt;
&lt;li&gt;Maven 3.6+&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;dependencies&quot;&gt;Dependencies&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Runtime: JDK standard library only&lt;/li&gt;
&lt;li&gt;Testing: JUnit 5, Apache Commons Math (test scope only)&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;building&quot;&gt;Building&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;# Compile and test
mvn clean test

# Build JAR
mvn clean package

# Generate Javadoc
mvn javadoc:javadoc# Build JAR
mvn clean package
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;acknowledgments&quot;&gt;Acknowledgments&lt;/h3&gt;
&lt;p&gt;POJO-actor was inspired by Alexander Zakusylo’s &lt;a rel=&quot;nofollow&quot; href=&quot;https://medium.com/@zakgof/type-safe-actor-model-for-java-7133857a9f72&quot;&gt;actr&lt;/a&gt; library, which pioneered the POJO-based actor model approach in Java. While actr introduced many excellent concepts, POJO-actor extends and improves upon them with:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Message ordering guarantee: Unlike actr, POJO-actor ensures that messages sent to an actor are processed in the order they were sent&lt;/li&gt;
&lt;li&gt;Modern Java features: Built with Java 21+ virtual threads and modern concurrency patterns&lt;/li&gt;
&lt;li&gt;Enhanced thread pool management: actr used real threads for actors, limiting scalability to CPU core count and causing performance issues with heavy computations. POJO-actor uses virtual threads for actors and delegates heavy computations to configurable work-stealing pools, allowing thousands of actors while controlling CPU core usage&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;We acknowledge the foundational work done by the actr library team in making actor model programming more accessible to Java developers.&lt;/p&gt;
&lt;p&gt;We also acknowledge &lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/untu/comedy&quot;&gt;Comedy.js&lt;/a&gt;, a Node.js actor framework, which inspired POJO-actor's basic architecture design, particularly the ActorSystem and ActorRef concepts. While Comedy.js uses one process or one real thread per actor, POJO-actor leverages Java's virtual threads to enable thousands of lightweight actors.&lt;/p&gt;
</description>
<category>Getting Started</category>
<guid isPermaLink="true">https://coderlegion.com/8748/pojo-actor-v1-0-a-lightweight-actor-model-library-for-java</guid>
<pubDate>Mon, 22 Dec 2025 21:19:41 +0000</pubDate>
</item>
</channel>
</rss>