deploying-java-sdk-bundles

作者: astronomer

構建並部署已編譯的 Airflow Java SDK 套件,以便 worker 可以執行它們。當使用者想要將 JVM 任務套件打包成 JAR,或詢問關於…時使用。

npx skills add https://github.com/astronomer/agents --skill deploying-java-sdk-bundles

Deploying Java SDK Bundles

A Java SDK deployment has one artifact: a bundle — your compiled task classes plus the SDK, packaged as a JAR (or a thin JAR alongside its dependency JARs). You build it with Gradle or Maven, then place it in a directory that the JavaCoordinator scans (jars_root) on every worker. This skill is platform-neutral; it shows the build once, then both an open-source and an Astro deployment path.

Experimental. The Java SDK is in preview. Artifact versions below are shown as ${version}; while the SDK is pre-release you may need to build the artifacts into your local Maven repository yourself (see the preview builds section).

Order of operations: build the bundle (this skill) → place it where jars_root points → configure the coordinator (configuring-airflow-language-sdks). The task code itself is authoring-java-sdk-tasks.


Build with Gradle (recommended)

Apply the SDK's Gradle plugin and declare dependencies in build.gradle:

plugins {
    id("org.apache.airflow.sdk") version "${version}"
}

repositories {
    mavenCentral()
}

dependencies {
    annotationProcessor("org.apache.airflow:airflow-sdk-processor:${version}")  // annotation API only
    implementation("org.apache.airflow:airflow-sdk:${version}")
    // Optional logging integration, e.g.:
    // implementation("org.apache.airflow:airflow-sdk-jpl:${version}")
}

airflowBundle {
    mainClass = "com.example.Main"   // your BundleBuilder entry point
    // fatJar = false                // opt out of the single-JAR build (see below)
}

Build it:

./gradlew bundle

The build/bundle/ directory then holds all required JAR(s). Notes:

  • The annotationProcessor line is needed only if you use the annotation-based API. The interface-based API doesn't need it.
  • By default the plugin produces a fat JAR (via the Shadow plugin) — one self-contained file, which avoids cross-project dependency clashes. Set fatJar = false in airflowBundle for thin JARs; you then deploy every dependency JAR too.
  • The Gradle plugin validates that mainClass exists at build time (verifyBundleMainClass).

Build with Maven

Import the BOM so artifact versions and the supervisor schema version are managed in one place:

<dependencyManagement>
  <dependencies>
    <dependency>
      <groupId>org.apache.airflow</groupId>
      <artifactId>airflow-sdk-bom</artifactId>
      <version>${version}</version>
      <type>pom</type>
      <scope>import</scope>
    </dependency>
  </dependencies>
</dependencyManagement>

<dependencies>
  <dependency>
    <groupId>org.apache.airflow</groupId>
    <artifactId>airflow-sdk</artifactId>   <!-- version from the BOM -->
  </dependency>
</dependencies>

Wire the annotation processor through maven-compiler-plugin (annotation API only) so it stays off the runtime classpath. Then pick a packaging option:

  • Fat JAR (recommended): use maven-shade-plugin. In its ManifestResourceTransformer, set <mainClass> to your BundleBuilder and add the manifest entry Airflow-Supervisor-Schema-Version resolved from the BOM property ${airflow.supervisor.schema.version} (don't hard-code it). mvn package writes the JAR to target/.
  • Thin JAR: use maven-jar-plugin to set Main-Class and maven-dependency-plugin (copy-dependencies) to collect runtime JARs into target/bundle/. Here Airflow-Supervisor-Schema-Version is not needed — Airflow reads it from the airflow-sdk JAR on the classpath.

Unlike Gradle, Maven does not validate mainClass at build time; a wrong value only fails at runtime.


Logging integration

For task log records to reach Airflow's log store (and the task log view in the UI), the bundle must include exactly one SDK logging artifact per logging facade you use. Versions are managed by airflow-sdk-bom; Maven users apply the same artifact IDs.

Choosing a facade. For a greenfield project, prefer JPL (System.Logger) — it is built into the JDK, so your tasks need no extra logging API. Pick another facade only when the libraries you integrate with already log through it, so their records reach Airflow too. Preference order: JPL > SLF4J = Log4j 2 > JUL; treat JUL as legacy integration only, not a choice for new code.

FacadeArtifactSetup beyond the dependency
System.Logger (JPL)airflow-sdk-jplNone — the provider is discovered via ServiceLoader.
SLF4J 2.xairflow-sdk-slf4jNone — the binding is discovered automatically (pulls in slf4j-api for you).
Log4j 2airflow-sdk-log4j2log4j-core on the runtime classpath + AirflowAppender declared in log4j2.xml (below).
java.util.logging (JUL)airflow-sdk-julCall AirflowJulHandler.setup() in main() (below), or use a logging.properties file (see configuring-airflow-language-sdks).

Log4j 2 — log4j-core hosts the plugin loader that discovers the appender (log4j-api comes in transitively):

implementation("org.apache.airflow:airflow-sdk-log4j2:${version}")
runtimeOnly("org.apache.logging.log4j:log4j-core:${log4jVersion}")
<Configuration>
  <Appenders>
    <AirflowAppender name="Airflow"/>
  </Appenders>
  <Loggers>
    <Root level="info">
      <AppenderRef ref="Airflow"/>
    </Root>
  </Loggers>
</Configuration>

JUL — call AirflowJulHandler.setup() before any task runs. It clears the root logger's existing handlers (the default ConsoleHandler writes to stderr, which Airflow would otherwise capture as task.stderr at ERROR level, duplicating each record):

public static void main(String[] args) {
    AirflowJulHandler.setup();
    Server.create(args).serve(new MyBundle().build());
}

Don't double up providers. A second System.LoggerFinder implementation alongside airflow-sdk-jpl, or a second SLF4J binding (logback-classic, slf4j-simple) alongside airflow-sdk-slf4j, makes provider selection unpredictable.


Preview builds (before a stable release)

Skip this section if you depend on a stable release. Once you pin a released version (e.g. 1.0.0) published to Maven Central, the mavenCentral() repository in the build snippets above is enough.

While the SDK is pre-release, the documented path is to build the artifacts and the Gradle plugin from the Airflow repo into your local Maven repository:

# in apache/airflow's java-sdk/ directory
./gradlew publishToMavenLocal -PskipSigning=true

Then add mavenLocal() in your project, in both pluginManagement (in settings.gradle) and project repositories (in build.gradle) — this is how the SDK's own example project resolves it.

Once -SNAPSHOT artifacts are published to Apache's snapshot Nexus, that repository can stand in for the local build (same two places):

maven {
    name = "apacheSnapshots"
    url = "https://repository.apache.org/content/repositories/snapshots/"
    mavenContent { snapshotsOnly() }
}

Snapshots move; force a refresh with ./gradlew bundle --refresh-dependencies. (For Maven, add the same repository to <repositories> and <pluginRepositories>.)


Place the bundle where the coordinator scans

The coordinator scans jars_root recursively and builds the classpath automatically, so you copy the whole output directory:

cp build/bundle/* /opt/airflow/jars/    # /opt/airflow/jars == jars_root

The worker also needs a JRE 17+. Wiring the coordinator to this directory is covered in configuring-airflow-language-sdks.


Deployment paths

Astronomer tooling is not required — the SDK runs on any Airflow with the Task SDK. Choose the path that matches the user's setup.

Open-source (Docker / Kubernetes)

Bake the JRE and the bundle into your Airflow image, or mount them:

FROM apache/airflow:3          # pin a specific 3.x in production
USER root
RUN apt-get update \
    && apt-get install -y --no-install-recommends default-jre-headless \
    && apt-get clean && rm -rf /var/lib/apt/lists/*
RUN mkdir -p /opt/airflow/jars
COPY build/bundle/ /opt/airflow/jars/
USER airflow

On Kubernetes (Helm chart), bake the JAR into a custom image as above, or mount it via a shared volume; set the [sdk] config through environment variables on the worker/scheduler. See deploying-airflow for the broader Docker Compose and Helm workflow.

Astro (one option, not required)

If the user is on Astronomer's Astro CLI, the same idea maps onto an Astro project:

  1. Build the bundle, then stage it in the project: mkdir -p include/jars && cp ../java-bundle/build/bundle/*.jar include/jars/.

  2. Edit the project Dockerfile to install a JRE and copy the JARs to the coordinator's directory:

    FROM quay.io/astronomer/astro-runtime:<version>
    USER root
    RUN apt-get update \
        && apt-get install -y --no-install-recommends default-jre-headless \
        && apt-get clean && rm -rf /var/lib/apt/lists/*
    RUN mkdir -p /opt/airflow/jars
    COPY include/jars/ /opt/airflow/jars/
    USER airflow
    
  3. Put the coordinator config in the project's .env (loaded automatically) — see configuring-airflow-language-sdks for the AIRFLOW__SDK__* values.

  4. astro dev start (or astro dev restart after changes) builds the image and starts Airflow locally; deploy with astro deploy as usual.

Don't pin Astro Runtime / Airflow versions from memory — read the generated Dockerfile or check current docs. While the SDK and Airflow 3.3 are in preview, a beta/dev Astro Runtime image may be required.


Deploy checklist

  • Bundle built (./gradlew bundle or mvn package) and mainClass points at your BundleBuilder.
  • annotationProcessor present iff you use the annotation API.
  • JAR(s) copied into the worker's jars_root directory; with thin JARs, dependency JARs too.
  • JRE 17+ available on the worker.
  • Coordinator + queue_to_coordinator configured (configuring-airflow-language-sdks).
  • If multiple executable JARs exist under jars_root, set main_class explicitly.

Related Skills

  • authoring-java-sdk-tasks: Write the Java task code and the matching Python stubs.
  • configuring-airflow-language-sdks: Register the coordinator and route the queue.
  • deploying-airflow: General Airflow deployment (Astro, Docker Compose, Kubernetes).
  • setting-up-astro-project: Initialize and configure an Astro project.

來自 astronomer 的更多技能

airflow-state-store
astronomer
Persists task and asset state across retries and DAG runs using Airflow 3.3's AIP-103 key/value stores (`task_state_store`, `asset_state_store`) and the…
creating-openlineage-extractors
astronomer
為不支援的Airflow運算子及複雜血緣場景設計的自訂OpenLineage提取器。提供兩種方法:直接在你擁有的運算子中加入OpenLineage方法(建議做法),或為無法修改的第三方運算子建立自訂提取器。提取器在三個時間點攔截運算子執行:執行前取得靜態血緣、成功後取得執行階段決定的輸出、以及選擇性地在失敗後取得部分血緣。可透過airflow.cfg或環境變數註冊提取器...
debugging-dags
astronomer
針對失敗的 Airflow DAG 進行系統性根本原因分析與修復,並提供結構化的調查流程。引導完成四個階段的診斷步驟:識別失敗、提取錯誤細節、收集背景資訊,以及提供可行的修復步驟。將失敗分為四種類型(資料、程式碼、基礎設施、相依性),以聚焦調查並建議適當的修正方式。提供可直接使用的 CLI 指令,用於日誌擷取、執行比較、任務清除與 DAG...
delegating-to-otto
astronomer
驅動 Astronomer 的 Otto 代理
deploying-airflow
astronomer
部署 Airflow DAG 和專案。當使用者想要部署程式碼、推送 DAG、設定 CI/CD、部署到生產環境,或詢問部署策略時使用…
deploying-go-sdk-bundles
astronomer
建置、打包並部署已編譯的 Airflow Go SDK 套件,以便 ExecutableCoordinator 能執行它們。當使用者想要編譯 Go 任務套件、要求…時使用。
testing-dags
astronomer
針對Airflow DAG的反覆測試-除錯-修復循環,提供全面的失敗診斷。從af runs trigger-wait <dag_id>開始執行DAG並等待完成,無需預先檢查。失敗時,使用af runs diagnose獲取完整的失敗摘要,並透過af tasks logs檢查特定任務的錯誤細節。支援自訂配置、超時設定與重試機制;能處理成功、失敗及超時情境,並提供清晰的回應解讀。快速驗證功能亦已就緒...
tracing-downstream-lineage
astronomer
追蹤下游資料血緣,在修改資料表或DAG前評估變更影響。透過原始碼搜尋、檢視相依性及BI工具連線,識別目標資料表或DAG的直接消費者。建立完整的相依性樹狀圖,繪製從資料表到儀表板再到機器學習模型的所有下游影響。依關鍵性(關鍵、高、中、低)分類相依性,以優先處理利害關係人溝通與測試。產出包含風險評估、受影響範圍的影響報告。