<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Oleksandr Kazimirov</title>
    <description>The latest articles on DEV Community by Oleksandr Kazimirov (@okazimirov).</description>
    <link>https://dev.to/okazimirov</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3910861%2Fff388602-2bd0-400b-b47b-9d9adb3734f6.png</url>
      <title>DEV Community: Oleksandr Kazimirov</title>
      <link>https://dev.to/okazimirov</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/okazimirov"/>
    <language>en</language>
    <item>
      <title>We forked Apache Stateful Functions for Flink 2.x — here's why</title>
      <dc:creator>Oleksandr Kazimirov</dc:creator>
      <pubDate>Sun, 03 May 2026 20:40:35 +0000</pubDate>
      <link>https://dev.to/okazimirov/we-forked-apache-stateful-functions-for-flink-2x-heres-why-18oj</link>
      <guid>https://dev.to/okazimirov/we-forked-apache-stateful-functions-for-flink-2x-heres-why-18oj</guid>
      <description>&lt;p&gt;&lt;a href="https://github.com/apache/flink-statefun" rel="noopener noreferrer"&gt;Apache Stateful Functions&lt;/a&gt; is one of the quietly powerful frameworks in the Flink ecosystem - durable per-key state, exactly-once messaging, polyglot remote functions, all on top of Apache Flink. It's also been functionally dormant since &lt;strong&gt;October 2024&lt;/strong&gt;, and it doesn't run on Flink 2.x.&lt;/p&gt;

&lt;p&gt;We needed it on Flink 2.x. So we maintained the continuation: &lt;a href="https://github.com/kzmlabs/flink-statefun" rel="noopener noreferrer"&gt;&lt;code&gt;kzmlabs/flink-statefun&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;This post is the &lt;em&gt;why&lt;/em&gt; and the &lt;em&gt;how it's different&lt;/em&gt;. If you're already running upstream and wondering whether to migrate, or you're picking a stateful-actor framework today and trying to understand the landscape, read on.&lt;/p&gt;

&lt;h2&gt;
  
  
  What StateFun gives you
&lt;/h2&gt;

&lt;p&gt;If you've never touched Stateful Functions, here's the elevator version. Compare a hand-written keyed Flink job:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;CounterFunction&lt;/span&gt;
    &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;KeyedProcessFunction&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Event&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Result&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;

  &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="kt"&gt;long&lt;/span&gt; &lt;span class="n"&gt;serialVersionUID&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1L&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

  &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="nc"&gt;ValueStateDescriptor&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Long&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="no"&gt;COUNT_DESCRIPTOR&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
      &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;ValueStateDescriptor&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&amp;gt;(&lt;/span&gt;&lt;span class="s"&gt;"count"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Types&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;LONG&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;

  &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="kd"&gt;transient&lt;/span&gt; &lt;span class="nc"&gt;ValueState&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Long&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;count&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

  &lt;span class="nd"&gt;@Override&lt;/span&gt;
  &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;open&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;OpenContext&lt;/span&gt; &lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;count&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;getRuntimeContext&lt;/span&gt;&lt;span class="o"&gt;().&lt;/span&gt;&lt;span class="na"&gt;getState&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="no"&gt;COUNT_DESCRIPTOR&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
  &lt;span class="o"&gt;}&lt;/span&gt;

  &lt;span class="nd"&gt;@Override&lt;/span&gt;
  &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;processElement&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Event&lt;/span&gt; &lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Context&lt;/span&gt; &lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Collector&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Result&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;out&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
      &lt;span class="kd"&gt;throws&lt;/span&gt; &lt;span class="nc"&gt;Exception&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="kt"&gt;long&lt;/span&gt; &lt;span class="n"&gt;next&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Objects&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;requireNonNullElse&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;count&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="o"&gt;(),&lt;/span&gt; &lt;span class="mi"&gt;0L&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1L&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
    &lt;span class="n"&gt;count&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;update&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;next&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
    &lt;span class="n"&gt;out&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;collect&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Result&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="o"&gt;(),&lt;/span&gt; &lt;span class="n"&gt;next&lt;/span&gt;&lt;span class="o"&gt;));&lt;/span&gt;
  &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="c1"&gt;// + Kafka source, sink, watermarks, keyBy,&lt;/span&gt;
&lt;span class="c1"&gt;//   checkpoint config, restart strategy, etc.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;…with the same logic as a StateFun function:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Counter&lt;/span&gt; &lt;span class="kd"&gt;implements&lt;/span&gt; &lt;span class="nc"&gt;StatefulFunction&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;

  &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="nc"&gt;EgressIdentifier&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Result&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="no"&gt;RESULTS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;EgressIdentifier&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&amp;gt;(&lt;/span&gt;&lt;span class="s"&gt;"io.kzm.counter"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"results"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Result&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;class&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;

  &lt;span class="nd"&gt;@Persisted&lt;/span&gt;
  &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="nc"&gt;PersistedValue&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Long&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;count&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;PersistedValue&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;of&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"count"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Long&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;class&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;

  &lt;span class="nd"&gt;@Override&lt;/span&gt;
  &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;invoke&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Context&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Object&lt;/span&gt; &lt;span class="n"&gt;input&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;input&lt;/span&gt; &lt;span class="k"&gt;instanceof&lt;/span&gt; &lt;span class="nc"&gt;Event&lt;/span&gt; &lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
      &lt;span class="kt"&gt;long&lt;/span&gt; &lt;span class="n"&gt;next&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Optional&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;ofNullable&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;count&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;get&lt;/span&gt;&lt;span class="o"&gt;()).&lt;/span&gt;&lt;span class="na"&gt;orElse&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0L&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1L&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
      &lt;span class="n"&gt;count&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;set&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;next&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
      &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;send&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="no"&gt;RESULTS&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Result&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="o"&gt;(),&lt;/span&gt; &lt;span class="n"&gt;next&lt;/span&gt;&lt;span class="o"&gt;));&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;
  &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="c1"&gt;// Routing, ingress, egress, checkpoints&lt;/span&gt;
&lt;span class="c1"&gt;// declared in module.yaml.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's the whole pitch: per-key durable state and exactly-once messaging without authoring the Flink topology yourself. It maps cleanly onto fraud detection, IoT digital twins, payment sagas - anything where state is keyed by a logical id and you'd rather not roll your own Flink job around it.&lt;/p&gt;

&lt;h2&gt;
  
  
  The state of upstream Apache Stateful Functions
&lt;/h2&gt;

&lt;p&gt;The &lt;a href="https://github.com/apache/flink-statefun" rel="noopener noreferrer"&gt;upstream repo&lt;/a&gt; is at version &lt;strong&gt;3.4.0&lt;/strong&gt;, released &lt;strong&gt;October 2024&lt;/strong&gt;. That release targets Flink 1.16 and Java 11. The mailing list is quiet, the PR queue is dormant, and there's no public roadmap.&lt;/p&gt;

&lt;p&gt;This isn't a criticism of the upstream maintainers - open-source projects come and go from active development for legitimate reasons. Committer attention is finite, vendors shift focus, the world moves on. It's a statement of fact: if you adopt upstream Stateful Functions today, you're adopting a piece of software the upstream community is no longer actively investing in.&lt;/p&gt;

&lt;p&gt;For some teams, that's fine. The API is stable, the runtime is mature, the model is sound. For others, it's a non-starter - particularly if you need Flink 2.x for any of its new capabilities.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Flink 2.x changed
&lt;/h2&gt;

&lt;p&gt;Flink 2.0 shipped in 2025; 2.2 followed shortly after. The headline changes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Disaggregated state&lt;/strong&gt; - RocksDB state can live in object storage with a smarter caching layer in front, decoupling state size from local disk and unlocking horizontal scaling for stateful jobs that previously hit per-TM disk ceilings.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A modernized configuration system&lt;/strong&gt; - &lt;code&gt;state.backend.type&lt;/code&gt; instead of &lt;code&gt;state.backend&lt;/code&gt;, &lt;code&gt;high-availability.type&lt;/code&gt; instead of &lt;code&gt;high-availability&lt;/code&gt;, and many other YAML keys that follow a consistent prefix-namespacing pattern.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Java 17 minimum / Java 21 supported&lt;/strong&gt; - upstream Stateful Functions still pins Java 11.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A revamped Kinesis connector&lt;/strong&gt; - &lt;code&gt;KinesisStreamsSource&lt;/code&gt; with proper Flink 2.x source/sink APIs, replacing the older &lt;code&gt;FlinkKinesisConsumer&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Stricter type system&lt;/strong&gt; for keyed state, surfacing latent bugs in serializers that 1.x silently tolerated.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;None of these are bolt-on patches. They're runtime-deep changes, and porting Stateful Functions to ride them takes more than a version bump in the POM.&lt;/p&gt;

&lt;h2&gt;
  
  
  What we changed
&lt;/h2&gt;

&lt;p&gt;The surface area of the work - full details in &lt;a href="https://kzmlabs.github.io/flink-statefun/upstream-vs-kzm/" rel="noopener noreferrer"&gt;the migration guide&lt;/a&gt;:&lt;/p&gt;

&lt;h3&gt;
  
  
  Build matrix
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Java 11 → &lt;strong&gt;Java 21&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Flink 1.16 → &lt;strong&gt;Flink 2.2&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;JUnit 4 → &lt;strong&gt;JUnit 5.11&lt;/strong&gt; (every test migrated, message-last argument order, Hamcrest pinned explicitly)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;maven-shade-plugin 3.6.1&lt;/code&gt; for &lt;code&gt;statefun-protobuf-shaded&lt;/code&gt; - bytecode-level relocation, replacing the source-level &lt;code&gt;replacer-plugin&lt;/code&gt; (last released 2017)&lt;/li&gt;
&lt;li&gt;Module structure preserved - same &lt;code&gt;statefun-flink-runner&lt;/code&gt;, &lt;code&gt;statefun-sdk-java&lt;/code&gt;, etc. SDK consumers don't break.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Connector replumbing
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Kafka&lt;/strong&gt; - straightforward. The Flink 2.x Kafka connector is API-compatible enough that the StateFun ingress/egress layer needed targeted updates, not rewrites.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Kinesis&lt;/strong&gt; - the heavier lift. The old &lt;code&gt;FlinkKinesisConsumer&lt;/code&gt; is gone in 2.x. We rewrote the StateFun Kinesis ingress and egress on top of &lt;code&gt;KinesisStreamsSource&lt;/code&gt; and &lt;code&gt;KinesisStreamsSink&lt;/code&gt;. One subtle invariant: the new connector hands the &lt;strong&gt;stream ARN&lt;/strong&gt; (not the short name) to &lt;code&gt;KinesisDeserializationSchema.deserialize&lt;/code&gt;, which means the routing layer's lookup table has to be re-keyed by ARN. Easy to miss; we caught it in the K8s end-to-end suite.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  State backend keys
&lt;/h3&gt;

&lt;p&gt;Upstream &lt;code&gt;module.yaml&lt;/code&gt; snippets that worked on Flink 1.16 don't work on 2.x because of the configuration key changes. We didn't try to rewrite them transparently - clear migration is more honest than silent translation. The migration guide spells out what to change.&lt;/p&gt;

&lt;h3&gt;
  
  
  CI and release pipeline
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;A new end-to-end suite that boots a real &lt;code&gt;kind&lt;/code&gt; cluster, deploys via the &lt;strong&gt;Flink Kubernetes Operator 1.11&lt;/strong&gt;, runs both Kafka and Kinesis ingress/egress flows against LocalStack (one pod for Kinesis + S3), and tears the cluster down deterministically. This is the gate before every release.&lt;/li&gt;
&lt;li&gt;Maven Central publishing under &lt;code&gt;io.github.kzmlabs.flinkstatefun&lt;/code&gt; - pulled the same way you'd pull any upstream Apache library.&lt;/li&gt;
&lt;li&gt;GHCR-published Docker images for the StateFun Flink runtime, signed via Sigstore keyless attestation, scanned with Trivy, with SLSA build provenance.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Documentation site
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Full MkDocs Material site at &lt;a href="https://kzmlabs.github.io/flink-statefun/" rel="noopener noreferrer"&gt;kzmlabs.github.io/flink-statefun&lt;/a&gt;, replacing the old upstream Hugo site.&lt;/li&gt;
&lt;li&gt;Two worked examples - fraud detection over Kafka, IoT digital twins over Kinesis - both with full Java code and &lt;code&gt;module.yaml&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  What stayed the same
&lt;/h2&gt;

&lt;p&gt;This is the part to emphasize: &lt;strong&gt;the programming model is unchanged.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;If your existing app uses upstream &lt;code&gt;StatefulFunction&lt;/code&gt;, &lt;code&gt;Context&lt;/code&gt;, &lt;code&gt;ValueSpec&lt;/code&gt;, &lt;code&gt;Address&lt;/code&gt;, &lt;code&gt;RoutableMessage&lt;/code&gt; - they all work. The remote-function HTTP protocol is unchanged. The &lt;code&gt;module.yaml&lt;/code&gt; schema is mostly unchanged (one or two keys updated for Flink 2.x, called out in the migration guide).&lt;/p&gt;

&lt;p&gt;We wanted migration to feel like a Flink major-version bump, not a framework rewrite. The whole point of forking - sorry, of &lt;em&gt;continuing&lt;/em&gt; - is that the model was good. The model didn't need to change. The runtime under it did.&lt;/p&gt;

&lt;h2&gt;
  
  
  A note on naming
&lt;/h2&gt;

&lt;p&gt;We don't call this a "fork" anymore. GitHub detached the repo from &lt;code&gt;apache/flink-statefun&lt;/code&gt;'s fork network in April 2026 (we asked GitHub support - the standard process for projects that have substantively diverged). It's a &lt;strong&gt;continuation&lt;/strong&gt;: derived from Apache Stateful Functions, no longer in the same upstream lineage, governed by us, released on our cadence.&lt;/p&gt;

&lt;p&gt;This isn't a hostile fork. We didn't start it because we disagreed with anyone. We started it because the project we depended on stopped shipping releases for the runtime we needed to deploy on, and we needed to ship our products. Apache Stateful Functions remains an Apache Software Foundation project, with its own license (Apache 2.0), provenance, and governance. We respect that, we link to it, we credit it, and we keep our docs explicit about which features came from upstream and which we added.&lt;/p&gt;

&lt;h2&gt;
  
  
  When should you use this?
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Is Apache Stateful Functions still maintained?
&lt;/h3&gt;

&lt;p&gt;Short answer: the upstream Apache project hasn't shipped a release since October 2024. The continuation &lt;code&gt;kzmlabs/flink-statefun&lt;/code&gt; is the actively maintained line - Apache Flink 2.2, Java 21, tagged releases on a regular cadence, and a Kubernetes end-to-end gate before anything ships to Maven Central.&lt;/p&gt;

&lt;h3&gt;
  
  
  Use &lt;code&gt;kzmlabs/flink-statefun&lt;/code&gt; when you need
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Stateful Functions on Apache Flink 2.x&lt;/strong&gt; (Flink 2.0, 2.1, 2.2) with Java 17 or Java 21. Upstream Apache Stateful Functions still pins Flink 1.16 and Java 11.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Durable per-key state with exactly-once messaging&lt;/strong&gt; - without writing a Flink job by hand. Replaces hand-rolled &lt;code&gt;KeyedProcessFunction&lt;/code&gt; + checkpoint config boilerplate with a function-and-state programming model.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Polyglot stateful microservices&lt;/strong&gt; - write functions in Java, Python, Go, or JavaScript over the same StateFun remote-function HTTP protocol, share state and routing through Flink, scale function pods independently of the runtime.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Kafka and Kinesis stateful streaming&lt;/strong&gt; - first-class ingress and egress on both, with the Flink 2.x &lt;code&gt;KinesisStreamsSource&lt;/code&gt; and &lt;code&gt;KinesisStreamsSink&lt;/code&gt; properly integrated. Upstream's Kinesis support stopped at the Flink 1.x &lt;code&gt;FlinkKinesisConsumer&lt;/code&gt; and never made the jump.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Kubernetes-native deployment of stateful streaming jobs&lt;/strong&gt; - the Flink Kubernetes Operator deploys StateFun the same way it deploys any other Flink job. Every release is gated on a real &lt;code&gt;kind&lt;/code&gt; + Operator + LocalStack run with the actual remote-function pod, S3 checkpoints, and both Kafka and Kinesis verified end-to-end.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A migration path off Apache Stateful Functions 3.4.0&lt;/strong&gt; - same programming model, one Maven coordinate change (&lt;code&gt;org.apache.flink:statefun-*&lt;/code&gt; → &lt;code&gt;io.github.kzmlabs.flinkstatefun:statefun-*&lt;/code&gt;), and a documented upgrade for the handful of &lt;code&gt;module.yaml&lt;/code&gt; keys that moved in Flink 2.x. Most user code compiles unchanged. See the &lt;a href="https://kzmlabs.github.io/flink-statefun/upstream-vs-kzm/" rel="noopener noreferrer"&gt;migration guide&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Production-grade supply-chain provenance&lt;/strong&gt; - every release is signed via Sigstore keyless attestation, scanned with Trivy, tracked by &lt;a href="https://scorecard.dev/viewer/?uri=github.com/kzmlabs/flink-statefun" rel="noopener noreferrer"&gt;OpenSSF Scorecard&lt;/a&gt;, and container images carry SLSA build provenance for &lt;code&gt;gh attestation verify&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Don't use it if
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;You're already running upstream Apache Stateful Functions successfully on Flink 1.16 with no CVE pressure, no Java 11 ceiling, and no dependency conflicts pulling you toward a newer stack. There's no urgency to migrate purely because the version number is higher. Stability is a feature.&lt;/li&gt;
&lt;li&gt;You don't actually need stateful actors. If your processing is stateless or windowed-aggregation-only, vanilla Apache Flink, Kafka Streams, or ksqlDB may fit better - the StateFun model adds value specifically when you need durable per-key state with addressable, message-passing functions on top of a stream-processing runtime.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  How does this compare to Apache Flink, Kafka Streams, ksqlDB, or Akka?
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;vs. vanilla Apache Flink&lt;/strong&gt; - same runtime underneath, but you write functions keyed by a logical id instead of authoring DataStream / Table API jobs. Trade some flexibility for much faster development of stateful, event-driven systems.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;vs. Kafka Streams&lt;/strong&gt; - Kafka Streams is excellent for Kafka-only stateful pipelines bounded to the JVM. StateFun runs on Flink, supports Kinesis, and gives you polyglot remote functions over HTTP.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;vs. ksqlDB&lt;/strong&gt; - ksqlDB is SQL-first and shines for declarative stream transformations. StateFun is for actor-style code with per-key state and command/control patterns.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;vs. Akka / Pekko cluster sharding&lt;/strong&gt; - Akka gives you actors but you own the persistence, supervision, and rebalancing. StateFun gives you actors backed by Flink's checkpointing, exactly-once delivery, and operator-managed scale-out.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Where to find it
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Source:&lt;/strong&gt; &lt;a href="https://github.com/kzmlabs/flink-statefun" rel="noopener noreferrer"&gt;github.com/kzmlabs/flink-statefun&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Docs:&lt;/strong&gt; &lt;a href="https://kzmlabs.github.io/flink-statefun/" rel="noopener noreferrer"&gt;kzmlabs.github.io/flink-statefun&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Maven Central:&lt;/strong&gt; &lt;code&gt;io.github.kzmlabs.flinkstatefun&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Docker images:&lt;/strong&gt; &lt;code&gt;ghcr.io/kzmlabs/flink-statefun&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Awesome list:&lt;/strong&gt; &lt;a href="https://github.com/kzmlabs/awesome-statefun" rel="noopener noreferrer"&gt;github.com/kzmlabs/awesome-statefun&lt;/a&gt; - curated resources for the broader stateful-actor ecosystem&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Migration guide:&lt;/strong&gt; &lt;a href="https://kzmlabs.github.io/flink-statefun/upstream-vs-kzm/" rel="noopener noreferrer"&gt;from upstream&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you're trying it out and something breaks, file an issue. If you're using it in production and it's working, drop a star - the only way a continuation like this stays viable is if the people using it tell us they're using it.&lt;/p&gt;

</description>
      <category>apacheflink</category>
      <category>statefun</category>
      <category>java</category>
      <category>actors</category>
    </item>
  </channel>
</rss>
