<?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>Forem: Luis Vasquez</title>
    <description>The latest articles on Forem by Luis Vasquez (@luidmidev).</description>
    <link>https://forem.com/luidmidev</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%2F3919202%2F1dc8cc31-0bdf-4a29-b4fb-de005ef7e0a3.jpeg</url>
      <title>Forem: Luis Vasquez</title>
      <link>https://forem.com/luidmidev</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/luidmidev"/>
    <language>en</language>
    <item>
      <title>Java Generics, Type Erasure, and a conceptual idea: reified generics as a first-class citizen?</title>
      <dc:creator>Luis Vasquez</dc:creator>
      <pubDate>Fri, 08 May 2026 05:35:53 +0000</pubDate>
      <link>https://forem.com/luidmidev/java-generics-type-erasure-and-a-conceptual-idea-reified-generics-as-a-first-class-citizen-1dm6</link>
      <guid>https://forem.com/luidmidev/java-generics-type-erasure-and-a-conceptual-idea-reified-generics-as-a-first-class-citizen-1dm6</guid>
      <description>&lt;p&gt;Java added generics in Java 5, and the team made a call that still echoes today: type erasure. At compile time, &lt;code&gt;List&amp;lt;String&amp;gt;&lt;/code&gt; and &lt;code&gt;List&amp;lt;Integer&amp;gt;&lt;/code&gt; become the same &lt;code&gt;List&lt;/code&gt;. The type parameter disappears. The JVM never sees it.&lt;/p&gt;

&lt;p&gt;At the time, this made sense. There were millions of lines of Java in production. Modifying the JVM to carry type metadata at runtime would have broken binary compatibility across the entire ecosystem. Erasure was the safe path, and probably the right one.&lt;/p&gt;

&lt;p&gt;But that was 2004.&lt;/p&gt;




&lt;h2&gt;
  
  
  The cost compounds
&lt;/h2&gt;

&lt;p&gt;Here's the thing: the modern JVM ecosystem increasingly &lt;em&gt;needs&lt;/em&gt; runtime type information. Jackson, Hibernate, Spring, Quarkus, Micronaut, Kafka serializers, Bean Validation, OpenAPI generators... they all depend on knowing what generic types are at runtime. And since the JVM threw that away, they spend enormous effort reconstructing it artificially.&lt;/p&gt;

&lt;p&gt;The most obvious example is Jackson:&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;List&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;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;mapper&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;readValue&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;json&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;TypeReference&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&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;String&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;()&lt;/span&gt; &lt;span class="o"&gt;{});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That &lt;code&gt;{}&lt;/code&gt; at the end is an anonymous class. You're literally subclassing &lt;code&gt;TypeReference&amp;lt;List&amp;lt;String&amp;gt;&amp;gt;&lt;/code&gt; so the compiler embeds the type in the class definition, which you then dig out via reflection. It works, but it's a workaround for something the runtime should just know. And in a large codebase you'll find hundreds of these.&lt;/p&gt;

&lt;p&gt;Every year the ecosystem gets more sophisticated at reconstructing what erasure discarded. &lt;code&gt;TypeReference&lt;/code&gt;, &lt;code&gt;TypeToken&lt;/code&gt;, &lt;code&gt;ResolvableType&lt;/code&gt;... the gap keeps growing.&lt;/p&gt;




&lt;h2&gt;
  
  
  The idea
&lt;/h2&gt;

&lt;p&gt;So I started thinking: what if Java introduced a new kind of class that keeps its generic type at runtime?&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="n"&gt;reified&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Box&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="no"&gt;T&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="no"&gt;T&lt;/span&gt; &lt;span class="n"&gt;value&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;The key word here is &lt;em&gt;new kind&lt;/em&gt;. The proposal isn't to change how existing generics work. It's to introduce a second system that coexists with the first:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;existing erased generics stay exactly as they are&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;reified&lt;/code&gt; classes carry real type metadata at runtime&lt;/li&gt;
&lt;li&gt;the boundary between both worlds is explicit and compiler-enforced&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;With this, things that are currently awkward become natural:&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;// no more TypeReference&lt;/span&gt;
&lt;span class="nc"&gt;Box&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;&amp;gt;.&lt;/span&gt;&lt;span class="na"&gt;class&lt;/span&gt;

&lt;span class="c1"&gt;// instanceof with real parameterized types&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;box&lt;/span&gt; &lt;span class="k"&gt;instanceof&lt;/span&gt; &lt;span class="nc"&gt;Box&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;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;b&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;// reflection that actually knows the type&lt;/span&gt;
&lt;span class="n"&gt;box&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getClass&lt;/span&gt;&lt;span class="o"&gt;().&lt;/span&gt;&lt;span class="na"&gt;getTypeArguments&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="c1"&gt;// -&amp;gt; [String]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Prior art
&lt;/h2&gt;

&lt;p&gt;This isn't a new problem, and other languages have tackled pieces of it.&lt;/p&gt;

&lt;p&gt;Kotlin has &lt;code&gt;inline fun &amp;lt;reified T&amp;gt;&lt;/code&gt;, which lets you use the type parameter at runtime inside a function. The compiler copies the function body at every call site and substitutes the concrete type there, similar to how Rust handles monomorphization. It works well, but only for functions:&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="k"&gt;inline&lt;/span&gt; &lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="k"&gt;reified&lt;/span&gt; &lt;span class="nc"&gt;T&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;isInstance&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Any&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt; &lt;span class="k"&gt;is&lt;/span&gt; &lt;span class="nc"&gt;T&lt;/span&gt;  &lt;span class="c1"&gt;// works&lt;/span&gt;

&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Box&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;T&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;getType&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;T&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="k"&gt;class&lt;/span&gt;  &lt;span class="c1"&gt;// doesn't compile&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For classes with generic state, Kotlin hits the same wall as Java. The compiler trick doesn't scale there.&lt;/p&gt;

&lt;p&gt;C# solved this properly, but they had the luxury of designing the CLR with reification from the start. &lt;code&gt;List&amp;lt;string&amp;gt;&lt;/code&gt; and &lt;code&gt;List&amp;lt;int&amp;gt;&lt;/code&gt; are genuinely different types at runtime. They also added explicit variance on interfaces with &lt;code&gt;in&lt;/code&gt; and &lt;code&gt;out&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;interface&lt;/span&gt; &lt;span class="nc"&gt;IProducer&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="k"&gt;out&lt;/span&gt; &lt;span class="n"&gt;T&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;T&lt;/span&gt; &lt;span class="nf"&gt;Get&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;      &lt;span class="c1"&gt;// covariant, T only comes out&lt;/span&gt;
&lt;span class="k"&gt;interface&lt;/span&gt; &lt;span class="nc"&gt;IConsumer&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="n"&gt;T&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;  &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;Use&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;T&lt;/span&gt; &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="c1"&gt;// contravariant, T only goes in&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Java's wildcards (&lt;code&gt;? extends&lt;/code&gt;, &lt;code&gt;? super&lt;/code&gt;) cover similar ground, but you declare the variance at every usage site instead of once on the interface. The PECS rule (Producer Extends, Consumer Super) exists precisely because people keep forgetting which is which.&lt;/p&gt;

&lt;p&gt;Project Valhalla is also working in this space, focused on specialized generics for value types. It's not exactly the same problem, but it overlaps.&lt;/p&gt;




&lt;h2&gt;
  
  
  Where things get complicated
&lt;/h2&gt;

&lt;p&gt;Two type systems living in the same language creates real tension.&lt;/p&gt;

&lt;p&gt;The most immediate issue is the boundary. A &lt;code&gt;reified&lt;/code&gt; class can always degrade to a raw type if you need to cross into the erased world, but the reverse doesn't work. You can't reconstruct type information that was never stored. This makes the boundary &lt;strong&gt;unidirectional&lt;/strong&gt;: reified -&amp;gt; erased is allowed (explicitly), erased -&amp;gt; reified is not.&lt;/p&gt;

&lt;p&gt;That also makes cross-world inheritance essentially unviable:&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;// almost certainly can't be allowed&lt;/span&gt;
&lt;span class="n"&gt;reified&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ReifiedList&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="no"&gt;T&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;ArrayList&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="no"&gt;T&lt;/span&gt;&lt;span class="o"&gt;&amp;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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;ArrayList&lt;/code&gt; is erased. Inheriting from it means crossing the boundary inside the class definition, which is the worst place for that to happen implicitly. The cleaner model is two separate inheritance hierarchies that only touch through an explicit, controlled conversion.&lt;/p&gt;

&lt;p&gt;Class loading is another hard problem. The JVM loads classes, not parameterized instantiations. &lt;code&gt;Box&amp;lt;String&amp;gt;&lt;/code&gt; and &lt;code&gt;Box&amp;lt;Integer&amp;gt;&lt;/code&gt; being distinct types at runtime means either a different classloading model or a metadata layer outside the traditional class system. C# did this from scratch. Retrofitting it onto 30 years of JVM infrastructure is a different problem entirely.&lt;/p&gt;

&lt;p&gt;Reflection also breaks. &lt;code&gt;java.lang.Class&lt;/code&gt; and &lt;code&gt;java.lang.reflect.*&lt;/code&gt; have no concept of real parameterized types. Parts of the core API would need to change.&lt;/p&gt;

&lt;p&gt;And bridge methods, generated by the compiler to handle erasure in inheritance, assume a single erased version of each generic class. With partial reification, the rules change in ways that aren't trivial.&lt;/p&gt;




&lt;h2&gt;
  
  
  Open questions
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Does a second type system make more sense than evolving the existing one?&lt;/li&gt;
&lt;li&gt;How do you define the exact rules for crossing the boundary in both directions?&lt;/li&gt;
&lt;li&gt;Is the dual-model complexity worth it, or is a more conservative approach inside the current system better?&lt;/li&gt;
&lt;li&gt;What other implementation problems am I not seeing?&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Wrapping up
&lt;/h2&gt;

&lt;p&gt;Java made the right call in 2004. Backward compatibility for a massive ecosystem is not a trivial concern, and type erasure let generics ship without breaking everything that existed.&lt;/p&gt;

&lt;p&gt;But technical debt compounds. The workarounds are getting more sophisticated every year, and the frameworks that power most of the JVM ecosystem are increasingly built around reconstructing what the runtime threw away.&lt;/p&gt;

&lt;p&gt;This post is a mental exercise in what an opt-in, backward-compatible path out of that debt could look like. I'm not a language designer, and I'm sure there are implementation problems I haven't thought of. That's kind of the point of writing it down.&lt;/p&gt;

</description>
      <category>java</category>
      <category>jdk</category>
      <category>generics</category>
      <category>programming</category>
    </item>
  </channel>
</rss>
