[{"data":1,"prerenderedAt":129},["ShallowReactive",2],{"qa-\u002Fjava\u002Fgenerics\u002Ftype-erasure":3},{"page":4,"siblings":117,"blog":126},{"id":5,"title":6,"body":7,"description":11,"difficulty":14,"extension":15,"framework":16,"frameworkSlug":17,"meta":18,"navigation":19,"order":20,"path":21,"questions":22,"questionsCount":108,"related":109,"seo":110,"seoDescription":111,"stem":112,"subtopic":6,"topic":113,"topicSlug":114,"updated":115,"__hash__":116},"qa\u002Fjava\u002Fgenerics\u002Ftype-erasure.md","Type Erasure",{"type":8,"value":9,"toc":10},"minimark",[],{"title":11,"searchDepth":12,"depth":12,"links":13},"",2,[],"hard","md","Java","java",{},true,3,"\u002Fjava\u002Fgenerics\u002Ftype-erasure",[23,28,32,36,40,44,48,52,56,60,64,68,72,76,80,84,88,92,96,100,104],{"id":24,"difficulty":25,"q":26,"a":27},"what-is-type-erasure","medium","What is type erasure in Java generics?","**Type erasure** is the process by which the compiler **removes all generic\ntype information** during compilation. Generics exist only at **compile time**\nfor type checking; at runtime the type parameters are gone, so a\n`List\u003CString>` and a `List\u003CInteger>` are both just plain `List`.\n\n```java\nList\u003CString> a = new ArrayList\u003C>();\nList\u003CInteger> b = new ArrayList\u003C>();\nSystem.out.println(a.getClass() == b.getClass()); \u002F\u002F true — both ArrayList\n```\n\nThe compiler replaces type parameters with their **bound** (or `Object` if\nunbounded) and inserts casts where needed. The result is bytecode that looks\nessentially like pre-generics Java. **Rule of thumb:** generics are a\ncompile-time fiction — nothing about the type argument survives to runtime.\n",{"id":29,"difficulty":25,"q":30,"a":31},"why-type-erasure","Why did Java implement generics using type erasure?","The driving reason was **backward compatibility**. Generics arrived in Java 5,\nlong after millions of lines of pre-generics code existed. Erasure let\ngeneric code and legacy raw-type code **interoperate** on the same JVM without\nchanging the bytecode format or breaking existing libraries.\n\n```java\nList\u003CString> generic = new ArrayList\u003C>();\nList raw = generic;          \u002F\u002F legacy raw type — still works\nraw.add(\"ok\");               \u002F\u002F unchecked, but compiles and runs\n```\n\nA pre-generics `.class` file and a generics-aware one produce **compatible**\nbytecode, and a `List` from old code can flow into new generic code.\n**Rule of thumb:** erasure was the price of adding generics without forking\nthe platform — runtime reification (like C#) would have broken the existing\necosystem.\n",{"id":33,"difficulty":14,"q":34,"a":35},"how-compiler-erases","How exactly does the compiler erase generic types?","The compiler applies three transformations: it **replaces type parameters**\nwith their leftmost bound (or `Object` if unbounded), **inserts casts** at\nevery site where erased values are read out, and **generates bridge methods**\nto preserve polymorphism. The runtime never sees `T`.\n\n```java\n\u002F\u002F You write:\nclass Box\u003CT> { T value; T get() { return value; } }\nString s = new Box\u003CString>().get();\n\n\u002F\u002F After erasure (conceptually):\nclass Box { Object value; Object get() { return value; } }\nString s = (String) new Box().get();   \u002F\u002F compiler inserts the cast\n```\n\nA bounded `\u003CT extends Number>` erases to `Number`, not `Object`.\n**Rule of thumb:** type parameter -> bound, plus compiler-inserted casts — the\ncasts are guaranteed safe because the compiler already type-checked the code.\n",{"id":37,"difficulty":25,"q":38,"a":39},"erasure-to-bound","What does a bounded type parameter erase to?","A type parameter erases to its **leftmost (first) bound**, not always `Object`.\nAn unbounded `\u003CT>` erases to `Object`, but `\u003CT extends Number>` erases to\n`Number`, which lets the compiler call `Number` methods directly on `T`.\n\n```java\n\u003CT extends Number> double sum(T a, T b) {\n  return a.doubleValue() + b.doubleValue(); \u002F\u002F legal — T erases to Number\n}\n\u002F\u002F erased signature: double sum(Number a, Number b)\n\n\u003CT extends Comparable\u003CT> & Serializable> T pick(T x) { return x; }\n\u002F\u002F erases to Comparable (the FIRST bound)\n```\n\nWith multiple bounds (`A & B`), only the **first** drives erasure; that's why\nyou put the class or most-used interface first. **Rule of thumb:** the erased\ntype is the first bound — choose bound order so the useful methods survive.\n",{"id":41,"difficulty":14,"q":42,"a":43},"reifiable-vs-non-reifiable","What is the difference between reifiable and non-reifiable types?","A **reifiable** type is one whose type information is **fully available at\nruntime** — its representation is complete after erasure. A **non-reifiable**\ntype loses information to erasure, so the JVM can't fully reconstruct it.\n\n| Reifiable (survives) | Non-reifiable (erased) |\n| -------------------- | ---------------------- |\n| `String`, `Integer` | `List\u003CString>` |\n| `List`, `Map` (raw) | `List\u003C? extends Number>` |\n| `List\u003C?>` (unbounded wildcard) | `T` (type parameter) |\n| `int[]`, `String[]` | `List\u003CString>[]` |\n\n```java\no instanceof List\u003C?>      \u002F\u002F legal — unbounded wildcard is reifiable\no instanceof List\u003CString> \u002F\u002F ERROR — non-reifiable, info was erased\n```\n\n**Rule of thumb:** if a type still carries a generic argument other than `?`,\nit's non-reifiable and you can't use it with `instanceof`, array creation, or\n`.class`.\n",{"id":45,"difficulty":25,"q":46,"a":47},"no-class-literal","Why is List\u003CString>.class not allowed?","Because there is only **one `Class` object** for `List`, shared by every\nparameterization. After erasure `List\u003CString>`, `List\u003CInteger>`, and raw\n`List` are the same class, so a `List\u003CString>.class` literal would be\nmeaningless — there's nothing distinct to point to.\n\n```java\nClass\u003C?> c = List.class;          \u002F\u002F legal — the one List class\nClass\u003C?> d = List\u003CString>.class;  \u002F\u002F compile error\nList\u003CString> a = new ArrayList\u003C>();\nList\u003CInteger> b = new ArrayList\u003C>();\nSystem.out.println(a.getClass() == b.getClass()); \u002F\u002F true\n```\n\n`getClass()` on any generic instance returns the **raw** class object for the\nsame reason. **Rule of thumb:** there's one `Class` per erased type, so a\nparameterized class literal can't exist.\n",{"id":49,"difficulty":14,"q":50,"a":51},"cannot-instantiate-t","Why can't you write new T() inside a generic class?","Because at runtime `T` has been erased — the JVM has **no idea which class** to\ninstantiate, and it can't guarantee `T` even has a no-arg constructor. The\n`new` bytecode needs a concrete class, which erasure has removed.\n\n```java\nclass Factory\u003CT> {\n  T make() { return new T(); }   \u002F\u002F compile error — cannot instantiate T\n}\n\n\u002F\u002F Workaround: pass a Class token or a supplier\nclass Factory\u003CT> {\n  T make(Class\u003CT> type) throws Exception {\n    return type.getDeclaredConstructor().newInstance(); \u002F\u002F reflection\n  }\n  T make(Supplier\u003CT> s) { return s.get(); }             \u002F\u002F cleaner\n}\n```\n\n**Rule of thumb:** to create instances of a type parameter, pass in a\n`Class\u003CT>` token or a `Supplier\u003CT>` — the type must come from somewhere\nreifiable.\n",{"id":53,"difficulty":14,"q":54,"a":55},"cannot-create-generic-array","Why can't you create a generic array like new T[] or new List\u003CString>[]?","Arrays are **reified** — they remember and **enforce their element type at\nruntime** (storing a wrong type throws `ArrayStoreException`). Generics are\n**erased**. The two models are incompatible: a generic array couldn't perform\nits runtime store-check because the element type was erased.\n\n```java\nT[] a = new T[10];               \u002F\u002F compile error — generic array creation\nList\u003CString>[] b = new List\u003CString>[10]; \u002F\u002F compile error\n\n\u002F\u002F Workarounds:\nList\u003CString>[] c = (List\u003CString>[]) new List[10]; \u002F\u002F unchecked cast\nT[] d = (T[]) Array.newInstance(componentType, 10); \u002F\u002F reflection + Class token\n```\n\n**Rule of thumb:** you can't directly create arrays of a non-reifiable type;\nuse a `List\u003CList\u003CString>>` or an unchecked cast from a raw array.\n",{"id":57,"difficulty":14,"q":58,"a":59},"heap-pollution","What is heap pollution and how do generic arrays cause it?","**Heap pollution** occurs when a variable of a parameterized type points to an\nobject that is **not** of that type — usually because erasure let an\nincompatible value slip in. Generic arrays are banned precisely because they'd\nmake this trivially easy and undetectable.\n\n```java\nList\u003CString>[] arr = (List\u003CString>[]) new List[1]; \u002F\u002F unchecked\nObject[] objs = arr;                  \u002F\u002F arrays are covariant\nobjs[0] = List.of(42);                \u002F\u002F stores List\u003CInteger> — no error!\nString s = arr[0].get(0);             \u002F\u002F ClassCastException at READ time\n```\n\nThe corruption is silent until a later read fails far from the cause.\n**Rule of thumb:** heap pollution = a generic variable holding the wrong\nelement type; it surfaces as a surprise `ClassCastException` from\ncompiler-inserted casts.\n",{"id":61,"difficulty":25,"q":62,"a":63},"instanceof-generics","Why can't you use instanceof with a parameterized type?","`instanceof` is a **runtime** check, but the generic type argument was\n**erased**, so there's nothing to test against. The JVM could only tell you\nit's a `List`, never a `List\u003CString>`. Only **reifiable** types (raw types or\nthe unbounded wildcard) are allowed.\n\n```java\nif (obj instanceof List\u003CString>) { } \u002F\u002F compile error\nif (obj instanceof List\u003C?>) { }       \u002F\u002F OK — unbounded wildcard is reifiable\nif (obj instanceof List) { }          \u002F\u002F OK — raw type\n\n@SuppressWarnings(\"unchecked\")\nList\u003CString> l = (List\u003CString>) obj;  \u002F\u002F cast compiles but is unchecked\n```\n\n**Rule of thumb:** check against `List\u003C?>` or the raw `List`; you can never\n`instanceof` a specific type argument because it doesn't exist at runtime.\n",{"id":65,"difficulty":14,"q":66,"a":67},"bridge-methods","What are bridge methods and why does the compiler generate them?","A **bridge method** is a synthetic method the compiler generates to **preserve\npolymorphism** after erasure. When a generic supertype's method erases to a\ndifferent signature than the subtype's override, the compiler adds a bridge so\ndynamic dispatch still finds the real method.\n\n```java\nclass Node\u003CT> { void set(T t) { } }            \u002F\u002F erases to set(Object)\nclass StringNode extends Node\u003CString> {\n  @Override void set(String s) { }             \u002F\u002F set(String)\n}\n\u002F\u002F Compiler adds a synthetic bridge in StringNode:\n\u002F\u002F void set(Object o) { set((String) o); }      \u002F\u002F forwards to the real method\n```\n\nWithout the bridge, `Node\u003CString> n = new StringNode(); n.set(\"x\")` would call\nthe inherited `set(Object)` and skip the override. **Rule of thumb:** bridge\nmethods are invisible glue that keep overriding working across erased generic\nsignatures.\n",{"id":69,"difficulty":25,"q":70,"a":71},"bridge-method-visibility","How can you tell a bridge method apart from a real one?","Bridge methods are marked **synthetic** and carry the **`ACC_BRIDGE`** flag in\nthe bytecode. You can detect them at runtime via reflection with\n`Method.isBridge()` and `Method.isSynthetic()`. They never appear in your\nsource and you shouldn't call them directly.\n\n```java\nfor (Method m : StringNode.class.getDeclaredMethods()) {\n  System.out.println(m + \" bridge=\" + m.isBridge());\n}\n\u002F\u002F set(String) bridge=false\n\u002F\u002F set(Object) bridge=true   \u003C- the synthetic bridge\n```\n\nThey're why reflective scans sometimes find \"duplicate\" methods that differ\nonly by parameter type. **Rule of thumb:** filter out `isBridge()`\u002F\n`isSynthetic()` methods when introspecting generic classes.\n",{"id":73,"difficulty":14,"q":74,"a":75},"overload-clash","Why won't two methods that differ only by generic type parameter compile?","Because after erasure they have the **same signature**, and a class can't have\ntwo methods with identical signatures. The compiler reports a **\"name clash\"**\neven though the source-level parameter types look different.\n\n```java\nclass Printer {\n  void print(List\u003CString> s) { }   \u002F\u002F erases to print(List)\n  void print(List\u003CInteger> i) { }  \u002F\u002F also erases to print(List) — CLASH!\n}\n\u002F\u002F error: name clash: both methods have the same erasure\n```\n\nThe fix is to give them genuinely different erased signatures (different raw\ntypes or arity) or rename one. **Rule of thumb:** overloads must differ after\nerasure — distinct type arguments alone are not enough.\n",{"id":77,"difficulty":14,"q":78,"a":79},"class-token-pattern","What is the Class\u003CT> type token pattern and why is it useful?","A **type token** is a `Class\u003CT>` object passed in to **recover the type\ninformation** that erasure removed. Since the type argument is gone at runtime,\nhanding the method a `Class\u003CT>` lets it instantiate, cast, or check types\nsafely.\n\n```java\n\u003CT> T fromJson(String json, Class\u003CT> type) {\n  Object parsed = parse(json);\n  return type.cast(parsed);          \u002F\u002F typed cast via the token\n}\nUser u = fromJson(s, User.class);     \u002F\u002F T inferred from the token\n\n\u003CT> T create(Class\u003CT> type) throws Exception {\n  return type.getDeclaredConstructor().newInstance();\n}\n```\n\nFrameworks like Jackson and Spring rely on this everywhere.\n**Rule of thumb:** when you need the runtime type of `T`, pass a `Class\u003CT>`\ntoken — it's the standard escape hatch from erasure.\n",{"id":81,"difficulty":14,"q":82,"a":83},"super-type-token","How do super type tokens (TypeReference) capture generic types at runtime?","A plain `Class\u003CT>` token can't represent a **parameterized** type like\n`List\u003CString>` (no such class literal). The **super type token** trick (used by\nJackson's `TypeReference`, Guava's `TypeToken`) captures it by subclassing an\nabstract generic class and reading the type argument via reflection from the\n**generic superclass**, which the compiler *does* record.\n\n```java\n\u002F\u002F The anonymous subclass embeds List\u003CString> in its signature:\nTypeReference\u003CList\u003CString>> ref = new TypeReference\u003CList\u003CString>>() {};\n\u002F\u002F Internally:\nType t = getClass().getGenericSuperclass();           \u002F\u002F TypeReference\u003CList\u003CString>>\nType arg = ((ParameterizedType) t).getActualTypeArguments()[0]; \u002F\u002F List\u003CString>\n```\n\nGeneric type info on **class\u002Fmethod signatures** survives erasure (in metadata),\neven though instance-level arguments don't. **Rule of thumb:** subclassing a\ngeneric type pins its arguments in the class signature, which reflection can\nread back.\n",{"id":85,"difficulty":25,"q":86,"a":87},"static-field-no-type-param","Why can't a static field or static context use the class type parameter?","A type parameter belongs to a **specific instance's** parameterization, but a\n**static** member is shared across **all** parameterizations — and after\nerasure there's only one class anyway. There's no single `T` for the static\ncontext to refer to, so it's forbidden.\n\n```java\nclass Box\u003CT> {\n  static T shared;              \u002F\u002F compile error — T in static context\n  static void set(T t) { }      \u002F\u002F compile error\n  static \u003CU> U pick(U u) { return u; } \u002F\u002F OK — its OWN type parameter\n}\n```\n\nA static **method** may declare its **own** type parameter, which is fine.\n**Rule of thumb:** the class's `T` is per-instance; statics are class-wide, so\nthey can't use it — give a static method its own type parameter instead.\n",{"id":89,"difficulty":14,"q":90,"a":91},"generic-exceptions","What are the restrictions on generics with exceptions?","A generic class **cannot extend `Throwable`**, and you **cannot catch a type\nparameter**, because `catch` is a runtime dispatch on the exception's actual\nclass — which erasure has removed. You *can* declare a method that **throws**\na type parameter bounded by `Throwable`.\n\n```java\nclass MyException\u003CT> extends Exception { }  \u002F\u002F compile error — generic Throwable\n\n\u003CT extends Throwable> void run() {\n  try { \u002F* ... *\u002F }\n  catch (T e) { }              \u002F\u002F compile error — cannot catch type parameter\n}\n\n\u003CT extends Throwable> void rethrow(T t) throws T { throw t; } \u002F\u002F OK\n```\n\nIf catch were allowed, two `catch (T)` clauses could erase to the same\n`catch (Exception)`. **Rule of thumb:** you can throw but never catch a type\nparameter, and exception classes can't be generic.\n",{"id":93,"difficulty":25,"q":94,"a":95},"runtime-type-info-survives","What generic type information actually survives erasure?","Instance type arguments vanish, but generic info embedded in **declarations** is\nkept in the class file's signature metadata and is readable via reflection\n(`java.lang.reflect.Type`). This includes field types, method\nparameter\u002Freturn types, type variable bounds, and the **generic superclass \u002F\ninterfaces**.\n\n```java\nclass Repo { List\u003CString> names; }\nField f = Repo.class.getDeclaredField(\"names\");\nType t = f.getGenericType();                 \u002F\u002F List\u003CString> — preserved!\nParameterizedType pt = (ParameterizedType) t;\nSystem.out.println(pt.getActualTypeArguments()[0]); \u002F\u002F class java.lang.String\n```\n\nWhat's lost is the argument of a **runtime object** (`new ArrayList\u003CString>()`\nknows nothing about `String`). **Rule of thumb:** declaration-site generics\nlive in metadata; use-site instance generics do not.\n",{"id":97,"difficulty":25,"q":98,"a":99},"safevarargs","What does @SafeVarargs do and how does it relate to erasure?","A **varargs parameter of a generic type** creates a generic array under the\nhood (`T...` becomes `T[]`), which triggers an unavoidable **unchecked \u002F\n\"possible heap pollution\"** warning. `@SafeVarargs` is your assertion that the\nmethod **only reads** from the varargs and never pollutes the array, which\nsuppresses the warning at the declaration and all call sites.\n\n```java\n@SafeVarargs\nstatic \u003CT> List\u003CT> listOf(T... items) {   \u002F\u002F T[] is a non-reifiable array\n  return new ArrayList\u003C>(Arrays.asList(items)); \u002F\u002F safe — read only\n}\n```\n\nIt's applicable only to methods that **can't be overridden** (`static`,\n`final`, or `private`). **Rule of thumb:** use `@SafeVarargs` when your\ngeneric varargs method is read-only and provably safe from heap pollution.\n",{"id":101,"difficulty":25,"q":102,"a":103},"unchecked-warnings","What is an unchecked warning and why does erasure cause it?","An **unchecked warning** means the compiler **can't verify the type safety** of\nan operation because the needed type information was erased — typically a cast\nto a parameterized type or use of a raw type. The code compiles, but the\ncompiler is telling you it can't guarantee the runtime cast won't fail.\n\n```java\nObject o = new ArrayList\u003CString>();\nList\u003CString> list = (List\u003CString>) o; \u002F\u002F unchecked — only List is verifiable\n\nList raw = new ArrayList();\nraw.add(\"x\");                          \u002F\u002F unchecked call to add(E)\n```\n\nSuppress only when you've manually proven safety, with the **narrowest scope**\nand a comment, via `@SuppressWarnings(\"unchecked\")`. **Rule of thumb:** an\nunchecked warning is erasure admitting it can't check you — treat each one as a\npotential `ClassCastException` to justify.\n",{"id":105,"difficulty":14,"q":106,"a":107},"erasure-vs-reification","How does Java's erasure differ from reified generics in languages like C#?","With **reified generics** (C#\u002F.NET), the type argument is **preserved at\nruntime**: `List\u003Cint>` and `List\u003Cstring>` are distinct types, you can call\n`typeof(T)`, do `new T()`, and use `is List\u003Cstring>`. Java's **erased**\ngenerics throw all of that away after compilation.\n\n```java\n\u002F\u002F Java — none of these work:\nT.class;  new T();  new T[10];  obj instanceof List\u003CString>;\n\n\u002F\u002F C# equivalents all work because T is reified at runtime.\n```\n\nReification costs a specialized type per parameterization and breaks legacy\ninterop; erasure is cheaper and compatible but loses runtime type identity.\n**Rule of thumb:** Java traded runtime type knowledge for backward\ncompatibility — every generics restriction you hit traces back to that choice.\n",21,null,{"description":11},"Java type erasure interview questions — how the compiler erases generics, bridge methods, reifiable vs non-reifiable types, generics and arrays, restrictions on generics, and runtime type information.","java\u002Fgenerics\u002Ftype-erasure","Generics","generics","2026-06-20","Rh7iwMUxEBeSRusc_Z0L9khTTqt-GzkevYFbBvte82E",[118,122,125],{"subtopic":119,"path":120,"order":121},"Generics Basics","\u002Fjava\u002Fgenerics\u002Fgenerics-basics",1,{"subtopic":123,"path":124,"order":12},"Wildcards & Bounded Types","\u002Fjava\u002Fgenerics\u002Fwildcards-bounds",{"subtopic":6,"path":21,"order":20},{"path":127,"title":128},"\u002Fblog\u002Fjava-generics-type-erasure","Java Type Erasure Explained — How Generics Vanish at Runtime",1782244116119]