<?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: Alexander Novikov</title>
    <description>The latest articles on DEV Community by Alexander Novikov (@alnovis).</description>
    <link>https://dev.to/alnovis</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%2F2229822%2Fe05bd1cb-55bc-45b9-ad8d-f5fe3b1ad69f.jpg</url>
      <title>DEV Community: Alexander Novikov</title>
      <link>https://dev.to/alnovis</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/alnovis"/>
    <language>en</language>
    <item>
      <title>MLIR, Nanopass, and Green-Red Trees: Compiler Ideas That Make Code Generators Better</title>
      <dc:creator>Alexander Novikov</dc:creator>
      <pubDate>Mon, 23 Mar 2026 11:08:51 +0000</pubDate>
      <link>https://dev.to/alnovis/mlir-nanopass-and-green-red-trees-compiler-ideas-that-make-code-generators-better-1p9n</link>
      <guid>https://dev.to/alnovis/mlir-nanopass-and-green-red-trees-compiler-ideas-that-make-code-generators-better-1p9n</guid>
      <description>&lt;p&gt;Most code generators are monolithic. A single class reads some input, makes a bunch of decisions, and spits out source files. It works until it doesn't — until you need to support another target language, until you need incremental builds, until a 2000-line generator method becomes everyone's least favorite file to touch.&lt;/p&gt;

&lt;p&gt;I hit this wall while building &lt;a href="https://github.com/alnovis/proto-wrapper-plugin" rel="noopener noreferrer"&gt;Proto Wrapper Plugin&lt;/a&gt;, a tool that generates version-agnostic Java wrappers from multiple protobuf schemas. The generator worked fine, but every new feature meant wrestling with a monolith. Adding Kotlin support would have meant duplicating the entire thing.&lt;/p&gt;

&lt;p&gt;So I went looking for better ideas. I found three — from compiler design, of all places — and they changed how I think about code generation entirely.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Update:&lt;/strong&gt; These ideas have since been implemented as &lt;a href="https://github.com/alnovis/ircraft" rel="noopener noreferrer"&gt;IRCraft&lt;/a&gt; — a standalone Scala 3 library for multi-language code generation with MLIR-inspired dialects, nanopass pipelines, and immutable green trees.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  The Three Concepts
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Concept&lt;/th&gt;
&lt;th&gt;Origin&lt;/th&gt;
&lt;th&gt;What it gives you&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;MLIR Dialects&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;LLVM/MLIR project&lt;/td&gt;
&lt;td&gt;Structure — multiple abstraction levels with typed operations&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Nanopass&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Academic compiler research&lt;/td&gt;
&lt;td&gt;Modularity — many small composable transformations&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Green-Red Trees&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Roslyn (C# compiler)&lt;/td&gt;
&lt;td&gt;Immutability — safe, cacheable, incrementally updatable AST&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Each one is powerful on its own. Together, they form an architecture that's extensible, testable, and surprisingly elegant.&lt;/p&gt;

&lt;p&gt;Let's look at each one.&lt;/p&gt;




&lt;h2&gt;
  
  
  MLIR: Stop Mixing Abstraction Levels
&lt;/h2&gt;

&lt;h3&gt;
  
  
  The Idea
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://mlir.llvm.org/" rel="noopener noreferrer"&gt;MLIR&lt;/a&gt; (Multi-Level Intermediate Representation) is an LLVM project that structures compiler IR into &lt;em&gt;dialects&lt;/em&gt; — groups of operations at a specific abstraction level. Instead of one giant IR that represents everything from high-level constructs to machine instructions, MLIR lets you define separate dialects and &lt;em&gt;lower&lt;/em&gt; between them progressively.&lt;/p&gt;

&lt;p&gt;The key insight: &lt;strong&gt;different stages of compilation think in different abstractions.&lt;/strong&gt; Forcing everything into one representation creates a mess.&lt;/p&gt;

&lt;h3&gt;
  
  
  How This Applies to Code Generation
&lt;/h3&gt;

&lt;p&gt;A typical code generator goes straight from input to output:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Finpmmx7dqnfpf9uxgz56.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Finpmmx7dqnfpf9uxgz56.png" alt="Simple flow: Protobuf descriptors → JavaPoet types → .java files" width="615" height="70"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Everything happens at once. The code that understands protobuf semantics is tangled with the code that knows Java syntax. Want to add Kotlin output? You'd need to rewrite everything.&lt;/p&gt;

&lt;p&gt;With dialects, you get clean separation:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1lzq0ttqjygkbx3ks278.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1lzq0ttqjygkbx3ks278.png" alt="Dialect levels: Proto → Semantic → Java/Kotlin Code Dialects" width="586" height="454"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Each dialect defines its own &lt;em&gt;operations&lt;/em&gt; — typed IR nodes that belong to that abstraction level. A &lt;code&gt;MessageOp&lt;/code&gt; belongs to the Proto Dialect. A &lt;code&gt;ClassOp&lt;/code&gt; belongs to the Semantic Dialect. You can't accidentally mix them.&lt;/p&gt;

&lt;p&gt;The critical piece is the &lt;strong&gt;Semantic Dialect&lt;/strong&gt; — it captures OOP concepts (classes, interfaces, methods, fields) without any language-specific details. This layer is shared. Adding Kotlin support means writing a new Code Dialect and an emission pass. The entire Proto → Semantic pipeline stays untouched.&lt;/p&gt;

&lt;h3&gt;
  
  
  Beyond Code Generators
&lt;/h3&gt;

&lt;p&gt;The dialect idea applies anywhere you have multiple abstraction levels:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;API gateways&lt;/strong&gt; — represent requests at the protocol level (HTTP, gRPC), the domain level (business operations), and the backend level (database queries, service calls)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Configuration management&lt;/strong&gt; — high-level intent ("deploy this service with 3 replicas"), mid-level plan ("create these Kubernetes resources"), low-level actions ("apply these YAML files")&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Data pipelines&lt;/strong&gt; — logical plan (what to compute), physical plan (how to compute it), execution plan (where to run it)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The pattern is always the same: define clean abstraction boundaries, type the operations at each level, and lower progressively.&lt;/p&gt;




&lt;h2&gt;
  
  
  Nanopass: Small Passes Beat Big Passes
&lt;/h2&gt;

&lt;h3&gt;
  
  
  The Idea
&lt;/h3&gt;

&lt;p&gt;The &lt;a href="https://nanopass.org/" rel="noopener noreferrer"&gt;Nanopass Framework&lt;/a&gt; comes from academic compiler research. Its premise is simple: instead of a few large compiler passes that each do many things, write many small passes that each do one thing.&lt;/p&gt;

&lt;p&gt;A traditional compiler might have a "type checking" pass that also resolves overloads, infers types, checks visibility, and validates constraints. A nanopass compiler splits that into five separate passes, each focused on a single concern.&lt;/p&gt;

&lt;h3&gt;
  
  
  How This Applies to Code Generation
&lt;/h3&gt;

&lt;p&gt;My original generator was one method that:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Read protobuf descriptors&lt;/li&gt;
&lt;li&gt;Decided which messages to include&lt;/li&gt;
&lt;li&gt;Resolved type references&lt;/li&gt;
&lt;li&gt;Detected type conflicts between versions&lt;/li&gt;
&lt;li&gt;Generated interfaces&lt;/li&gt;
&lt;li&gt;Generated abstract classes&lt;/li&gt;
&lt;li&gt;Generated implementations&lt;/li&gt;
&lt;li&gt;Generated builders (conditionally)&lt;/li&gt;
&lt;li&gt;Generated VersionContext factory&lt;/li&gt;
&lt;li&gt;Wrote files&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;That's 10 responsibilities in one place. Testing any of them in isolation was nearly impossible.&lt;/p&gt;

&lt;p&gt;With nanopass, each becomes a standalone transformation:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4fakzzwh79ei2mtdo6tx.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4fakzzwh79ei2mtdo6tx.png" alt="Nanopass pipeline: Proto → Semantic → Emission passes" width="327" height="1272"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Each pass is a pure function: immutable input → immutable output. Each can be tested with a 10-line unit test. Each can be conditionally enabled:&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="c1"&gt;// AddBuildersPass only runs when the user configured generateBuilders=true&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;boolean&lt;/span&gt; &lt;span class="nf"&gt;isEnabled&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;PassContext&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&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;getConfig&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"generateBuilders"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  The Composition Problem
&lt;/h3&gt;

&lt;p&gt;Small passes need a way to compose. In our case, it's a simple pipeline:&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="nc"&gt;Pipeline&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;ProtoFileInput&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;List&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;JavaFile&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;pipeline&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Pipeline&lt;/span&gt;
    &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;from&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;ProtoFileInput&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="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;then&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;ParseProtoPass&lt;/span&gt;&lt;span class="o"&gt;())&lt;/span&gt;
    &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;then&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;ResolveTypesPass&lt;/span&gt;&lt;span class="o"&gt;())&lt;/span&gt;
    &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;then&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;FilterMessagesPass&lt;/span&gt;&lt;span class="o"&gt;())&lt;/span&gt;
    &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;then&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;ValidateProtoPass&lt;/span&gt;&lt;span class="o"&gt;())&lt;/span&gt;
    &lt;span class="c1"&gt;// ... more passes&lt;/span&gt;
    &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;build&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;

&lt;span class="nc"&gt;List&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;JavaFile&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;output&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pipeline&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;execute&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;input&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The pipeline handles execution order, skips disabled passes, and logs timing. Passes communicate through &lt;code&gt;PassContext&lt;/code&gt; — a typed key-value store for shared data (e.g., a type registry built by one pass and consumed by another).&lt;/p&gt;

&lt;h3&gt;
  
  
  Beyond Code Generators
&lt;/h3&gt;

&lt;p&gt;Nanopass is just the &lt;a href="https://en.wikipedia.org/wiki/Single-responsibility_principle" rel="noopener noreferrer"&gt;Single Responsibility Principle&lt;/a&gt; applied to data transformations. It works anywhere you have a processing pipeline:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Image processing&lt;/strong&gt; — resize, crop, watermark, compress, optimize — each as a separate filter&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Request validation&lt;/strong&gt; — authenticate, authorize, validate schema, check rate limits, sanitize input — each as middleware&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Data migration&lt;/strong&gt; — extract, clean, transform, validate, load — each as a named step with independent rollback&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Document processing&lt;/strong&gt; — parse, normalize, enrich, validate, render — each as a pipeline stage&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The pattern gives you testability (test each step alone), composability (add/remove/reorder steps), and observability (log/time each step independently).&lt;/p&gt;




&lt;h2&gt;
  
  
  Green-Red Trees: Immutability That Pays for Itself
&lt;/h2&gt;

&lt;h3&gt;
  
  
  The Idea
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://ericlippert.com/2012/06/08/red-green-trees/" rel="noopener noreferrer"&gt;Green-Red Trees&lt;/a&gt; come from Roslyn, Microsoft's C# and VB.NET compiler. The problem Roslyn solves: an IDE needs to re-parse code on every keystroke, but re-creating the entire syntax tree each time is expensive.&lt;/p&gt;

&lt;p&gt;The solution splits the tree into two layers:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fx6kyz5j84jfq6p08k8dd.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fx6kyz5j84jfq6p08k8dd.png" alt="Green-Red Trees: Red facade wraps immutable Green nodes" width="800" height="329"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Green tree&lt;/strong&gt; (the data):&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Immutable&lt;/li&gt;
&lt;li&gt;Content-addressable (identity based on content, not object reference)&lt;/li&gt;
&lt;li&gt;No parent references&lt;/li&gt;
&lt;li&gt;Relative positions (width), not absolute&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Red tree&lt;/strong&gt; (the navigation):&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Lightweight facade over green nodes&lt;/li&gt;
&lt;li&gt;Adds parent references and absolute positions&lt;/li&gt;
&lt;li&gt;Created lazily during traversal&lt;/li&gt;
&lt;li&gt;Thrown away entirely on any change&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The insight: &lt;strong&gt;most of the tree doesn't change between edits.&lt;/strong&gt; If you insert a character on line 50, every node on lines 1-49 is structurally identical. With content-based identity, you can reuse those green nodes directly — no copying, no diffing.&lt;/p&gt;

&lt;h3&gt;
  
  
  How This Applies to Code Generation
&lt;/h3&gt;

&lt;p&gt;All our IR nodes are immutable case classes. Each computes a &lt;code&gt;contentHash&lt;/code&gt; from its content:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight scala"&gt;&lt;code&gt;&lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;MessageOp&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="k"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;String&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;presentInVersions&lt;/span&gt;&lt;span class="k"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;Set&lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="kt"&gt;String&lt;/span&gt;&lt;span class="o"&gt;],&lt;/span&gt;
    &lt;span class="n"&gt;regions&lt;/span&gt;&lt;span class="k"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;Vector&lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="kt"&gt;Region&lt;/span&gt;&lt;span class="o"&gt;],&lt;/span&gt;
    &lt;span class="n"&gt;attributes&lt;/span&gt;&lt;span class="k"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;AttributeMap&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;span&lt;/span&gt;&lt;span class="k"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;Option&lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="kt"&gt;Span&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;
&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="k"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;Operation&lt;/span&gt;&lt;span class="k"&gt;:&lt;/span&gt;

  &lt;span class="kt"&gt;lazy&lt;/span&gt; &lt;span class="kt"&gt;val&lt;/span&gt; &lt;span class="kt"&gt;contentHash:&lt;/span&gt; &lt;span class="kt"&gt;Int&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
    &lt;span class="nv"&gt;ContentHash&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="py"&gt;combine&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
      &lt;span class="nv"&gt;ContentHash&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="py"&gt;ofString&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="o"&gt;),&lt;/span&gt;
      &lt;span class="nv"&gt;ContentHash&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="py"&gt;ofSet&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;presentInVersions&lt;/span&gt;&lt;span class="o"&gt;),&lt;/span&gt;
      &lt;span class="nv"&gt;ContentHash&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="py"&gt;ofList&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;fields&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="py"&gt;toList&lt;/span&gt;&lt;span class="o"&gt;)(&lt;/span&gt;&lt;span class="n"&gt;using&lt;/span&gt; &lt;span class="nv"&gt;Operation&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="py"&gt;operationHashable&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
    &lt;span class="o"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Two &lt;code&gt;MessageOp&lt;/code&gt; nodes with the same content have the same hash. This gives us:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Change detection.&lt;/strong&gt; After running a pass, compare output hash to input hash. Same hash? Nothing changed — skip downstream passes for this node.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Caching.&lt;/strong&gt; Store &lt;code&gt;contentHash → generated output&lt;/code&gt; mapping. If a proto file hasn't changed, reuse the previous output without running the pipeline.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Incremental builds.&lt;/strong&gt; In a project with 50 proto files, changing one file triggers regeneration for only that file's IR subtree. Everything else is a cache hit.&lt;/p&gt;

&lt;p&gt;The "red tree" layer (parent references, absolute positions) is planned for IDE integration — enabling "go to definition" and "find usages" across proto schemas and generated code.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why Immutability Matters for Passes
&lt;/h3&gt;

&lt;p&gt;Nanopass transformations are pure functions: &lt;code&gt;GreenNode(v1) → GreenNode(v2)&lt;/code&gt;. The input is never modified. This means:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;No defensive copying.&lt;/strong&gt; You know the input won't change under you.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Safe parallelism.&lt;/strong&gt; Multiple passes can read the same input concurrently.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Time travel.&lt;/strong&gt; Keep the input around for debugging — it's immutable, it won't change.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Structural sharing.&lt;/strong&gt; If a pass only modifies 2 out of 20 fields in a message, the unmodified field ops are shared between input and output trees.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Beyond Code Generators
&lt;/h3&gt;

&lt;p&gt;Immutable content-addressed trees appear in many systems:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Git&lt;/strong&gt; — the entire object model (blobs, trees, commits) is content-addressed and immutable. Every &lt;code&gt;git commit&lt;/code&gt; creates new tree objects while sharing unchanged blobs.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;React&lt;/strong&gt; — virtual DOM diffing works because components produce immutable trees that can be compared efficiently.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Nix&lt;/strong&gt; — the package manager uses content-addressed derivations. Same inputs always produce the same output hash.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Persistent data structures&lt;/strong&gt; — Clojure's vectors, maps, and sets use structural sharing to create "modified" copies in O(log n) time.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Event sourcing&lt;/strong&gt; — events are immutable facts. State is derived by replaying events, with snapshots as caching.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The principle is the same: make data immutable, identify it by content, share structure where possible.&lt;/p&gt;




&lt;h2&gt;
  
  
  How They Work Together
&lt;/h2&gt;

&lt;p&gt;The three concepts are complementary:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fc7n5hg3qw1g4p10d4d6v.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fc7n5hg3qw1g4p10d4d6v.png" alt="How they work together: Dialects → Nanopass → Green-Red Trees → Dialects" width="369" height="1325"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Dialects&lt;/strong&gt; define the abstraction levels (where)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Passes&lt;/strong&gt; define the transformations (how)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Green nodes&lt;/strong&gt; define the data model (what)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Each pass is a pure function: &lt;code&gt;GreenNode(v1) → GreenNode(v2)&lt;/code&gt;. Input is never modified. This means no defensive copying, safe parallelism, and the ability to keep previous IR versions around for debugging.&lt;/p&gt;




&lt;h2&gt;
  
  
  When to Use These Ideas
&lt;/h2&gt;

&lt;p&gt;You don't need all three concepts, and you don't need to build a full compiler framework. Here's when each one pays off:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;MLIR Dialects&lt;/strong&gt; — when your system has multiple abstraction levels and you're fighting the urge to put everything in one data model. If you find yourself adding "kind" or "type" fields to distinguish between fundamentally different operations, you probably want dialects.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Nanopass&lt;/strong&gt; — when you have a processing pipeline with more than 3-4 concerns. If your &lt;code&gt;generate()&lt;/code&gt; or &lt;code&gt;process()&lt;/code&gt; method is over 200 lines and growing, break it into passes.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Green-Red Trees&lt;/strong&gt; — when you need change detection, caching, or incremental processing. If your data is already immutable (or should be), content hashing is a natural extension.&lt;/p&gt;

&lt;h2&gt;
  
  
  Practical Advice
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Start with nanopass.&lt;/strong&gt; It's the easiest to adopt and gives the most immediate benefit. Take your monolithic processor, identify the distinct responsibilities, and extract them into passes. You'll immediately gain testability.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Add content hashing when you need caching.&lt;/strong&gt; If your pipeline runs on every build and takes noticeable time, add &lt;code&gt;contentHash()&lt;/code&gt; to your data types. Use it for short-circuiting ("nothing changed, skip this pass") and caching ("same input, return cached output").&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Introduce dialects when you add a second target.&lt;/strong&gt; If you only generate Java, one abstraction level might be fine. The moment you need Kotlin, TypeScript, or any second output format, the dialect split pays for itself instantly.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Don't over-abstract.&lt;/strong&gt; These are tools, not rules. A 50-line code generator doesn't need an IR framework. Use the ideas when the complexity justifies them — and not a moment sooner.&lt;/p&gt;




&lt;h2&gt;
  
  
  Further Reading
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://arxiv.org/abs/2002.11054" rel="noopener noreferrer"&gt;MLIR: A Compiler Infrastructure for the End of Moore's Law&lt;/a&gt; — the original MLIR paper&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://mlir.llvm.org/docs/Tutorials/Toy/" rel="noopener noreferrer"&gt;MLIR Tutorial&lt;/a&gt; — LLVM's hands-on tutorial&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://legacy.cs.indiana.edu/~dyb/pubs/nano-jfp.pdf" rel="noopener noreferrer"&gt;A Nanopass Framework for Compiler Education&lt;/a&gt; — the foundational nanopass paper&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://andykeep.com/pubs/np-preprint.pdf" rel="noopener noreferrer"&gt;Nanopass Framework for Commercial Compiler Development&lt;/a&gt; — nanopass in practice&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://ericlippert.com/2012/06/08/red-green-trees/" rel="noopener noreferrer"&gt;Persistence, Facades, and Roslyn's Red-Green Trees&lt;/a&gt; — Eric Lippert's original blog post&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://willspeak.me/2021/11/24/red-green-syntax-trees-an-overview.html" rel="noopener noreferrer"&gt;Red-Green Syntax Trees: An Overview&lt;/a&gt; — accessible summary with examples&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/alnovis/proto-wrapper-plugin" rel="noopener noreferrer"&gt;Proto Wrapper Plugin&lt;/a&gt; — where we're applying all of this&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/alnovis/ircraft" rel="noopener noreferrer"&gt;IRCraft&lt;/a&gt; — the standalone Scala 3 implementation of these ideas&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;em&gt;Originally published at &lt;a href="https://alnovis.io/blog/compiler-ideas-for-code-generation" rel="noopener noreferrer"&gt;alnovis.io&lt;/a&gt; on Februari 5, 2026.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>scala</category>
      <category>compilers</category>
      <category>architecture</category>
      <category>codegen</category>
    </item>
    <item>
      <title>Do You Still Need Computer Science in the Age of AI</title>
      <dc:creator>Alexander Novikov</dc:creator>
      <pubDate>Fri, 20 Mar 2026 23:10:21 +0000</pubDate>
      <link>https://dev.to/alnovis/do-you-still-need-computer-science-in-the-age-of-ai-2mc1</link>
      <guid>https://dev.to/alnovis/do-you-still-need-computer-science-in-the-age-of-ai-2mc1</guid>
      <description>&lt;h2&gt;
  
  
  "Maybe I Should Become a Welder"
&lt;/h2&gt;

&lt;p&gt;A colleague recently told me about a conversation with someone at Amazon. The person described how the company is aggressively pushing AI tools for code generation — management pressure to move faster, ship more, spend less. My colleague, twenty years in the industry, went quiet afterward. "Maybe I should learn welding," he joked. It was only half a joke.&lt;/p&gt;

&lt;p&gt;I get it. When you've spent twenty years in this profession and keep hearing that any intern with Copilot will soon do the same thing — it hits not your skills, but your identity. The sense that everything you've learned actually mattered.&lt;/p&gt;

&lt;p&gt;Amazon, like any large company, has plenty of strong engineers who've given years to the craft and understand exactly what's happening. But popular opinion doesn't make distinctions: why learn algorithms if AI will write them? Why bother with design patterns if Claude or Copilot can generate a working service in an hour? Why understand how a hash table works when the library gives you one out of the box? It seems like you just need to ask — "build me a service" — and it all works out.&lt;/p&gt;

&lt;p&gt;I disagree. Not because I'm defending my profession. But because I can see where mindless use of a tool leads.&lt;/p&gt;

&lt;h2&gt;
  
  
  A Tool, Not a Replacement
&lt;/h2&gt;

&lt;p&gt;There was a time when we wrote code in terminal editors. Vim, Emacs — rough days. Then came Eclipse, Visual Studio, IntelliJ IDEA. It was a quantum leap in productivity: autocomplete, refactoring, debugger, VCS integration. Nobody said IDEs replaced programmers. IDEs made programmers faster.&lt;/p&gt;

&lt;p&gt;But using even an IDE mindlessly comes at a cost. I once came across job postings — from different companies, so this wasn't a one-off — that listed as a requirement: "ability to write and run a Java program outside of IntelliJ IDEA." I laughed for a while, then stopped to think. People had become so dependent on the tool that they couldn't perform a basic task without it. They didn't understand what was happening under the hood.&lt;/p&gt;

&lt;p&gt;AI is the next step in that same evolution. A more powerful tool. But still a tool. And depending on it without understanding the fundamentals leads to the same place — only the stakes are higher.&lt;/p&gt;

&lt;h2&gt;
  
  
  What AI Actually Automated
&lt;/h2&gt;

&lt;p&gt;Let's be honest: what does AI do well?&lt;/p&gt;

&lt;p&gt;It's excellent at generating boilerplate code. CRUD services, data mapping, standard integrations — everything an experienced developer writes on autopilot, AI writes faster. That's real value, and arguing against it is pointless.&lt;/p&gt;

&lt;p&gt;But AI doesn't understand your business context. It doesn't know why you chose eventual consistency over strong consistency. It can't see that your current load will grow tenfold in six months. It doesn't account for the fact that this service will be maintained by a team of three, not twenty. AI doesn't make architectural decisions — it generates code within the decisions that you (or nobody) made for it.&lt;/p&gt;

&lt;p&gt;And that "or nobody" is key. If you don't make a decision deliberately, AI will make it for you. Quietly, confidently, and unconsciously.&lt;/p&gt;

&lt;h2&gt;
  
  
  Two Price Tags
&lt;/h2&gt;

&lt;p&gt;Every piece of software has two costs: the cost of writing it and the cost of maintaining it. The second is almost always several times higher.&lt;/p&gt;

&lt;p&gt;AI has radically reduced the first cost. Generating code is fast, cheap, practically free. But it hasn't reduced the cost of maintenance. If anything, it's made it worse: when code is generated ten times faster, it needs to be reviewed ten times more. And if there's no review, or the reviewer doesn't understand what they're looking at — the cost of maintenance grows exponentially.&lt;/p&gt;

&lt;p&gt;Code that's easy to write and expensive to maintain isn't a savings. It's technical debt taken out at a high interest rate.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Happens Without Oversight
&lt;/h2&gt;

&lt;p&gt;This isn't theory. In March 2026, Amazon faced a series of incidents tied to AI-generated code. On March 2nd — 120,000 lost orders and 1.6 million website errors. On March 5th — a 99% drop in orders across North America: 6.3 million lost orders in a single day. In a separate case, the AI agent Kiro, attempting to fix an issue with a cost calculation system, decided to delete and recreate the environment from scratch. Thirteen hours of downtime.&lt;/p&gt;

&lt;p&gt;Amazon's response is telling. Not abandoning AI. Tightening the requirements for reviewer qualifications. Now any AI-assisted code must be approved by a senior engineer before deployment. Amazon essentially confirmed: the problem isn't the tool — it's who's using it.&lt;/p&gt;

&lt;p&gt;And this isn't just Amazon. According to CodeRabbit, AI-generated code contains 1.7x more bugs than human-written code. 1.75x more logic errors. 1.57x more security issues. And eight times more unnecessary I/O operations. In 2025, pull requests per developer increased by 20%, while incidents per PR rose by 23.5%. More code — more problems.&lt;/p&gt;

&lt;p&gt;A telling case: in February 2026, AI generated code using &lt;code&gt;Promise.all&lt;/code&gt; that fired 4,200 simultaneous requests for user data synchronization. The connection pool couldn't handle it — twenty-two minutes of downtime. Anyone who understands concurrency and backpressure would have spotted the problem in seconds. But whoever accepted this code either wasn't taught that, or decided it no longer mattered.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Language of Task Definition
&lt;/h2&gt;

&lt;p&gt;And this brings us to the main point. Why do you need Computer Science if the machine writes the code?&lt;/p&gt;

&lt;p&gt;Not to write a sorting algorithm on a whiteboard during an interview. Not to memorize the pseudocode for depth-first search. That really is routine work, and yes, the machine can reproduce it.&lt;/p&gt;

&lt;p&gt;The value of Computer Science is in the conceptual framework. In the ability to think systematically, decompose problems, distinguish between abstractions, understand constraints and trade-offs. In the ability to verify what AI just generated for you.&lt;/p&gt;

&lt;p&gt;Without terminology, a person says: "rewrite this better," "something's off here," "make it look nice," "speed it up," "fix the architecture." Fix it how? Speed up in what direction? What does "better" mean?&lt;/p&gt;

&lt;p&gt;With terminology, a person says: isolate the domain from infrastructure, wire dependencies through IoC, implement an adapter for the legacy API integration, replace branching with the strategy pattern, reduce coupling, separate component responsibilities, fix the abstraction leak, break circular dependencies, extract object creation into a factory. Use a doubly linked list instead of an array, optimize hash table collisions, reduce unnecessary allocations. Protect the module from race conditions, add backpressure, use a circuit breaker. Don't mix application service with domain service, preserve aggregate invariants, make the operation idempotent.&lt;/p&gt;

&lt;p&gt;The first person asks AI to guess what they mean. The second precisely defines the task. And more importantly, the second one can verify the result.&lt;/p&gt;

&lt;h2&gt;
  
  
  CS as a Frame of Reference
&lt;/h2&gt;

&lt;p&gt;I'm not saying you need to implement a hash table from scratch with your eyes closed. I'm saying you need to understand how it works — so you can notice when AI suggests the wrong data structure. You don't need to remember Dijkstra's implementation — but you need to understand algorithmic complexity to see when AI produces O(n²) where O(n log n) would do.&lt;/p&gt;

&lt;p&gt;Though it's still better to both understand and know how to implement. Or at least strive for it.&lt;/p&gt;

&lt;p&gt;Computer Science isn't a set of skills you need to reproduce by hand. It's a frame of reference in which you think about software. IoC, SOLID, GRASP, DDD, Clean Architecture, CQRS, Event Sourcing, CAP theorem, ACID, consistency models, memory model, cache locality, idempotency, fault tolerance — these aren't for exams. They're a working vocabulary that lets you define tasks precisely, review results meaningfully, and tell working architecture from a convincing-looking hallucination.&lt;/p&gt;

&lt;h2&gt;
  
  
  Not About Fear
&lt;/h2&gt;

&lt;p&gt;I'm not trying to scare anyone. AI is a breakthrough, and I use it every day. It makes me more productive. But it doesn't make me unnecessary — just as IntelliJ didn't make Java developers unnecessary.&lt;/p&gt;

&lt;p&gt;To that colleague who joked about becoming a welder, I'd say: your twenty years of experience haven't lost their value. They've become more valuable. Because now you're needed not to write code, but to understand what's been written. To control quality. To make architectural decisions. To spot design flaws before they become production incidents.&lt;/p&gt;

&lt;p&gt;Computer Science matters. Maybe more than ever. Just not the kind they ask about in interviews. The kind that lets you think.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Originally published at &lt;a href="https://alnovis.io/blog/do-you-need-cs-in-ai-era" rel="noopener noreferrer"&gt;alnovis.io&lt;/a&gt; on March 20, 2026.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>opinion</category>
      <category>ai</category>
      <category>computerscience</category>
      <category>career</category>
    </item>
    <item>
      <title>Proto Wrapper Plugin: How I Stopped Fighting Protobuf Versioning</title>
      <dc:creator>Alexander Novikov</dc:creator>
      <pubDate>Wed, 04 Mar 2026 12:03:59 +0000</pubDate>
      <link>https://dev.to/alnovis/proto-wrapper-plugin-how-i-stopped-fighting-protobuf-versioning-4jd3</link>
      <guid>https://dev.to/alnovis/proto-wrapper-plugin-how-i-stopped-fighting-protobuf-versioning-4jd3</guid>
      <description>&lt;p&gt;If you've ever worked on a system that processes protobuf messages from multiple protocol versions, you know the pain. You add a new field, change a type from &lt;code&gt;int32&lt;/code&gt; to &lt;code&gt;enum&lt;/code&gt;, and suddenly your codebase is littered with &lt;code&gt;if (version == 1) ... else if (version == 2)&lt;/code&gt; blocks.&lt;/p&gt;

&lt;p&gt;I built Proto Wrapper Plugin to solve this problem once and for all.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Pain Point
&lt;/h2&gt;

&lt;p&gt;Here's a typical scenario. You have a payment processing system. Version 1 of your protocol uses an integer for payment type:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight protobuf"&gt;&lt;code&gt;&lt;span class="c1"&gt;// v1/payment.proto&lt;/span&gt;
&lt;span class="kd"&gt;message&lt;/span&gt; &lt;span class="nc"&gt;Payment&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kt"&gt;int32&lt;/span&gt; &lt;span class="na"&gt;payment_type&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;  &lt;span class="c1"&gt;// 1=cash, 2=card, 3=transfer&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then someone decides (rightfully so) that enums are better:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight protobuf"&gt;&lt;code&gt;&lt;span class="c1"&gt;// v2/payment.proto&lt;/span&gt;
&lt;span class="kd"&gt;message&lt;/span&gt; &lt;span class="nc"&gt;Payment&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="n"&gt;PaymentType&lt;/span&gt; &lt;span class="na"&gt;payment_type&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;enum&lt;/span&gt; &lt;span class="n"&gt;PaymentType&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;CASH&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="na"&gt;CARD&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="na"&gt;TRANSFER&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now your code looks like this:&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="c1"&gt;// This is everywhere in your codebase&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;protocolVersion&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;type&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;paymentV1&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getPaymentType&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
    &lt;span class="n"&gt;processPayment&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;type&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&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;protocolVersion&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="nc"&gt;PaymentType&lt;/span&gt; &lt;span class="n"&gt;type&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;paymentV2&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getPaymentType&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
    &lt;span class="n"&gt;processPayment&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;type&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getNumber&lt;/span&gt;&lt;span class="o"&gt;());&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Multiply this by dozens of messages, and you have a maintenance nightmare.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Solution: One Interface to Rule Them All
&lt;/h2&gt;

&lt;p&gt;Proto Wrapper generates a unified Java API that works across all your protocol versions:&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="c1"&gt;// Single code path. Works with v1, v2, v3... whatever.&lt;/span&gt;
&lt;span class="nc"&gt;Payment&lt;/span&gt; &lt;span class="n"&gt;payment&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;versionContext&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;wrapPayment&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;anyProto&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;type&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;payment&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getPaymentType&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;  &lt;span class="c1"&gt;// Always returns int&lt;/span&gt;
&lt;span class="nc"&gt;PaymentType&lt;/span&gt; &lt;span class="n"&gt;typeEnum&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;payment&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getPaymentTypeEnum&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;  &lt;span class="c1"&gt;// Returns enum when available&lt;/span&gt;

&lt;span class="kt"&gt;byte&lt;/span&gt;&lt;span class="o"&gt;[]&lt;/span&gt; &lt;span class="n"&gt;bytes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;payment&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;toBytes&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;  &lt;span class="c1"&gt;// Serializes back to original version&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The plugin analyzes your proto schemas, detects conflicts, and generates appropriate accessors. You write version-agnostic code, and the generated wrappers handle the details.&lt;/p&gt;

&lt;h2&gt;
  
  
  Getting Started
&lt;/h2&gt;

&lt;p&gt;Add the plugin to your Maven build:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;plugin&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;groupId&amp;gt;&lt;/span&gt;io.alnovis&lt;span class="nt"&gt;&amp;lt;/groupId&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;artifactId&amp;gt;&lt;/span&gt;proto-wrapper-maven-plugin&lt;span class="nt"&gt;&amp;lt;/artifactId&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;version&amp;gt;&lt;/span&gt;2.3.2&lt;span class="nt"&gt;&amp;lt;/version&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;configuration&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;basePackage&amp;gt;&lt;/span&gt;com.example.model&lt;span class="nt"&gt;&amp;lt;/basePackage&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;protoRoot&amp;gt;&lt;/span&gt;${basedir}/proto&lt;span class="nt"&gt;&amp;lt;/protoRoot&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;versions&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;version&amp;gt;&amp;lt;protoDir&amp;gt;&lt;/span&gt;v1&lt;span class="nt"&gt;&amp;lt;/protoDir&amp;gt;&amp;lt;/version&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;version&amp;gt;&amp;lt;protoDir&amp;gt;&lt;/span&gt;v2&lt;span class="nt"&gt;&amp;lt;/protoDir&amp;gt;&amp;lt;/version&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;/versions&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/configuration&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;executions&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;execution&amp;gt;&amp;lt;goals&amp;gt;&amp;lt;goal&amp;gt;&lt;/span&gt;generate&lt;span class="nt"&gt;&amp;lt;/goal&amp;gt;&amp;lt;/goals&amp;gt;&amp;lt;/execution&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/executions&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/plugin&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Or if you prefer Gradle:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="nf"&gt;plugins&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;id&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"io.alnovis.proto-wrapper"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;version&lt;/span&gt; &lt;span class="s"&gt;"2.3.2"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nf"&gt;protoWrapper&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;basePackage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"com.example.model"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;protoRoot&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;file&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"proto"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="nf"&gt;versions&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nf"&gt;version&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"v1"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="nf"&gt;version&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"v2"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Run &lt;code&gt;mvn generate-sources&lt;/code&gt; or &lt;code&gt;./gradlew generateProtoWrapper&lt;/code&gt;, and you get a clean API.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Conflicts Can It Handle?
&lt;/h2&gt;

&lt;p&gt;Over time, I've added support for pretty much every type conflict I've encountered in production:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;INT_ENUM&lt;/strong&gt; — The most common one. Field starts as &lt;code&gt;int32&lt;/code&gt;, becomes &lt;code&gt;enum&lt;/code&gt;. You get both &lt;code&gt;getField()&lt;/code&gt; (returns int) and &lt;code&gt;getFieldEnum()&lt;/code&gt; (returns enum).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;ENUM_ENUM&lt;/strong&gt; — Two different enums across versions. Unified as int with enum conversion methods.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;WIDENING&lt;/strong&gt; — &lt;code&gt;int32&lt;/code&gt; grows to &lt;code&gt;int64&lt;/code&gt;. The wrapper uses &lt;code&gt;long&lt;/code&gt; everywhere, with runtime validation for versions that need the narrower type.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;FLOAT_DOUBLE&lt;/strong&gt; — Precision change from &lt;code&gt;float&lt;/code&gt; to &lt;code&gt;double&lt;/code&gt;. Unified as &lt;code&gt;double&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;SIGNED_UNSIGNED&lt;/strong&gt; — &lt;code&gt;int32&lt;/code&gt; vs &lt;code&gt;uint32&lt;/code&gt;, &lt;code&gt;sint32&lt;/code&gt; vs &lt;code&gt;int32&lt;/code&gt;. Unified as &lt;code&gt;long&lt;/code&gt; with proper range validation.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;PRIMITIVE_MESSAGE&lt;/strong&gt; — A simple &lt;code&gt;int64 total&lt;/code&gt; becomes &lt;code&gt;Money total&lt;/code&gt; with amount and currency. You get &lt;code&gt;getTotal()&lt;/code&gt; for primitive versions and &lt;code&gt;getTotalMessage()&lt;/code&gt; for message versions.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;STRING_BYTES&lt;/strong&gt; — Someone decides &lt;code&gt;string&lt;/code&gt; should be &lt;code&gt;bytes&lt;/code&gt;. Dual accessors handle the conversion.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;REPEATED_SINGLE&lt;/strong&gt; — Field changes from singular to repeated or vice versa. Returns &lt;code&gt;List&lt;/code&gt; in all cases.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;FIELD_RENUMBER&lt;/strong&gt; — Sometimes field numbers change between versions (legacy systems, don't ask). You can explicitly map them:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;fieldMappings&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;fieldMapping&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;message&amp;gt;&lt;/span&gt;Order&lt;span class="nt"&gt;&amp;lt;/message&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;fieldName&amp;gt;&lt;/span&gt;parent_ref&lt;span class="nt"&gt;&amp;lt;/fieldName&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;versionNumbers&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;v1&amp;gt;&lt;/span&gt;3&lt;span class="nt"&gt;&amp;lt;/v1&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;v2&amp;gt;&lt;/span&gt;5&lt;span class="nt"&gt;&amp;lt;/v2&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;/versionNumbers&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/fieldMapping&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/fieldMappings&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The diff tool can even detect suspected renumbering and suggest the configuration.&lt;/p&gt;

&lt;h2&gt;
  
  
  Batteries Included
&lt;/h2&gt;

&lt;p&gt;A few things that make life easier:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;No protoc installation required.&lt;/strong&gt; The plugin downloads the right protoc binary for your platform automatically. Just works.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Incremental builds.&lt;/strong&gt; Changed one proto file? Only affected wrappers regenerate. Saves 50%+ build time on large projects.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Well-known types conversion.&lt;/strong&gt; &lt;code&gt;google.protobuf.Timestamp&lt;/code&gt; becomes &lt;code&gt;java.time.Instant&lt;/code&gt;. &lt;code&gt;Duration&lt;/code&gt; becomes &lt;code&gt;java.time.Duration&lt;/code&gt;. No more manual conversions.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Builder pattern.&lt;/strong&gt; Full support for creating and modifying messages:&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="nc"&gt;Payment&lt;/span&gt; &lt;span class="n"&gt;payment&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Payment&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;newBuilder&lt;/span&gt;&lt;span class="o"&gt;(&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="na"&gt;setPaymentType&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;PaymentType&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;CARD&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
    &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;setAmount&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Money&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;newBuilder&lt;/span&gt;&lt;span class="o"&gt;(&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="na"&gt;setValue&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
        &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;setCurrency&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"USD"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
        &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;build&lt;/span&gt;&lt;span class="o"&gt;())&lt;/span&gt;
    &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;build&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Spring Boot Starter.&lt;/strong&gt; For Spring Boot 3+ projects, there's an auto-configuration that handles version context per HTTP request.&lt;/p&gt;

&lt;h2&gt;
  
  
  Schema Diff Tool
&lt;/h2&gt;

&lt;p&gt;Before deploying a new protocol version, you probably want to know what changed. The built-in diff tool helps:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;mvn proto-wrapper:diff &lt;span class="nt"&gt;-Dv1&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;proto/v1 &lt;span class="nt"&gt;-Dv2&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;proto/v2
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It shows added/removed fields, type changes, and flags breaking changes. You can integrate it into CI to catch compatibility issues before they hit production:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;mvn proto-wrapper:diff &lt;span class="nt"&gt;-Dv1&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;proto/production &lt;span class="nt"&gt;-Dv2&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;proto/development &lt;span class="nt"&gt;-DfailOnBreaking&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;true&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Output formats include text, JSON, and Markdown for reports.&lt;/p&gt;

&lt;h2&gt;
  
  
  Version Constants
&lt;/h2&gt;

&lt;p&gt;No more magic strings. The plugin generates a &lt;code&gt;ProtocolVersions&lt;/code&gt; class:&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="c1"&gt;// Compile-time constants&lt;/span&gt;
&lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;version&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;ProtocolVersions&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;V1&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// Runtime validation&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;ProtocolVersions&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;isSupported&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;versionId&lt;/span&gt;&lt;span class="o"&gt;))&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="nc"&gt;VersionContext&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;VersionContext&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;forVersionId&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;versionId&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// Fail fast on unknown versions&lt;/span&gt;
&lt;span class="nc"&gt;ProtocolVersions&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;requireSupported&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;versionId&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  The Generated Code
&lt;/h2&gt;

&lt;p&gt;Here's what the structure looks like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;com/example/model/
├── api/
│   ├── Payment.java           # Interface
│   ├── PaymentType.java       # Unified enum
│   ├── VersionContext.java    # Factory
│   ├── ProtocolVersions.java  # Constants
│   └── impl/
│       └── AbstractPayment.java
├── v1/
│   ├── PaymentV1.java
│   └── VersionContextV1.java
└── v2/
    ├── PaymentV2.java
    └── VersionContextV2.java
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The generated code is plain Java. No runtime dependencies beyond protobuf itself. You can read it, debug it, understand it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Links
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/alnovis/proto-wrapper-plugin" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://central.sonatype.com/artifact/io.alnovis/proto-wrapper-core" rel="noopener noreferrer"&gt;Maven Central&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/alnovis/proto-wrapper-plugin/tree/main/docs" rel="noopener noreferrer"&gt;Full Documentation&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The plugin is open source (Apache 2.0) and actively maintained. If you're dealing with protobuf versioning headaches, give it a try.&lt;/p&gt;

</description>
      <category>java</category>
      <category>protobuf</category>
      <category>grpc</category>
      <category>codegen</category>
    </item>
  </channel>
</rss>
