[{"data":1,"prerenderedAt":1715},["ShallowReactive",2],{"blog-\u002Fblog\u002Fjava-instanceof-pattern-matching":3},{"id":4,"title":5,"body":6,"description":1701,"difficulty":1702,"extension":1703,"framework":1704,"frameworkSlug":23,"meta":1705,"navigation":53,"order":87,"path":1706,"qaPath":1707,"seo":1708,"stem":1709,"subtopic":1710,"topic":1711,"topicSlug":1712,"updated":1713,"__hash__":1714},"blog\u002Fblog\u002Fjava-instanceof-pattern-matching.md","Java instanceof Pattern Matching — Type Tests Without the Redundant Cast",{"type":7,"value":8,"toc":1685},"minimark",[9,14,18,109,141,154,190,197,201,208,320,325,332,394,398,409,444,448,455,537,548,721,725,737,822,832,836,846,1097,1100,1104,1107,1154,1160,1164,1175,1269,1272,1276,1279,1394,1398,1405,1451,1550,1559,1563,1569,1640,1643,1647,1681],[10,11,13],"h2",{"id":12},"the-redundant-cast-problem","The redundant-cast problem",[15,16,17],"p",{},"Before Java 16, checking a type and then using the value always required two steps:",[19,20,25],"pre",{"className":21,"code":22,"language":23,"meta":24,"style":24},"language-java shiki shiki-themes github-light github-dark","Object obj = getObject();\n\nif (obj instanceof String) {\n    String s = (String) obj;   \u002F\u002F we already know it's a String — why cast?\n    System.out.println(s.toUpperCase());\n}\n","java","",[26,27,28,48,55,70,85,103],"code",{"__ignoreMap":24},[29,30,33,37,41,45],"span",{"class":31,"line":32},"line",1,[29,34,36],{"class":35},"sVt8B","Object obj ",[29,38,40],{"class":39},"szBVR","=",[29,42,44],{"class":43},"sScJk"," getObject",[29,46,47],{"class":35},"();\n",[29,49,51],{"class":31,"line":50},2,[29,52,54],{"emptyLinePlaceholder":53},true,"\n",[29,56,58,61,64,67],{"class":31,"line":57},3,[29,59,60],{"class":39},"if",[29,62,63],{"class":35}," (obj ",[29,65,66],{"class":39},"instanceof",[29,68,69],{"class":35}," String) {\n",[29,71,73,76,78,81],{"class":31,"line":72},4,[29,74,75],{"class":35},"    String s ",[29,77,40],{"class":39},[29,79,80],{"class":35}," (String) obj;   ",[29,82,84],{"class":83},"sJ8bj","\u002F\u002F we already know it's a String — why cast?\n",[29,86,88,91,94,97,100],{"class":31,"line":87},5,[29,89,90],{"class":35},"    System.out.",[29,92,93],{"class":43},"println",[29,95,96],{"class":35},"(s.",[29,98,99],{"class":43},"toUpperCase",[29,101,102],{"class":35},"());\n",[29,104,106],{"class":31,"line":105},6,[29,107,108],{"class":35},"}\n",[15,110,111,112,115,116,119,120,123,124,127,128,131,132,136,137,140],{},"The cast ",[26,113,114],{},"(String) obj"," cannot fail here — we just proved ",[26,117,118],{},"obj"," is a ",[26,121,122],{},"String",". It's pure\nboilerplate, and it violates DRY: the type is stated twice (",[26,125,126],{},"instanceof String"," and\n",[26,129,130],{},"(String)","). A typo could cast to a ",[133,134,135],"em",{},"different"," type than was tested, producing a runtime\n",[26,138,139],{},"ClassCastException"," instead of a compile error.",[15,142,143,149,150,153],{},[144,145,146,147],"strong",{},"Pattern matching for ",[26,148,66],{}," (finalized in ",[144,151,152],{},"Java 16",", JEP 394) solves this:",[19,155,157],{"className":21,"code":156,"language":23,"meta":24,"style":24},"if (obj instanceof String s) {\n    System.out.println(s.toUpperCase()); \u002F\u002F s is bound — no cast needed\n}\n",[26,158,159,170,186],{"__ignoreMap":24},[29,160,161,163,165,167],{"class":31,"line":32},[29,162,60],{"class":39},[29,164,63],{"class":35},[29,166,66],{"class":39},[29,168,169],{"class":35}," String s) {\n",[29,171,172,174,176,178,180,183],{"class":31,"line":50},[29,173,90],{"class":35},[29,175,93],{"class":43},[29,177,96],{"class":35},[29,179,99],{"class":43},[29,181,182],{"class":35},"()); ",[29,184,185],{"class":83},"\u002F\u002F s is bound — no cast needed\n",[29,187,188],{"class":31,"line":57},[29,189,108],{"class":35},[15,191,192,193,196],{},"One expression: test the type, bind the result. The variable ",[26,194,195],{},"s"," is typed, non-null, and\nin scope exactly where the test is known to be true.",[10,198,200],{"id":199},"flow-sensitive-typing-and-scope-rules","Flow-sensitive typing and scope rules",[15,202,203,204,207],{},"The binding variable is in scope only where the compiler can ",[144,205,206],{},"prove"," the pattern has\nmatched — a property called flow-sensitive typing or definite assignment:",[19,209,211],{"className":21,"code":210,"language":23,"meta":24,"style":24},"Object obj = getObject();\n\nif (obj instanceof String s) {\n    \u002F\u002F s is in scope here — pattern matched\n    System.out.println(s.length());\n}\n\u002F\u002F s is NOT in scope here\n\n\u002F\u002F In the else branch, the pattern is known to be false:\nif (obj instanceof String s) {\n    System.out.println(s);\n} else {\n    \u002F\u002F s is NOT in scope — the test failed\n}\n",[26,212,213,223,227,237,242,255,259,265,270,276,287,297,309,315],{"__ignoreMap":24},[29,214,215,217,219,221],{"class":31,"line":32},[29,216,36],{"class":35},[29,218,40],{"class":39},[29,220,44],{"class":43},[29,222,47],{"class":35},[29,224,225],{"class":31,"line":50},[29,226,54],{"emptyLinePlaceholder":53},[29,228,229,231,233,235],{"class":31,"line":57},[29,230,60],{"class":39},[29,232,63],{"class":35},[29,234,66],{"class":39},[29,236,169],{"class":35},[29,238,239],{"class":31,"line":72},[29,240,241],{"class":83},"    \u002F\u002F s is in scope here — pattern matched\n",[29,243,244,246,248,250,253],{"class":31,"line":87},[29,245,90],{"class":35},[29,247,93],{"class":43},[29,249,96],{"class":35},[29,251,252],{"class":43},"length",[29,254,102],{"class":35},[29,256,257],{"class":31,"line":105},[29,258,108],{"class":35},[29,260,262],{"class":31,"line":261},7,[29,263,264],{"class":83},"\u002F\u002F s is NOT in scope here\n",[29,266,268],{"class":31,"line":267},8,[29,269,54],{"emptyLinePlaceholder":53},[29,271,273],{"class":31,"line":272},9,[29,274,275],{"class":83},"\u002F\u002F In the else branch, the pattern is known to be false:\n",[29,277,279,281,283,285],{"class":31,"line":278},10,[29,280,60],{"class":39},[29,282,63],{"class":35},[29,284,66],{"class":39},[29,286,169],{"class":35},[29,288,290,292,294],{"class":31,"line":289},11,[29,291,90],{"class":35},[29,293,93],{"class":43},[29,295,296],{"class":35},"(s);\n",[29,298,300,303,306],{"class":31,"line":299},12,[29,301,302],{"class":35},"} ",[29,304,305],{"class":39},"else",[29,307,308],{"class":35}," {\n",[29,310,312],{"class":31,"line":311},13,[29,313,314],{"class":83},"    \u002F\u002F s is NOT in scope — the test failed\n",[29,316,318],{"class":31,"line":317},14,[29,319,108],{"class":35},[321,322,324],"h3",{"id":323},"chains","&& chains",[15,326,327,328,331],{},"The binding variable is in scope throughout an ",[26,329,330],{},"&&"," chain because the left side must be\ntrue before the right side is evaluated:",[19,333,335],{"className":21,"code":334,"language":23,"meta":24,"style":24},"if (obj instanceof String s && s.length() > 5) {\n    System.out.println(\"long: \" + s); \u002F\u002F s available throughout the && chain\n}\n",[26,336,337,368,390],{"__ignoreMap":24},[29,338,339,341,343,345,348,350,353,355,358,361,365],{"class":31,"line":32},[29,340,60],{"class":39},[29,342,63],{"class":35},[29,344,66],{"class":39},[29,346,347],{"class":35}," String s ",[29,349,330],{"class":39},[29,351,352],{"class":35}," s.",[29,354,252],{"class":43},[29,356,357],{"class":35},"() ",[29,359,360],{"class":39},">",[29,362,364],{"class":363},"sj4cs"," 5",[29,366,367],{"class":35},") {\n",[29,369,370,372,374,377,381,384,387],{"class":31,"line":50},[29,371,90],{"class":35},[29,373,93],{"class":43},[29,375,376],{"class":35},"(",[29,378,380],{"class":379},"sZZnC","\"long: \"",[29,382,383],{"class":39}," +",[29,385,386],{"class":35}," s); ",[29,388,389],{"class":83},"\u002F\u002F s available throughout the && chain\n",[29,391,392],{"class":31,"line":57},[29,393,108],{"class":35},[321,395,397],{"id":396},"chains-1","|| chains",[15,399,400,401,404,405,408],{},"The compiler cannot prove the type in an ",[26,402,403],{},"||"," chain, so the binding variable is ",[144,406,407],{},"not","\nin scope:",[19,410,412],{"className":21,"code":411,"language":23,"meta":24,"style":24},"\u002F\u002F Compile error:\nif (obj instanceof String s || s.isEmpty()) { ... }\n\u002F\u002F                              ^ s not in scope — test might not have matched\n",[26,413,414,419,439],{"__ignoreMap":24},[29,415,416],{"class":31,"line":32},[29,417,418],{"class":83},"\u002F\u002F Compile error:\n",[29,420,421,423,425,427,429,431,433,436],{"class":31,"line":50},[29,422,60],{"class":39},[29,424,63],{"class":35},[29,426,66],{"class":39},[29,428,347],{"class":35},[29,430,403],{"class":39},[29,432,352],{"class":35},[29,434,435],{"class":43},"isEmpty",[29,437,438],{"class":35},"()) { ... }\n",[29,440,441],{"class":31,"line":57},[29,442,443],{"class":83},"\u002F\u002F                              ^ s not in scope — test might not have matched\n",[10,445,447],{"id":446},"negation-and-guard-clauses","Negation and guard clauses",[15,449,450,451,454],{},"When you negate the pattern (",[26,452,453],{},"!(obj instanceof T t)","), the binding variable is in scope\nafter an early-exit (return, throw) — the compiler knows that any code past the guard can\nassume the test passed:",[19,456,458],{"className":21,"code":457,"language":23,"meta":24,"style":24},"void process(Object obj) {\n    if (!(obj instanceof String s)) {\n        throw new IllegalArgumentException(\"Expected String, got: \" + obj);\n    }\n    \u002F\u002F s is in scope here — we know obj is a String\n    System.out.println(s.toUpperCase());\n}\n",[26,459,460,471,490,511,516,521,533],{"__ignoreMap":24},[29,461,462,465,468],{"class":31,"line":32},[29,463,464],{"class":39},"void",[29,466,467],{"class":43}," process",[29,469,470],{"class":35},"(Object obj) {\n",[29,472,473,476,479,482,485,487],{"class":31,"line":50},[29,474,475],{"class":39},"    if",[29,477,478],{"class":35}," (",[29,480,481],{"class":39},"!",[29,483,484],{"class":35},"(obj ",[29,486,66],{"class":39},[29,488,489],{"class":35}," String s)) {\n",[29,491,492,495,498,501,503,506,508],{"class":31,"line":57},[29,493,494],{"class":39},"        throw",[29,496,497],{"class":39}," new",[29,499,500],{"class":43}," IllegalArgumentException",[29,502,376],{"class":35},[29,504,505],{"class":379},"\"Expected String, got: \"",[29,507,383],{"class":39},[29,509,510],{"class":35}," obj);\n",[29,512,513],{"class":31,"line":72},[29,514,515],{"class":35},"    }\n",[29,517,518],{"class":31,"line":87},[29,519,520],{"class":83},"    \u002F\u002F s is in scope here — we know obj is a String\n",[29,522,523,525,527,529,531],{"class":31,"line":105},[29,524,90],{"class":35},[29,526,93],{"class":43},[29,528,96],{"class":35},[29,530,99],{"class":43},[29,532,102],{"class":35},[29,534,535],{"class":31,"line":261},[29,536,108],{"class":35},[15,538,539,540,543,544,547],{},"This is the ",[144,541,542],{},"guard clause"," pattern (fail fast, return early) applied to type checking.\nIt's often cleaner than a deep nested ",[26,545,546],{},"if\u002Felse"," block:",[19,549,551],{"className":21,"code":550,"language":23,"meta":24,"style":24},"\u002F\u002F Deeply nested — harder to read:\nvoid handle(Object obj) {\n    if (obj instanceof Request req) {\n        if (req.isValid()) {\n            if (req.hasPermission()) {\n                process(req);\n            }\n        }\n    }\n}\n\n\u002F\u002F Guard clauses — flat and readable:\nvoid handle(Object obj) {\n    if (!(obj instanceof Request req)) return;\n    if (!req.isValid()) return;\n    if (!req.hasPermission()) return;\n    process(req);\n}\n",[26,552,553,558,567,578,592,604,612,617,622,626,630,634,639,647,668,689,708,716],{"__ignoreMap":24},[29,554,555],{"class":31,"line":32},[29,556,557],{"class":83},"\u002F\u002F Deeply nested — harder to read:\n",[29,559,560,562,565],{"class":31,"line":50},[29,561,464],{"class":39},[29,563,564],{"class":43}," handle",[29,566,470],{"class":35},[29,568,569,571,573,575],{"class":31,"line":57},[29,570,475],{"class":39},[29,572,63],{"class":35},[29,574,66],{"class":39},[29,576,577],{"class":35}," Request req) {\n",[29,579,580,583,586,589],{"class":31,"line":72},[29,581,582],{"class":39},"        if",[29,584,585],{"class":35}," (req.",[29,587,588],{"class":43},"isValid",[29,590,591],{"class":35},"()) {\n",[29,593,594,597,599,602],{"class":31,"line":87},[29,595,596],{"class":39},"            if",[29,598,585],{"class":35},[29,600,601],{"class":43},"hasPermission",[29,603,591],{"class":35},[29,605,606,609],{"class":31,"line":105},[29,607,608],{"class":43},"                process",[29,610,611],{"class":35},"(req);\n",[29,613,614],{"class":31,"line":261},[29,615,616],{"class":35},"            }\n",[29,618,619],{"class":31,"line":267},[29,620,621],{"class":35},"        }\n",[29,623,624],{"class":31,"line":272},[29,625,515],{"class":35},[29,627,628],{"class":31,"line":278},[29,629,108],{"class":35},[29,631,632],{"class":31,"line":289},[29,633,54],{"emptyLinePlaceholder":53},[29,635,636],{"class":31,"line":299},[29,637,638],{"class":83},"\u002F\u002F Guard clauses — flat and readable:\n",[29,640,641,643,645],{"class":31,"line":311},[29,642,464],{"class":39},[29,644,564],{"class":43},[29,646,470],{"class":35},[29,648,649,651,653,655,657,659,662,665],{"class":31,"line":317},[29,650,475],{"class":39},[29,652,478],{"class":35},[29,654,481],{"class":39},[29,656,484],{"class":35},[29,658,66],{"class":39},[29,660,661],{"class":35}," Request req)) ",[29,663,664],{"class":39},"return",[29,666,667],{"class":35},";\n",[29,669,671,673,675,677,680,682,685,687],{"class":31,"line":670},15,[29,672,475],{"class":39},[29,674,478],{"class":35},[29,676,481],{"class":39},[29,678,679],{"class":35},"req.",[29,681,588],{"class":43},[29,683,684],{"class":35},"()) ",[29,686,664],{"class":39},[29,688,667],{"class":35},[29,690,692,694,696,698,700,702,704,706],{"class":31,"line":691},16,[29,693,475],{"class":39},[29,695,478],{"class":35},[29,697,481],{"class":39},[29,699,679],{"class":35},[29,701,601],{"class":43},[29,703,684],{"class":35},[29,705,664],{"class":39},[29,707,667],{"class":35},[29,709,711,714],{"class":31,"line":710},17,[29,712,713],{"class":43},"    process",[29,715,611],{"class":35},[29,717,719],{"class":31,"line":718},18,[29,720,108],{"class":35},[10,722,724],{"id":723},"instanceof-and-null","instanceof and null",[15,726,727,729,730,733,734,736],{},[26,728,66],{}," ",[144,731,732],{},"always returns false for null"," — this is unchanged from traditional\n",[26,735,66],{},". Pattern matching inherits this: if the object is null, the pattern doesn't\nmatch, and the binding variable is never bound.",[19,738,740],{"className":21,"code":739,"language":23,"meta":24,"style":24},"Object obj = null;\nSystem.out.println(obj instanceof String);    \u002F\u002F false\nSystem.out.println(obj instanceof String s);  \u002F\u002F false — s never bound\n\nif (obj instanceof String s) {\n    \u002F\u002F We never enter this branch for null\n    s.length(); \u002F\u002F no NPE risk — guaranteed non-null\n}\n",[26,741,742,753,770,786,790,800,805,818],{"__ignoreMap":24},[29,743,744,746,748,751],{"class":31,"line":32},[29,745,36],{"class":35},[29,747,40],{"class":39},[29,749,750],{"class":363}," null",[29,752,667],{"class":35},[29,754,755,758,760,762,764,767],{"class":31,"line":50},[29,756,757],{"class":35},"System.out.",[29,759,93],{"class":43},[29,761,484],{"class":35},[29,763,66],{"class":39},[29,765,766],{"class":35}," String);    ",[29,768,769],{"class":83},"\u002F\u002F false\n",[29,771,772,774,776,778,780,783],{"class":31,"line":57},[29,773,757],{"class":35},[29,775,93],{"class":43},[29,777,484],{"class":35},[29,779,66],{"class":39},[29,781,782],{"class":35}," String s);  ",[29,784,785],{"class":83},"\u002F\u002F false — s never bound\n",[29,787,788],{"class":31,"line":72},[29,789,54],{"emptyLinePlaceholder":53},[29,791,792,794,796,798],{"class":31,"line":87},[29,793,60],{"class":39},[29,795,63],{"class":35},[29,797,66],{"class":39},[29,799,169],{"class":35},[29,801,802],{"class":31,"line":105},[29,803,804],{"class":83},"    \u002F\u002F We never enter this branch for null\n",[29,806,807,810,812,815],{"class":31,"line":261},[29,808,809],{"class":35},"    s.",[29,811,252],{"class":43},[29,813,814],{"class":35},"(); ",[29,816,817],{"class":83},"\u002F\u002F no NPE risk — guaranteed non-null\n",[29,819,820],{"class":31,"line":267},[29,821,108],{"class":35},[15,823,824,825,828,829,831],{},"This means you don't need a separate ",[26,826,827],{},"!= null"," check before an ",[26,830,66],{}," pattern —\nthe null case is handled implicitly.",[10,833,835],{"id":834},"improving-equals-with-pattern-matching","Improving equals() with pattern matching",[15,837,838,839,841,842,845],{},"The most common use of the old ",[26,840,66],{}," + cast was in ",[26,843,844],{},"equals()"," overrides. Pattern\nmatching makes them concise and eliminates the cast:",[19,847,849],{"className":21,"code":848,"language":23,"meta":24,"style":24},"public final class Point {\n    final int x, y;\n    Point(int x, int y) { this.x = x; this.y = y; }\n\n    \u002F\u002F Old way:\n    @Override\n    public boolean equals(Object o) {\n        if (!(o instanceof Point)) return false;\n        Point other = (Point) o;          \u002F\u002F redundant cast\n        return x == other.x && y == other.y;\n    }\n\n    \u002F\u002F Pattern matching (Java 16+):\n    @Override\n    public boolean equals(Object o) {\n        return o instanceof Point other\n            && x == other.x\n            && y == other.y;\n    }\n}\n",[26,850,851,867,878,924,928,933,941,960,983,996,1020,1024,1028,1033,1039,1053,1065,1077,1087,1092],{"__ignoreMap":24},[29,852,853,856,859,862,865],{"class":31,"line":32},[29,854,855],{"class":39},"public",[29,857,858],{"class":39}," final",[29,860,861],{"class":39}," class",[29,863,864],{"class":43}," Point",[29,866,308],{"class":35},[29,868,869,872,875],{"class":31,"line":50},[29,870,871],{"class":39},"    final",[29,873,874],{"class":39}," int",[29,876,877],{"class":35}," x, y;\n",[29,879,880,883,885,888,892,895,897,900,903,906,909,911,914,916,919,921],{"class":31,"line":57},[29,881,882],{"class":43},"    Point",[29,884,376],{"class":35},[29,886,887],{"class":39},"int",[29,889,891],{"class":890},"s4XuR"," x",[29,893,894],{"class":35},", ",[29,896,887],{"class":39},[29,898,899],{"class":890}," y",[29,901,902],{"class":35},") { ",[29,904,905],{"class":363},"this",[29,907,908],{"class":35},".x ",[29,910,40],{"class":39},[29,912,913],{"class":35}," x; ",[29,915,905],{"class":363},[29,917,918],{"class":35},".y ",[29,920,40],{"class":39},[29,922,923],{"class":35}," y; }\n",[29,925,926],{"class":31,"line":72},[29,927,54],{"emptyLinePlaceholder":53},[29,929,930],{"class":31,"line":87},[29,931,932],{"class":83},"    \u002F\u002F Old way:\n",[29,934,935,938],{"class":31,"line":105},[29,936,937],{"class":35},"    @",[29,939,940],{"class":39},"Override\n",[29,942,943,946,949,952,955,958],{"class":31,"line":261},[29,944,945],{"class":39},"    public",[29,947,948],{"class":39}," boolean",[29,950,951],{"class":43}," equals",[29,953,954],{"class":35},"(Object ",[29,956,957],{"class":890},"o",[29,959,367],{"class":35},[29,961,962,964,966,968,971,973,976,978,981],{"class":31,"line":267},[29,963,582],{"class":39},[29,965,478],{"class":35},[29,967,481],{"class":39},[29,969,970],{"class":35},"(o ",[29,972,66],{"class":39},[29,974,975],{"class":35}," Point)) ",[29,977,664],{"class":39},[29,979,980],{"class":363}," false",[29,982,667],{"class":35},[29,984,985,988,990,993],{"class":31,"line":272},[29,986,987],{"class":35},"        Point other ",[29,989,40],{"class":39},[29,991,992],{"class":35}," (Point) o;          ",[29,994,995],{"class":83},"\u002F\u002F redundant cast\n",[29,997,998,1001,1004,1007,1010,1012,1015,1017],{"class":31,"line":278},[29,999,1000],{"class":39},"        return",[29,1002,1003],{"class":35}," x ",[29,1005,1006],{"class":39},"==",[29,1008,1009],{"class":35}," other.x ",[29,1011,330],{"class":39},[29,1013,1014],{"class":35}," y ",[29,1016,1006],{"class":39},[29,1018,1019],{"class":35}," other.y;\n",[29,1021,1022],{"class":31,"line":289},[29,1023,515],{"class":35},[29,1025,1026],{"class":31,"line":299},[29,1027,54],{"emptyLinePlaceholder":53},[29,1029,1030],{"class":31,"line":311},[29,1031,1032],{"class":83},"    \u002F\u002F Pattern matching (Java 16+):\n",[29,1034,1035,1037],{"class":31,"line":317},[29,1036,937],{"class":35},[29,1038,940],{"class":39},[29,1040,1041,1043,1045,1047,1049,1051],{"class":31,"line":670},[29,1042,945],{"class":39},[29,1044,948],{"class":39},[29,1046,951],{"class":43},[29,1048,954],{"class":35},[29,1050,957],{"class":890},[29,1052,367],{"class":35},[29,1054,1055,1057,1060,1062],{"class":31,"line":691},[29,1056,1000],{"class":39},[29,1058,1059],{"class":35}," o ",[29,1061,66],{"class":39},[29,1063,1064],{"class":35}," Point other\n",[29,1066,1067,1070,1072,1074],{"class":31,"line":710},[29,1068,1069],{"class":39},"            &&",[29,1071,1003],{"class":35},[29,1073,1006],{"class":39},[29,1075,1076],{"class":35}," other.x\n",[29,1078,1079,1081,1083,1085],{"class":31,"line":718},[29,1080,1069],{"class":39},[29,1082,1014],{"class":35},[29,1084,1006],{"class":39},[29,1086,1019],{"class":35},[29,1088,1090],{"class":31,"line":1089},19,[29,1091,515],{"class":35},[29,1093,1095],{"class":31,"line":1094},20,[29,1096,108],{"class":35},[15,1098,1099],{},"The pattern-matching version is a single expression — compact, readable, and impossible\nto accidentally cast to the wrong type.",[10,1101,1103],{"id":1102},"ternary-expressions","Ternary expressions",[15,1105,1106],{},"Pattern binding variables work in the true branch of a ternary:",[19,1108,1110],{"className":21,"code":1109,"language":23,"meta":24,"style":24},"Object obj = getValue();\nString result = obj instanceof String s ? s.toUpperCase() : \"not a string\";\n",[26,1111,1112,1123],{"__ignoreMap":24},[29,1113,1114,1116,1118,1121],{"class":31,"line":32},[29,1115,36],{"class":35},[29,1117,40],{"class":39},[29,1119,1120],{"class":43}," getValue",[29,1122,47],{"class":35},[29,1124,1125,1128,1130,1133,1135,1137,1140,1142,1144,1146,1149,1152],{"class":31,"line":50},[29,1126,1127],{"class":35},"String result ",[29,1129,40],{"class":39},[29,1131,1132],{"class":35}," obj ",[29,1134,66],{"class":39},[29,1136,347],{"class":35},[29,1138,1139],{"class":39},"?",[29,1141,352],{"class":35},[29,1143,99],{"class":43},[29,1145,357],{"class":35},[29,1147,1148],{"class":39},":",[29,1150,1151],{"class":379}," \"not a string\"",[29,1153,667],{"class":35},[15,1155,1156,1157,1159],{},"The binding variable ",[26,1158,195],{}," is only accessible in the true branch, exactly where the test\nis known to have passed.",[10,1161,1163],{"id":1162},"generic-types-the-erasure-limitation","Generic types — the erasure limitation",[15,1165,1166,1167,1170,1171,1174],{},"Because of type erasure, you can only test the ",[144,1168,1169],{},"raw type"," at runtime, not a\nparameterized generic. Testing ",[26,1172,1173],{},"obj instanceof List\u003CString>"," is a compile error:",[19,1176,1178],{"className":21,"code":1177,"language":23,"meta":24,"style":24},"Object obj = List.of(\"a\", \"b\");\n\n\u002F\u002F Compile error — generic argument erased at runtime:\n\u002F\u002F if (obj instanceof List\u003CString> strings) { }\n\n\u002F\u002F Correct — use wildcard:\nif (obj instanceof List\u003C?> list) {\n    list.forEach(System.out::println); \u002F\u002F elements are Object\n}\n",[26,1179,1180,1205,1209,1214,1219,1223,1228,1245,1265],{"__ignoreMap":24},[29,1181,1182,1184,1186,1189,1192,1194,1197,1199,1202],{"class":31,"line":32},[29,1183,36],{"class":35},[29,1185,40],{"class":39},[29,1187,1188],{"class":35}," List.",[29,1190,1191],{"class":43},"of",[29,1193,376],{"class":35},[29,1195,1196],{"class":379},"\"a\"",[29,1198,894],{"class":35},[29,1200,1201],{"class":379},"\"b\"",[29,1203,1204],{"class":35},");\n",[29,1206,1207],{"class":31,"line":50},[29,1208,54],{"emptyLinePlaceholder":53},[29,1210,1211],{"class":31,"line":57},[29,1212,1213],{"class":83},"\u002F\u002F Compile error — generic argument erased at runtime:\n",[29,1215,1216],{"class":31,"line":72},[29,1217,1218],{"class":83},"\u002F\u002F if (obj instanceof List\u003CString> strings) { }\n",[29,1220,1221],{"class":31,"line":87},[29,1222,54],{"emptyLinePlaceholder":53},[29,1224,1225],{"class":31,"line":105},[29,1226,1227],{"class":83},"\u002F\u002F Correct — use wildcard:\n",[29,1229,1230,1232,1234,1236,1239,1242],{"class":31,"line":261},[29,1231,60],{"class":39},[29,1233,63],{"class":35},[29,1235,66],{"class":39},[29,1237,1238],{"class":35}," List",[29,1240,1241],{"class":39},"\u003C?>",[29,1243,1244],{"class":35}," list) {\n",[29,1246,1247,1250,1253,1256,1259,1262],{"class":31,"line":267},[29,1248,1249],{"class":35},"    list.",[29,1251,1252],{"class":43},"forEach",[29,1254,1255],{"class":35},"(System.out",[29,1257,1258],{"class":39},"::",[29,1260,1261],{"class":35},"println); ",[29,1263,1264],{"class":83},"\u002F\u002F elements are Object\n",[29,1266,1267],{"class":31,"line":272},[29,1268,108],{"class":35},[15,1270,1271],{},"If you need to verify element types, check each element individually inside the branch.",[10,1273,1275],{"id":1274},"variable-shadowing","Variable shadowing",[15,1277,1278],{},"Pattern binding variables follow the same shadowing rules as local variables. They can\nshadow a field from an outer scope but cannot shadow an existing local variable in the\nsame scope:",[19,1280,1282],{"className":21,"code":1281,"language":23,"meta":24,"style":24},"class Processor {\n    String value = \"field\";\n\n    void run(Object obj) {\n        \u002F\u002F Shadows the field 'value' — allowed:\n        if (obj instanceof String value) {\n            System.out.println(value); \u002F\u002F pattern variable, not the field\n        }\n\n        String existing = \"local\";\n        \u002F\u002F Cannot shadow a local variable in the same scope:\n        \u002F\u002F if (obj instanceof String existing) { }  \u002F\u002F compile error\n    }\n}\n",[26,1283,1284,1294,1306,1310,1324,1329,1340,1353,1357,1361,1373,1378,1386,1390],{"__ignoreMap":24},[29,1285,1286,1289,1292],{"class":31,"line":32},[29,1287,1288],{"class":39},"class",[29,1290,1291],{"class":43}," Processor",[29,1293,308],{"class":35},[29,1295,1296,1299,1301,1304],{"class":31,"line":50},[29,1297,1298],{"class":35},"    String value ",[29,1300,40],{"class":39},[29,1302,1303],{"class":379}," \"field\"",[29,1305,667],{"class":35},[29,1307,1308],{"class":31,"line":57},[29,1309,54],{"emptyLinePlaceholder":53},[29,1311,1312,1315,1318,1320,1322],{"class":31,"line":72},[29,1313,1314],{"class":39},"    void",[29,1316,1317],{"class":43}," run",[29,1319,954],{"class":35},[29,1321,118],{"class":890},[29,1323,367],{"class":35},[29,1325,1326],{"class":31,"line":87},[29,1327,1328],{"class":83},"        \u002F\u002F Shadows the field 'value' — allowed:\n",[29,1330,1331,1333,1335,1337],{"class":31,"line":105},[29,1332,582],{"class":39},[29,1334,63],{"class":35},[29,1336,66],{"class":39},[29,1338,1339],{"class":35}," String value) {\n",[29,1341,1342,1345,1347,1350],{"class":31,"line":261},[29,1343,1344],{"class":35},"            System.out.",[29,1346,93],{"class":43},[29,1348,1349],{"class":35},"(value); ",[29,1351,1352],{"class":83},"\u002F\u002F pattern variable, not the field\n",[29,1354,1355],{"class":31,"line":267},[29,1356,621],{"class":35},[29,1358,1359],{"class":31,"line":272},[29,1360,54],{"emptyLinePlaceholder":53},[29,1362,1363,1366,1368,1371],{"class":31,"line":278},[29,1364,1365],{"class":35},"        String existing ",[29,1367,40],{"class":39},[29,1369,1370],{"class":379}," \"local\"",[29,1372,667],{"class":35},[29,1374,1375],{"class":31,"line":289},[29,1376,1377],{"class":83},"        \u002F\u002F Cannot shadow a local variable in the same scope:\n",[29,1379,1380,1383],{"class":31,"line":299},[29,1381,1382],{"class":83},"        \u002F\u002F if (obj instanceof String existing) { }",[29,1384,1385],{"class":83},"  \u002F\u002F compile error\n",[29,1387,1388],{"class":31,"line":311},[29,1389,515],{"class":35},[29,1391,1392],{"class":31,"line":317},[29,1393,108],{"class":35},[10,1395,1397],{"id":1396},"instanceof-patterns-vs-switch-patterns","instanceof patterns vs switch patterns",[15,1399,1400,1401,1404],{},"Both belong to the same ",[144,1402,1403],{},"pattern matching"," feature family:",[1406,1407,1408,1424],"table",{},[1409,1410,1411],"thead",{},[1412,1413,1414,1418,1421],"tr",{},[1415,1416,1417],"th",{},"Feature",[1415,1419,1420],{},"Java version",[1415,1422,1423],{},"Best for",[1425,1426,1427,1440],"tbody",{},[1412,1428,1429,1435,1437],{},[1430,1431,1432,1434],"td",{},[26,1433,66],{}," pattern",[1430,1436,152],{},[1430,1438,1439],{},"One or two type checks",[1412,1441,1442,1445,1448],{},[1430,1443,1444],{},"Switch type pattern",[1430,1446,1447],{},"Java 21",[1430,1449,1450],{},"Three or more types, exhaustiveness needed",[19,1452,1454],{"className":21,"code":1453,"language":23,"meta":24,"style":24},"\u002F\u002F One type — instanceof is cleanest:\nif (event instanceof OrderPlaced e) {\n    handlePlaced(e);\n}\n\n\u002F\u002F Multiple types — switch is cleaner and exhaustiveness-checked:\nswitch (event) {\n    case OrderPlaced e    -> handlePlaced(e);\n    case OrderShipped e   -> handleShipped(e);\n    case OrderCancelled e -> handleCancelled(e);\n}\n",[26,1455,1456,1461,1473,1481,1485,1489,1494,1502,1518,1532,1546],{"__ignoreMap":24},[29,1457,1458],{"class":31,"line":32},[29,1459,1460],{"class":83},"\u002F\u002F One type — instanceof is cleanest:\n",[29,1462,1463,1465,1468,1470],{"class":31,"line":50},[29,1464,60],{"class":39},[29,1466,1467],{"class":35}," (event ",[29,1469,66],{"class":39},[29,1471,1472],{"class":35}," OrderPlaced e) {\n",[29,1474,1475,1478],{"class":31,"line":57},[29,1476,1477],{"class":43},"    handlePlaced",[29,1479,1480],{"class":35},"(e);\n",[29,1482,1483],{"class":31,"line":72},[29,1484,108],{"class":35},[29,1486,1487],{"class":31,"line":87},[29,1488,54],{"emptyLinePlaceholder":53},[29,1490,1491],{"class":31,"line":105},[29,1492,1493],{"class":83},"\u002F\u002F Multiple types — switch is cleaner and exhaustiveness-checked:\n",[29,1495,1496,1499],{"class":31,"line":261},[29,1497,1498],{"class":39},"switch",[29,1500,1501],{"class":35}," (event) {\n",[29,1503,1504,1507,1510,1513,1516],{"class":31,"line":267},[29,1505,1506],{"class":39},"    case",[29,1508,1509],{"class":35}," OrderPlaced e    ",[29,1511,1512],{"class":39},"->",[29,1514,1515],{"class":43}," handlePlaced",[29,1517,1480],{"class":35},[29,1519,1520,1522,1525,1527,1530],{"class":31,"line":272},[29,1521,1506],{"class":39},[29,1523,1524],{"class":35}," OrderShipped e   ",[29,1526,1512],{"class":39},[29,1528,1529],{"class":43}," handleShipped",[29,1531,1480],{"class":35},[29,1533,1534,1536,1539,1541,1544],{"class":31,"line":278},[29,1535,1506],{"class":39},[29,1537,1538],{"class":35}," OrderCancelled e ",[29,1540,1512],{"class":39},[29,1542,1543],{"class":43}," handleCancelled",[29,1545,1480],{"class":35},[29,1547,1548],{"class":31,"line":289},[29,1549,108],{"class":35},[15,1551,1552,1553,1555,1556,1558],{},"Use ",[26,1554,66],{}," patterns for simple type guards and ",[26,1557,844],{}," overrides; graduate to\nswitch type patterns when dispatching over three or more types, especially sealed ones.",[10,1560,1562],{"id":1561},"old-instanceof-cast-is-a-code-smell","Old instanceof + cast is a code smell",[15,1564,1565,1566,1568],{},"In Java 16+ codebases, the old ",[26,1567,66],{}," + cast on the next line is a code smell. It\nshould be replaced in code reviews:",[19,1570,1572],{"className":21,"code":1571,"language":23,"meta":24,"style":24},"\u002F\u002F Code smell — Java 16+ projects:\nif (event instanceof OrderPlaced) {\n    OrderPlaced placed = (OrderPlaced) event; \u002F\u002F ← flag this in review\n    ...\n}\n\n\u002F\u002F Fix:\nif (event instanceof OrderPlaced placed) {\n    ...\n}\n",[26,1573,1574,1579,1590,1603,1608,1612,1616,1621,1632,1636],{"__ignoreMap":24},[29,1575,1576],{"class":31,"line":32},[29,1577,1578],{"class":83},"\u002F\u002F Code smell — Java 16+ projects:\n",[29,1580,1581,1583,1585,1587],{"class":31,"line":50},[29,1582,60],{"class":39},[29,1584,1467],{"class":35},[29,1586,66],{"class":39},[29,1588,1589],{"class":35}," OrderPlaced) {\n",[29,1591,1592,1595,1597,1600],{"class":31,"line":57},[29,1593,1594],{"class":35},"    OrderPlaced placed ",[29,1596,40],{"class":39},[29,1598,1599],{"class":35}," (OrderPlaced) event; ",[29,1601,1602],{"class":83},"\u002F\u002F ← flag this in review\n",[29,1604,1605],{"class":31,"line":72},[29,1606,1607],{"class":35},"    ...\n",[29,1609,1610],{"class":31,"line":87},[29,1611,108],{"class":35},[29,1613,1614],{"class":31,"line":105},[29,1615,54],{"emptyLinePlaceholder":53},[29,1617,1618],{"class":31,"line":261},[29,1619,1620],{"class":83},"\u002F\u002F Fix:\n",[29,1622,1623,1625,1627,1629],{"class":31,"line":267},[29,1624,60],{"class":39},[29,1626,1467],{"class":35},[29,1628,66],{"class":39},[29,1630,1631],{"class":35}," OrderPlaced placed) {\n",[29,1633,1634],{"class":31,"line":272},[29,1635,1607],{"class":35},[29,1637,1638],{"class":31,"line":278},[29,1639,108],{"class":35},[15,1641,1642],{},"Many IDEs (IntelliJ IDEA, Eclipse) offer automatic quick fixes to convert the old pattern\nto the new one across the entire codebase.",[10,1644,1646],{"id":1645},"recap","Recap",[15,1648,1649,1653,1654,1656,1657,1659,1660,729,1662,1665,1666,1668,1669,1672,1673,1676,1677,1680],{},[144,1650,146,1651],{},[26,1652,66],{}," (Java 16) eliminates the redundant cast that always\nfollowed a type test. The binding variable is in scope only where the compiler can prove\nthe test passed — in the true branch and ",[26,1655,330],{}," chains, but not in ",[26,1658,403],{}," chains or the else\nbranch. Negated patterns work with guard clauses: after an early exit, the binding\nvariable flows into scope for the rest of the method. ",[26,1661,66],{},[144,1663,1664],{},"always returns false\nfor null",", so bound variables are guaranteed non-null. The most impactful use is\nsimplifying ",[26,1667,844],{}," overrides into a single expression. Generic arguments cannot be\ntested at runtime due to type erasure — use ",[26,1670,1671],{},"List\u003C?>"," instead of ",[26,1674,1675],{},"List\u003CString>",". For\nmulti-way type dispatch, graduate to ",[144,1678,1679],{},"switch type patterns"," (Java 21), which add\nexhaustiveness checking for sealed types.",[1682,1683,1684],"style",{},"html pre.shiki code .sVt8B, html code.shiki .sVt8B{--shiki-default:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .szBVR, html code.shiki .szBVR{--shiki-default:#D73A49;--shiki-dark:#F97583}html pre.shiki code .sScJk, html code.shiki .sScJk{--shiki-default:#6F42C1;--shiki-dark:#B392F0}html pre.shiki code .sJ8bj, html code.shiki .sJ8bj{--shiki-default:#6A737D;--shiki-dark:#6A737D}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .sj4cs, html code.shiki .sj4cs{--shiki-default:#005CC5;--shiki-dark:#79B8FF}html pre.shiki code .sZZnC, html code.shiki .sZZnC{--shiki-default:#032F62;--shiki-dark:#9ECBFF}html pre.shiki code .s4XuR, html code.shiki .s4XuR{--shiki-default:#E36209;--shiki-dark:#FFAB70}",{"title":24,"searchDepth":50,"depth":50,"links":1686},[1687,1688,1692,1693,1694,1695,1696,1697,1698,1699,1700],{"id":12,"depth":50,"text":13},{"id":199,"depth":50,"text":200,"children":1689},[1690,1691],{"id":323,"depth":57,"text":324},{"id":396,"depth":57,"text":397},{"id":446,"depth":50,"text":447},{"id":723,"depth":50,"text":724},{"id":834,"depth":50,"text":835},{"id":1102,"depth":50,"text":1103},{"id":1162,"depth":50,"text":1163},{"id":1274,"depth":50,"text":1275},{"id":1396,"depth":50,"text":1397},{"id":1561,"depth":50,"text":1562},{"id":1645,"depth":50,"text":1646},"Complete guide to Java instanceof pattern matching — binding variables, scope and flow-sensitive typing, negation guards, compound conditions, null safety, equals() improvements, generics limitation, and the relationship to switch type patterns.","easy","md","Java",{},"\u002Fblog\u002Fjava-instanceof-pattern-matching","\u002Fjava\u002Fmodern-java\u002Finstanceof-pattern-matching",{"title":5,"description":1701},"blog\u002Fjava-instanceof-pattern-matching","instanceof Pattern Matching","Modern Java","modern-java","2026-06-20","3R29d1KAUmEYPTYgkMhgbHynjyueZBLpcoXmVjI-tHo",1782244091776]