Skip to content

Java SDK: Add edge labels to native Dags - #74523

Merged
jason810496 merged 3 commits into
apache:mainfrom
FrankYang0529:java-sdk-edge-label
Oct 20, 2026
Merged

jason810496 merged 3 commits into
apache:mainfrom
FrankYang0529:java-sdk-edge-label

Conversation

@FrankYang0529

Copy link
Copy Markdown
Member
  • Deps.Flow.label(flow, text) wraps one end of an edge. Each edge of a fan-out can then carry its own label. Declaring an existing edge again only adds the label to that edge.
  • A later label on the same edge replaces the earlier label.

Was generative AI tooling used to co-author this PR?
  • Yes - Claude Code

@jason810496 jason810496 left a comment •

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice, thanks.

I pushed 255a2b1 on top with a few fixes from review:

  • Labels are now applied in the order they were set, so the label set last wins, as in Python's DAG.set_edge_info. Before, a task edge's own label always beat its group edge's label, and relabeling an existing
    group edge didn't take effect.
  • Updated EdgeLabelTest to match. I checked both cases against Python's Label, and the edge_info matches.
  • Corrected the KDoc and docs note: Python's Label also changes the edge when one end is in no task group.

@uranusjr

Copy link
Copy Markdown
Member

Python uses the >> Label >> syntax because this naturally reads like having a label in the arrow. This does not read as well in other languages without >>, and makes even less sense with before.

I think we should come up with a better syntax instead just copying Python with >> and << substituted.

@jason810496 jason810496 left a comment •

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we should come up with a better syntax instead just copying Python with >> and << substituted.

I considered the following syntax before:

loaded.before(reportEmpty, "when empty");
cleaned.after(loaded, "always");

However, the wiring should supports fan-in and fan-out.

loaded.before(reportEmpty, "when empty", reportSuspicious, "when suspicious"); // this is not a working syntax to support fan-in.
// the only sutiable way might looks like:
loaded.before([reportEmpty, reportSuspicious], ["when empty", "when suspicious"]); 
loaded.before([reportEmpty,"when empty"], [reportSuspicious,, "when suspicious"]); 

This is why the current syntax goes with current shape, having a dedicated Flow.label wrapper to representing the edge with label:

loaded.before(Flow.label(reportEmpty, "when empty"), Flow.label(reportSuspicious, "when suspicious"));
cleaned.after(Flow.label(loaded, "always"));

The Go-SDK went with the same shape already and there was't objection back then.
I don't have a better user interface design at this moment.

@FrankYang0529

Copy link
Copy Markdown
Member Author

Thanks for the comments and suggestions.

I agree with Jason that Java checks every varargs argument against the same type. A before(Object...) signature would accept the call above, but it would also accept nodes and labels in any order. Only a runtime check could then catch a misplaced label.

With each label still paired with its node, two changes may make the Java code read better. WDYT?

  1. Add a labeled method to the node so that each call reads more like an arrow with a label on it:

    loaded.before(reportEmpty.labeled("when empty"), reportSuspicious.labeled("when suspicious"));
    cleaned.after(loaded.labeled("always"));

A bad part is that labeled becomes a reserved name for task methods inside a task group, because the generated view of a group inherits every method of the node type.

  1. Let Then, Else and Case take a label directly, because labels are used most often on branches:

    dag.If(HasRows.class).Then(load, "rows found").Else(reportEmpty, "no rows");
    dag.Switch(PickPath.class).Case(handleLong, "long").Case(handleShort, "short");

@jason810496 jason810496 left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi @uranusjr,
Please let me know if there's any further concern from merging this PR when you have a moment, thanks.

@uranusjr

Copy link
Copy Markdown
Member

I feel it’d make more sense to put the label before the task. Maybe something like

loaded.before(Label.of("when empty").on(reportEmpty), Label.of("when suspicious").on(reportSuspicious))

I’m not sure if Python currently has both a >> label >> [b, c] and a >> [label_for_b >> b, label_for_c >> c] (label before or after the flow forks). If this is a thing, it also makes sense to also have

loaded.labeled("forks").before(reportEmpty, reportSuspicious)

It’s more difficult to come up with a syntax for after (since that one is backwards by design), but the general idea is to keep the label text between the tasks to reflect its position in the rendered graph.

@FrankYang0529

Copy link
Copy Markdown
Member Author

Thanks for the suggestion. The label term between tasks is more clear. I switched to following syntax:

loaded.before(Label.of("when empty").on(reportEmpty), Label.of("when suspicious").on(reportSuspicious));

About Python syntax:

  • a >> Label("forks") >> [b, c] labels both edges.
  • a >> [Label("x") >> b, Label("y") >> c] draws both edges but labels neither, and raises no error. When Label("x") >> b runs, the label has no upstream yet, and the expression returns b. So the outer expression is just a >> [b, c].

on can take several tasks, so the first form maps to:

loaded.before(Label.of("forks").on(reportEmpty, reportSuspicious)); // a >> Label("forks") >> [b, c]

To keep the text between the tasks, I did not add loaded.labeled("forks"). after also works with the same syntax:

cleaned.after(Label.of("always").on(loaded)); // load >> Label("always") >> cleanup

The Go SDK (airflow.Label(node, text)) and the TS SDK (label(node, text)) both put the task first. Should those two SDKs follow the Java syntax?

FrankYang0529 and others added 3 commits October 19, 2026 18:22
Signed-off-by: PoAn Yang <payang@apache.org>
A task edge's own label always won over a group edge's label, and a
relabeled group edge kept its first position, so edge_info differed from
Python's, where the label set last wins. Also note that Python's Label
rewrites an edge when one end is in no task group.

Co-Authored-By: Claude <noreply@anthropic.com>
Signed-off-by: PoAn Yang <payang@apache.org>
@jason810496

Copy link
Copy Markdown
Member

The Go SDK (airflow.Label(node, text)) and the TS SDK (label(node, text)) both put the task first. Should those two SDKs follow the Java syntax?

The TS SDK side edge label just merged yesterday btw.
Yes, let's align the Java side syntax please, thanks a lot.

@jason810496 jason810496 added the backport-to-v3-4-test Backport to v3-4-test label Oct 20, 2026
@jason810496 jason810496 added this to the Airflow 3.4.0 milestone Oct 20, 2026
@jason810496
jason810496 merged commit 5a64dfc into apache:main Oct 20, 2026
81 checks passed
@github-actions

Copy link
Copy Markdown
Contributor

Backport successfully created: v3-4-test

Note: As of Merging PRs targeted for Airflow 3.X
the committer who merges the PR is responsible for backporting the PRs that are bug fixes (generally speaking) to the maintenance branches.

In matter of doubt please ask in #release-management Slack channel.

Status Branch Result
✅ v3-4-test PR Link

aws-airflow-bot pushed a commit to aws-mwaa/upstream-to-airflow that referenced this pull request Oct 20, 2026
* Java SDK: Add edge labels to native Dags

Signed-off-by: PoAn Yang <payang@apache.org>

* Apply edge labels in the order they were set

A task edge's own label always won over a group edge's label, and a
relabeled group edge kept its first position, so edge_info differed from
Python's, where the label set last wins. Also note that Python's Label
rewrites an edge when one end is in no task group.

* Java SDK: Write the edge label text before the task

Signed-off-by: PoAn Yang <payang@apache.org>

---------
(cherry picked from commit 5a64dfc)

Co-authored-by: PoAn Yang <payang@apache.org>
Signed-off-by: PoAn Yang <payang@apache.org>
Co-authored-by: ZHE YOU LIU <zhu424.dev@gmail.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants