[{"data":1,"prerenderedAt":1867},["ShallowReactive",2],{"blog-\u002Fblog\u002Fjava-sealed-classes":3},{"id":4,"title":5,"body":6,"description":1853,"difficulty":1854,"extension":1855,"framework":1856,"frameworkSlug":56,"meta":1857,"navigation":105,"order":102,"path":1858,"qaPath":1859,"seo":1860,"stem":1861,"subtopic":1862,"topic":1863,"topicSlug":1864,"updated":1865,"__hash__":1866},"blog\u002Fblog\u002Fjava-sealed-classes.md","Java Sealed Classes — Closed Hierarchies, Exhaustive Switch, and ADTs",{"type":7,"value":8,"toc":1839},"minimark",[9,14,36,47,51,188,219,223,234,308,318,322,325,332,342,350,519,524,528,612,621,740,744,759,886,905,909,919,1167,1174,1178,1189,1553,1567,1571,1592,1596,1599,1631,1634,1638,1641,1790,1799,1803,1835],[10,11,13],"h2",{"id":12},"the-problem-with-open-inheritance","The problem with open inheritance",[15,16,17,18,22,23,26,27,31,32,35],"p",{},"Before sealed classes, a ",[19,20,21],"code",{},"public abstract class Shape"," or ",[19,24,25],{},"public interface Shape"," could\nbe extended by ",[28,29,30],"em",{},"anyone",", anywhere. This is fine for true extension points but problematic\nwhen you own the complete set of subtypes — you can never safely write an exhaustive\n",[19,33,34],{},"if\u002Felse instanceof"," chain because the compiler can't verify you've handled every case.",[15,37,38,42,43,46],{},[39,40,41],"strong",{},"Sealed classes"," (finalized in ",[39,44,45],{},"Java 17",", JEP 409) solve this by letting you declare\nexactly which types may directly extend or implement a class or interface.",[10,48,50],{"id":49},"basic-syntax","Basic syntax",[52,53,58],"pre",{"className":54,"code":55,"language":56,"meta":57,"style":57},"language-java shiki shiki-themes github-light github-dark","public sealed interface Shape permits Circle, Rectangle, Triangle {}\n\npublic record Circle(double radius)       implements Shape {}\npublic record Rectangle(double w, double h) implements Shape {}\npublic record Triangle(double base, double h) implements Shape {}\n","java","",[19,59,60,100,107,133,161],{"__ignoreMap":57},[61,62,65,69,72,75,79,82,85,89,92,94,97],"span",{"class":63,"line":64},"line",1,[61,66,68],{"class":67},"szBVR","public",[61,70,71],{"class":67}," sealed",[61,73,74],{"class":67}," interface",[61,76,78],{"class":77},"sScJk"," Shape",[61,80,81],{"class":67}," permits",[61,83,84],{"class":77}," Circle",[61,86,88],{"class":87},"sVt8B",", ",[61,90,91],{"class":77},"Rectangle",[61,93,88],{"class":87},[61,95,96],{"class":77},"Triangle",[61,98,99],{"class":87}," {}\n",[61,101,103],{"class":63,"line":102},2,[61,104,106],{"emptyLinePlaceholder":105},true,"\n",[61,108,110,112,115,117,120,123,126,129,131],{"class":63,"line":109},3,[61,111,68],{"class":67},[61,113,114],{"class":67}," record",[61,116,84],{"class":77},[61,118,119],{"class":87},"(",[61,121,122],{"class":67},"double",[61,124,125],{"class":87}," radius)       ",[61,127,128],{"class":67},"implements",[61,130,78],{"class":77},[61,132,99],{"class":87},[61,134,136,138,140,143,145,147,150,152,155,157,159],{"class":63,"line":135},4,[61,137,68],{"class":67},[61,139,114],{"class":67},[61,141,142],{"class":77}," Rectangle",[61,144,119],{"class":87},[61,146,122],{"class":67},[61,148,149],{"class":87}," w, ",[61,151,122],{"class":67},[61,153,154],{"class":87}," h) ",[61,156,128],{"class":67},[61,158,78],{"class":77},[61,160,99],{"class":87},[61,162,164,166,168,171,173,175,178,180,182,184,186],{"class":63,"line":163},5,[61,165,68],{"class":67},[61,167,114],{"class":67},[61,169,170],{"class":77}," Triangle",[61,172,119],{"class":87},[61,174,122],{"class":67},[61,176,177],{"class":87}," base, ",[61,179,122],{"class":67},[61,181,154],{"class":87},[61,183,128],{"class":67},[61,185,78],{"class":77},[61,187,99],{"class":87},[15,189,190,193,194,88,197,199,200,202,203,206,207,210,211,214,215,218],{},[19,191,192],{},"Shape"," can now only be implemented by ",[19,195,196],{},"Circle",[19,198,91],{},", and ",[19,201,96],{},". Any other\nclass attempting ",[19,204,205],{},"implements Shape"," gets a ",[39,208,209],{},"compile error",". The three permitted types\nare ",[19,212,213],{},"record"," (implicitly ",[19,216,217],{},"final","), which naturally closes the hierarchy.",[10,220,222],{"id":221},"the-permits-clause-explicit-and-implicit","The permits clause — explicit and implicit",[15,224,225,226,229,230,233],{},"When all permitted subtypes are in the ",[39,227,228],{},"same source file"," as the sealed type, ",[19,231,232],{},"permits","\ncan be omitted and the compiler infers it:",[52,235,237],{"className":54,"code":236,"language":56,"meta":57,"style":57},"\u002F\u002F Single file — permits inferred:\nsealed class Result\u003CT> {\n    record Ok\u003CT>(T value) extends Result\u003CT> {}\n    record Err\u003CT>(String msg) extends Result\u003CT> {}\n}\n",[19,238,239,245,265,285,303],{"__ignoreMap":57},[61,240,241],{"class":63,"line":64},[61,242,244],{"class":243},"sJ8bj","\u002F\u002F Single file — permits inferred:\n",[61,246,247,250,253,256,259,262],{"class":63,"line":102},[61,248,249],{"class":67},"sealed",[61,251,252],{"class":67}," class",[61,254,255],{"class":77}," Result",[61,257,258],{"class":87},"\u003C",[61,260,261],{"class":67},"T",[61,263,264],{"class":87},"> {\n",[61,266,267,270,273,275,277,280,282],{"class":63,"line":109},[61,268,269],{"class":67},"    record",[61,271,272],{"class":77}," Ok",[61,274,258],{"class":87},[61,276,261],{"class":67},[61,278,279],{"class":87},">(T value) extends Result\u003C",[61,281,261],{"class":67},[61,283,284],{"class":87},"> {}\n",[61,286,287,289,292,294,296,299,301],{"class":63,"line":135},[61,288,269],{"class":67},[61,290,291],{"class":77}," Err",[61,293,258],{"class":87},[61,295,261],{"class":67},[61,297,298],{"class":87},">(String msg) extends Result\u003C",[61,300,261],{"class":67},[61,302,284],{"class":87},[61,304,305],{"class":63,"line":163},[61,306,307],{"class":87},"}\n",[15,309,310,311,314,315,317],{},"When subtypes are in ",[39,312,313],{},"separate files",", the ",[19,316,232],{}," clause is required, and the\nsubtypes must reside in the same package (non-modular) or same module.",[10,319,321],{"id":320},"permitted-subtype-modifiers-final-sealed-non-sealed","Permitted subtype modifiers: final, sealed, non-sealed",[15,323,324],{},"Every permitted subtype must declare one of three modifiers:",[15,326,327,331],{},[39,328,329],{},[19,330,217],{}," — closes the branch. No further subclasses allowed.",[15,333,334,338,339,341],{},[39,335,336],{},[19,337,249],{}," — extends the hierarchy but restricts it further with its own ",[19,340,232],{},".",[15,343,344,349],{},[39,345,346],{},[19,347,348],{},"non-sealed"," — deliberately re-opens the hierarchy. Anyone can extend this subtype.",[52,351,353],{"className":54,"code":352,"language":56,"meta":57,"style":57},"sealed interface Notification permits Email, Push, Sms {}\n\nfinal class Email implements Notification {}       \u002F\u002F leaf — closed\n\nsealed class Push implements Notification\n    permits AndroidPush, IosPush {}                \u002F\u002F further sealed\nfinal class AndroidPush extends Push {}\nfinal class IosPush     extends Push {}\n\nnon-sealed class Sms implements Notification {}    \u002F\u002F re-opened\nclass SmsPremium extends Sms {}                    \u002F\u002F anyone can extend Sms\n",[19,354,355,381,385,404,408,422,442,458,475,480,500],{"__ignoreMap":57},[61,356,357,359,361,364,366,369,371,374,376,379],{"class":63,"line":64},[61,358,249],{"class":67},[61,360,74],{"class":67},[61,362,363],{"class":77}," Notification",[61,365,81],{"class":67},[61,367,368],{"class":77}," Email",[61,370,88],{"class":87},[61,372,373],{"class":77},"Push",[61,375,88],{"class":87},[61,377,378],{"class":77},"Sms",[61,380,99],{"class":87},[61,382,383],{"class":63,"line":102},[61,384,106],{"emptyLinePlaceholder":105},[61,386,387,389,391,393,396,398,401],{"class":63,"line":109},[61,388,217],{"class":67},[61,390,252],{"class":67},[61,392,368],{"class":77},[61,394,395],{"class":67}," implements",[61,397,363],{"class":77},[61,399,400],{"class":87}," {}       ",[61,402,403],{"class":243},"\u002F\u002F leaf — closed\n",[61,405,406],{"class":63,"line":135},[61,407,106],{"emptyLinePlaceholder":105},[61,409,410,412,414,417,419],{"class":63,"line":163},[61,411,249],{"class":67},[61,413,252],{"class":67},[61,415,416],{"class":77}," Push",[61,418,395],{"class":67},[61,420,421],{"class":77}," Notification\n",[61,423,425,428,431,433,436,439],{"class":63,"line":424},6,[61,426,427],{"class":67},"    permits",[61,429,430],{"class":77}," AndroidPush",[61,432,88],{"class":87},[61,434,435],{"class":77},"IosPush",[61,437,438],{"class":87}," {}                ",[61,440,441],{"class":243},"\u002F\u002F further sealed\n",[61,443,445,447,449,451,454,456],{"class":63,"line":444},7,[61,446,217],{"class":67},[61,448,252],{"class":67},[61,450,430],{"class":77},[61,452,453],{"class":67}," extends",[61,455,416],{"class":77},[61,457,99],{"class":87},[61,459,461,463,465,468,471,473],{"class":63,"line":460},8,[61,462,217],{"class":67},[61,464,252],{"class":67},[61,466,467],{"class":77}," IosPush",[61,469,470],{"class":67},"     extends",[61,472,416],{"class":77},[61,474,99],{"class":87},[61,476,478],{"class":63,"line":477},9,[61,479,106],{"emptyLinePlaceholder":105},[61,481,483,485,487,490,492,494,497],{"class":63,"line":482},10,[61,484,348],{"class":67},[61,486,252],{"class":67},[61,488,489],{"class":77}," Sms",[61,491,395],{"class":67},[61,493,363],{"class":77},[61,495,496],{"class":87}," {}    ",[61,498,499],{"class":243},"\u002F\u002F re-opened\n",[61,501,503,506,509,511,513,516],{"class":63,"line":502},11,[61,504,505],{"class":67},"class",[61,507,508],{"class":77}," SmsPremium",[61,510,453],{"class":67},[61,512,489],{"class":77},[61,514,515],{"class":87}," {}                    ",[61,517,518],{"class":243},"\u002F\u002F anyone can extend Sms\n",[15,520,521,523],{},[19,522,348],{}," is the escape hatch for intentional extension points inside an otherwise\nclosed hierarchy — use it sparingly and document it as a public API contract.",[10,525,527],{"id":526},"sealed-classes-vs-abstract-classes-vs-enums","Sealed classes vs abstract classes vs enums",[529,530,531,549],"table",{},[532,533,534],"thead",{},[535,536,537,540,543,546],"tr",{},[538,539],"th",{},[538,541,542],{},"abstract class",[538,544,545],{},"sealed class",[538,547,548],{},"enum",[550,551,552,570,587,601],"tbody",{},[535,553,554,558,561,567],{},[555,556,557],"td",{},"Prevents direct instantiation",[555,559,560],{},"Yes",[555,562,563,564],{},"Only if also ",[19,565,566],{},"abstract",[555,568,569],{},"Yes (only constants)",[535,571,572,575,578,584],{},[555,573,574],{},"Restricts subclassing",[555,576,577],{},"No",[555,579,580,581,583],{},"Yes — ",[19,582,232],{}," list",[555,585,586],{},"N\u002FA",[535,588,589,592,595,598],{},[555,590,591],{},"Per-variant data",[555,593,594],{},"Same fields",[555,596,597],{},"Different per subtype",[555,599,600],{},"Same fields for all constants",[535,602,603,606,608,610],{},[555,604,605],{},"Exhaustive switch (Java 21)",[555,607,577],{},[555,609,560],{},[555,611,560],{},[15,613,614,615,617,618,620],{},"Use ",[39,616,548],{}," when all variants are structurally identical singletons. Use ",[39,619,545],{},"\nwhen variants carry different data or need their own logic.",[52,622,624],{"className":54,"code":623,"language":56,"meta":57,"style":57},"\u002F\u002F Enum: all constants have the same shape\nenum Status { PENDING, ACTIVE, CLOSED }\n\n\u002F\u002F Sealed: each variant has distinct structure\nsealed interface Event permits OrderPlaced, OrderShipped, OrderCancelled {}\nrecord OrderPlaced(String id, Instant at)       implements Event {}\nrecord OrderShipped(String id, String carrier)  implements Event {}\nrecord OrderCancelled(String id, String reason) implements Event {}\n",[19,625,626,631,658,662,667,693,708,724],{"__ignoreMap":57},[61,627,628],{"class":63,"line":64},[61,629,630],{"class":243},"\u002F\u002F Enum: all constants have the same shape\n",[61,632,633,635,638,641,645,647,650,652,655],{"class":63,"line":102},[61,634,548],{"class":67},[61,636,637],{"class":77}," Status",[61,639,640],{"class":87}," { ",[61,642,644],{"class":643},"sj4cs","PENDING",[61,646,88],{"class":87},[61,648,649],{"class":643},"ACTIVE",[61,651,88],{"class":87},[61,653,654],{"class":643},"CLOSED",[61,656,657],{"class":87}," }\n",[61,659,660],{"class":63,"line":109},[61,661,106],{"emptyLinePlaceholder":105},[61,663,664],{"class":63,"line":135},[61,665,666],{"class":243},"\u002F\u002F Sealed: each variant has distinct structure\n",[61,668,669,671,673,676,678,681,683,686,688,691],{"class":63,"line":163},[61,670,249],{"class":67},[61,672,74],{"class":67},[61,674,675],{"class":77}," Event",[61,677,81],{"class":67},[61,679,680],{"class":77}," OrderPlaced",[61,682,88],{"class":87},[61,684,685],{"class":77},"OrderShipped",[61,687,88],{"class":87},[61,689,690],{"class":77},"OrderCancelled",[61,692,99],{"class":87},[61,694,695,697,699,702,704,706],{"class":63,"line":424},[61,696,213],{"class":67},[61,698,680],{"class":77},[61,700,701],{"class":87},"(String id, Instant at)       ",[61,703,128],{"class":67},[61,705,675],{"class":77},[61,707,99],{"class":87},[61,709,710,712,715,718,720,722],{"class":63,"line":444},[61,711,213],{"class":67},[61,713,714],{"class":77}," OrderShipped",[61,716,717],{"class":87},"(String id, String carrier)  ",[61,719,128],{"class":67},[61,721,675],{"class":77},[61,723,99],{"class":87},[61,725,726,728,731,734,736,738],{"class":63,"line":460},[61,727,213],{"class":67},[61,729,730],{"class":77}," OrderCancelled",[61,732,733],{"class":87},"(String id, String reason) ",[61,735,128],{"class":67},[61,737,675],{"class":77},[61,739,99],{"class":87},[10,741,743],{"id":742},"exhaustive-switch-expressions","Exhaustive switch expressions",[15,745,746,747,750,751,754,755,758],{},"The killer feature of sealed types: because the compiler knows the complete set of\npermitted subtypes, a ",[19,748,749],{},"switch"," expression over a sealed type can be ",[39,752,753],{},"exhaustively\nverified at compile time",". No ",[19,756,757],{},"default"," branch needed:",[52,760,762],{"className":54,"code":761,"language":56,"meta":57,"style":57},"double area(Shape s) {\n    return switch (s) {\n        case Circle c        -> Math.PI * c.radius() * c.radius();\n        case Rectangle r     -> r.w() * r.h();\n        case Triangle t      -> 0.5 * t.base() * t.h();\n    };\n}\n",[19,763,764,774,785,820,846,877,882],{"__ignoreMap":57},[61,765,766,768,771],{"class":63,"line":64},[61,767,122],{"class":67},[61,769,770],{"class":77}," area",[61,772,773],{"class":87},"(Shape s) {\n",[61,775,776,779,782],{"class":63,"line":102},[61,777,778],{"class":67},"    return",[61,780,781],{"class":67}," switch",[61,783,784],{"class":87}," (s) {\n",[61,786,787,790,793,796,799,802,805,808,811,813,815,817],{"class":63,"line":109},[61,788,789],{"class":67},"        case",[61,791,792],{"class":87}," Circle c        ",[61,794,795],{"class":67},"->",[61,797,798],{"class":87}," Math.PI ",[61,800,801],{"class":67},"*",[61,803,804],{"class":87}," c.",[61,806,807],{"class":77},"radius",[61,809,810],{"class":87},"() ",[61,812,801],{"class":67},[61,814,804],{"class":87},[61,816,807],{"class":77},[61,818,819],{"class":87},"();\n",[61,821,822,824,827,829,832,835,837,839,841,844],{"class":63,"line":135},[61,823,789],{"class":67},[61,825,826],{"class":87}," Rectangle r     ",[61,828,795],{"class":67},[61,830,831],{"class":87}," r.",[61,833,834],{"class":77},"w",[61,836,810],{"class":87},[61,838,801],{"class":67},[61,840,831],{"class":87},[61,842,843],{"class":77},"h",[61,845,819],{"class":87},[61,847,848,850,853,855,858,861,864,867,869,871,873,875],{"class":63,"line":163},[61,849,789],{"class":67},[61,851,852],{"class":87}," Triangle t      ",[61,854,795],{"class":67},[61,856,857],{"class":643}," 0.5",[61,859,860],{"class":67}," *",[61,862,863],{"class":87}," t.",[61,865,866],{"class":77},"base",[61,868,810],{"class":87},[61,870,801],{"class":67},[61,872,863],{"class":87},[61,874,843],{"class":77},[61,876,819],{"class":87},[61,878,879],{"class":63,"line":424},[61,880,881],{"class":87},"    };\n",[61,883,884],{"class":63,"line":444},[61,885,307],{"class":87},[15,887,888,889,892,893,896,897,900,901,904],{},"If you later add ",[19,890,891],{},"record Pentagon(double side) implements Shape {}"," without updating\n",[19,894,895],{},"area()",", the compiler ",[39,898,899],{},"immediately reports an error"," at the switch. The exhaustive\ncheck guides every update — no runtime ",[19,902,903],{},"IllegalStateException"," from a forgotten case.",[10,906,908],{"id":907},"combining-sealed-classes-with-record-deconstruction","Combining sealed classes with record deconstruction",[15,910,911,912,915,916,918],{},"Java 21 allows ",[39,913,914],{},"record patterns"," inside ",[19,917,749],{},", destructuring component values inline:",[52,920,922],{"className":54,"code":921,"language":56,"meta":57,"style":57},"sealed interface Expr permits Num, Add, Mul {}\nrecord Num(int v)           implements Expr {}\nrecord Add(Expr l, Expr r)  implements Expr {}\nrecord Mul(Expr l, Expr r)  implements Expr {}\n\nint eval(Expr e) {\n    return switch (e) {\n        case Num(int v)          -> v;\n        case Add(Expr l, Expr r) -> eval(l) + eval(r);\n        case Mul(Expr l, Expr r) -> eval(l) * eval(r);\n    };\n}\n\nSystem.out.println(eval(new Add(new Num(3), new Mul(new Num(4), new Num(5))))); \u002F\u002F 23\n",[19,923,924,950,970,986,1001,1005,1015,1024,1042,1066,1086,1090,1095,1100],{"__ignoreMap":57},[61,925,926,928,930,933,935,938,940,943,945,948],{"class":63,"line":64},[61,927,249],{"class":67},[61,929,74],{"class":67},[61,931,932],{"class":77}," Expr",[61,934,81],{"class":67},[61,936,937],{"class":77}," Num",[61,939,88],{"class":87},[61,941,942],{"class":77},"Add",[61,944,88],{"class":87},[61,946,947],{"class":77},"Mul",[61,949,99],{"class":87},[61,951,952,954,956,958,961,964,966,968],{"class":63,"line":102},[61,953,213],{"class":67},[61,955,937],{"class":77},[61,957,119],{"class":87},[61,959,960],{"class":67},"int",[61,962,963],{"class":87}," v)           ",[61,965,128],{"class":67},[61,967,932],{"class":77},[61,969,99],{"class":87},[61,971,972,974,977,980,982,984],{"class":63,"line":109},[61,973,213],{"class":67},[61,975,976],{"class":77}," Add",[61,978,979],{"class":87},"(Expr l, Expr r)  ",[61,981,128],{"class":67},[61,983,932],{"class":77},[61,985,99],{"class":87},[61,987,988,990,993,995,997,999],{"class":63,"line":135},[61,989,213],{"class":67},[61,991,992],{"class":77}," Mul",[61,994,979],{"class":87},[61,996,128],{"class":67},[61,998,932],{"class":77},[61,1000,99],{"class":87},[61,1002,1003],{"class":63,"line":163},[61,1004,106],{"emptyLinePlaceholder":105},[61,1006,1007,1009,1012],{"class":63,"line":424},[61,1008,960],{"class":67},[61,1010,1011],{"class":77}," eval",[61,1013,1014],{"class":87},"(Expr e) {\n",[61,1016,1017,1019,1021],{"class":63,"line":444},[61,1018,778],{"class":67},[61,1020,781],{"class":67},[61,1022,1023],{"class":87}," (e) {\n",[61,1025,1026,1028,1030,1032,1034,1037,1039],{"class":63,"line":460},[61,1027,789],{"class":67},[61,1029,937],{"class":77},[61,1031,119],{"class":87},[61,1033,960],{"class":67},[61,1035,1036],{"class":87}," v)          ",[61,1038,795],{"class":67},[61,1040,1041],{"class":87}," v;\n",[61,1043,1044,1046,1048,1051,1053,1055,1058,1061,1063],{"class":63,"line":477},[61,1045,789],{"class":67},[61,1047,976],{"class":77},[61,1049,1050],{"class":87},"(Expr l, Expr r) ",[61,1052,795],{"class":67},[61,1054,1011],{"class":77},[61,1056,1057],{"class":87},"(l) ",[61,1059,1060],{"class":67},"+",[61,1062,1011],{"class":77},[61,1064,1065],{"class":87},"(r);\n",[61,1067,1068,1070,1072,1074,1076,1078,1080,1082,1084],{"class":63,"line":482},[61,1069,789],{"class":67},[61,1071,992],{"class":77},[61,1073,1050],{"class":87},[61,1075,795],{"class":67},[61,1077,1011],{"class":77},[61,1079,1057],{"class":87},[61,1081,801],{"class":67},[61,1083,1011],{"class":77},[61,1085,1065],{"class":87},[61,1087,1088],{"class":63,"line":502},[61,1089,881],{"class":87},[61,1091,1093],{"class":63,"line":1092},12,[61,1094,307],{"class":87},[61,1096,1098],{"class":63,"line":1097},13,[61,1099,106],{"emptyLinePlaceholder":105},[61,1101,1103,1106,1109,1111,1114,1116,1119,1121,1123,1125,1127,1129,1132,1135,1137,1139,1141,1143,1145,1147,1150,1152,1154,1156,1158,1161,1164],{"class":63,"line":1102},14,[61,1104,1105],{"class":87},"System.out.",[61,1107,1108],{"class":77},"println",[61,1110,119],{"class":87},[61,1112,1113],{"class":77},"eval",[61,1115,119],{"class":87},[61,1117,1118],{"class":67},"new",[61,1120,976],{"class":77},[61,1122,119],{"class":87},[61,1124,1118],{"class":67},[61,1126,937],{"class":77},[61,1128,119],{"class":87},[61,1130,1131],{"class":643},"3",[61,1133,1134],{"class":87},"), ",[61,1136,1118],{"class":67},[61,1138,992],{"class":77},[61,1140,119],{"class":87},[61,1142,1118],{"class":67},[61,1144,937],{"class":77},[61,1146,119],{"class":87},[61,1148,1149],{"class":643},"4",[61,1151,1134],{"class":87},[61,1153,1118],{"class":67},[61,1155,937],{"class":77},[61,1157,119],{"class":87},[61,1159,1160],{"class":643},"5",[61,1162,1163],{"class":87},"))))); ",[61,1165,1166],{"class":243},"\u002F\u002F 23\n",[15,1168,1169,1170,1173],{},"No visitor interface, no ",[19,1171,1172],{},"accept()"," methods, no double dispatch — the compiler handles\nexhaustiveness and the pattern matching handles dispatch. This models recursive ASTs,\nJSON documents, domain events, or any sum type cleanly.",[10,1175,1177],{"id":1176},"modelling-algebraic-data-types-adts","Modelling algebraic data types (ADTs)",[15,1179,1180,1181,1184,1185,1188],{},"Sealed interfaces are Java's answer to sum types (also called tagged unions or variant\ntypes) in languages like Rust, Haskell, or Kotlin. A ",[39,1182,1183],{},"sealed interface + record\nvariants"," describes a type that is ",[28,1186,1187],{},"exactly one of"," a fixed set of shapes:",[52,1190,1192],{"className":54,"code":1191,"language":56,"meta":57,"style":57},"\u002F\u002F Option type:\nsealed interface Option\u003CT> permits Option.Some, Option.None {\n    record Some\u003CT>(T value) implements Option\u003CT> {}\n    record None\u003CT>()        implements Option\u003CT> {}\n}\n\n\u002F\u002F Result type:\nsealed interface Result\u003CT> permits Result.Ok, Result.Err {\n    record Ok\u003CT>(T value)    implements Result\u003CT> {}\n    record Err\u003CT>(String msg) implements Result\u003CT> {}\n}\n\nResult\u003CInteger> parse(String s) {\n    try { return new Result.Ok\u003C>(Integer.parseInt(s)); }\n    catch (NumberFormatException e) { return new Result.Err\u003C>(e.getMessage()); }\n}\n\n\u002F\u002F Caller must handle both branches:\nswitch (parse(\"abc\")) {\n    case Result.Ok\u003CInteger>(int v)   -> System.out.println(\"Got: \" + v);\n    case Result.Err\u003CInteger>(String m) -> System.err.println(\"Error: \" + m);\n}\n",[19,1193,1194,1199,1228,1252,1276,1280,1284,1289,1315,1338,1361,1365,1369,1388,1410,1439,1444,1449,1455,1475,1514,1548],{"__ignoreMap":57},[61,1195,1196],{"class":63,"line":64},[61,1197,1198],{"class":243},"\u002F\u002F Option type:\n",[61,1200,1201,1203,1205,1208,1210,1212,1215,1217,1220,1222,1225],{"class":63,"line":102},[61,1202,249],{"class":67},[61,1204,74],{"class":67},[61,1206,1207],{"class":77}," Option",[61,1209,258],{"class":87},[61,1211,261],{"class":67},[61,1213,1214],{"class":87},"> ",[61,1216,232],{"class":67},[61,1218,1219],{"class":77}," Option.Some",[61,1221,88],{"class":87},[61,1223,1224],{"class":77},"Option.None",[61,1226,1227],{"class":87}," {\n",[61,1229,1230,1232,1235,1237,1239,1242,1244,1246,1248,1250],{"class":63,"line":109},[61,1231,269],{"class":67},[61,1233,1234],{"class":77}," Some",[61,1236,258],{"class":87},[61,1238,261],{"class":67},[61,1240,1241],{"class":87},">(T value) ",[61,1243,128],{"class":67},[61,1245,1207],{"class":77},[61,1247,258],{"class":87},[61,1249,261],{"class":67},[61,1251,284],{"class":87},[61,1253,1254,1256,1259,1261,1263,1266,1268,1270,1272,1274],{"class":63,"line":135},[61,1255,269],{"class":67},[61,1257,1258],{"class":77}," None",[61,1260,258],{"class":87},[61,1262,261],{"class":67},[61,1264,1265],{"class":87},">()        ",[61,1267,128],{"class":67},[61,1269,1207],{"class":77},[61,1271,258],{"class":87},[61,1273,261],{"class":67},[61,1275,284],{"class":87},[61,1277,1278],{"class":63,"line":163},[61,1279,307],{"class":87},[61,1281,1282],{"class":63,"line":424},[61,1283,106],{"emptyLinePlaceholder":105},[61,1285,1286],{"class":63,"line":444},[61,1287,1288],{"class":243},"\u002F\u002F Result type:\n",[61,1290,1291,1293,1295,1297,1299,1301,1303,1305,1308,1310,1313],{"class":63,"line":460},[61,1292,249],{"class":67},[61,1294,74],{"class":67},[61,1296,255],{"class":77},[61,1298,258],{"class":87},[61,1300,261],{"class":67},[61,1302,1214],{"class":87},[61,1304,232],{"class":67},[61,1306,1307],{"class":77}," Result.Ok",[61,1309,88],{"class":87},[61,1311,1312],{"class":77},"Result.Err",[61,1314,1227],{"class":87},[61,1316,1317,1319,1321,1323,1325,1328,1330,1332,1334,1336],{"class":63,"line":477},[61,1318,269],{"class":67},[61,1320,272],{"class":77},[61,1322,258],{"class":87},[61,1324,261],{"class":67},[61,1326,1327],{"class":87},">(T value)    ",[61,1329,128],{"class":67},[61,1331,255],{"class":77},[61,1333,258],{"class":87},[61,1335,261],{"class":67},[61,1337,284],{"class":87},[61,1339,1340,1342,1344,1346,1348,1351,1353,1355,1357,1359],{"class":63,"line":482},[61,1341,269],{"class":67},[61,1343,291],{"class":77},[61,1345,258],{"class":87},[61,1347,261],{"class":67},[61,1349,1350],{"class":87},">(String msg) ",[61,1352,128],{"class":67},[61,1354,255],{"class":77},[61,1356,258],{"class":87},[61,1358,261],{"class":67},[61,1360,284],{"class":87},[61,1362,1363],{"class":63,"line":502},[61,1364,307],{"class":87},[61,1366,1367],{"class":63,"line":1092},[61,1368,106],{"emptyLinePlaceholder":105},[61,1370,1371,1374,1376,1379,1382,1385],{"class":63,"line":1097},[61,1372,1373],{"class":87},"Result",[61,1375,258],{"class":67},[61,1377,1378],{"class":87},"Integer",[61,1380,1381],{"class":67},">",[61,1383,1384],{"class":77}," parse",[61,1386,1387],{"class":87},"(String s) {\n",[61,1389,1390,1393,1395,1398,1401,1404,1407],{"class":63,"line":1102},[61,1391,1392],{"class":67},"    try",[61,1394,640],{"class":87},[61,1396,1397],{"class":67},"return",[61,1399,1400],{"class":67}," new",[61,1402,1403],{"class":87}," Result.Ok\u003C>(Integer.",[61,1405,1406],{"class":77},"parseInt",[61,1408,1409],{"class":87},"(s)); }\n",[61,1411,1413,1416,1419,1423,1426,1428,1430,1433,1436],{"class":63,"line":1412},15,[61,1414,1415],{"class":67},"    catch",[61,1417,1418],{"class":87}," (NumberFormatException ",[61,1420,1422],{"class":1421},"s4XuR","e",[61,1424,1425],{"class":87},") { ",[61,1427,1397],{"class":67},[61,1429,1400],{"class":67},[61,1431,1432],{"class":87}," Result.Err\u003C>(e.",[61,1434,1435],{"class":77},"getMessage",[61,1437,1438],{"class":87},"()); }\n",[61,1440,1442],{"class":63,"line":1441},16,[61,1443,307],{"class":87},[61,1445,1447],{"class":63,"line":1446},17,[61,1448,106],{"emptyLinePlaceholder":105},[61,1450,1452],{"class":63,"line":1451},18,[61,1453,1454],{"class":243},"\u002F\u002F Caller must handle both branches:\n",[61,1456,1458,1460,1463,1466,1468,1472],{"class":63,"line":1457},19,[61,1459,749],{"class":67},[61,1461,1462],{"class":87}," (",[61,1464,1465],{"class":77},"parse",[61,1467,119],{"class":87},[61,1469,1471],{"class":1470},"sZZnC","\"abc\"",[61,1473,1474],{"class":87},")) {\n",[61,1476,1478,1481,1483,1485,1487,1489,1491,1493,1496,1498,1501,1503,1505,1508,1511],{"class":63,"line":1477},20,[61,1479,1480],{"class":67},"    case",[61,1482,1307],{"class":87},[61,1484,258],{"class":67},[61,1486,1378],{"class":87},[61,1488,1381],{"class":67},[61,1490,119],{"class":87},[61,1492,960],{"class":67},[61,1494,1495],{"class":87}," v)   ",[61,1497,795],{"class":67},[61,1499,1500],{"class":87}," System.out.",[61,1502,1108],{"class":77},[61,1504,119],{"class":87},[61,1506,1507],{"class":1470},"\"Got: \"",[61,1509,1510],{"class":67}," +",[61,1512,1513],{"class":87}," v);\n",[61,1515,1517,1519,1522,1524,1526,1528,1531,1533,1536,1538,1540,1543,1545],{"class":63,"line":1516},21,[61,1518,1480],{"class":67},[61,1520,1521],{"class":87}," Result.Err",[61,1523,258],{"class":67},[61,1525,1378],{"class":87},[61,1527,1381],{"class":67},[61,1529,1530],{"class":87},"(String m) ",[61,1532,795],{"class":67},[61,1534,1535],{"class":87}," System.err.",[61,1537,1108],{"class":77},[61,1539,119],{"class":87},[61,1541,1542],{"class":1470},"\"Error: \"",[61,1544,1510],{"class":67},[61,1546,1547],{"class":87}," m);\n",[61,1549,1551],{"class":63,"line":1550},22,[61,1552,307],{"class":87},[15,1554,1555,1556,1559,1560,1562,1563,1566],{},"Compare this to returning ",[19,1557,1558],{},"null"," or throwing an exception — sealed ",[19,1561,1373],{}," makes the\nerror path ",[39,1564,1565],{},"explicit in the type system",". Callers can't ignore it.",[10,1568,1570],{"id":1569},"sealed-interfaces-in-the-jdk","Sealed interfaces in the JDK",[15,1572,1573,1574,1577,1578,88,1581,88,1584,1587,1588,1591],{},"The JDK itself uses sealed types. ",[19,1575,1576],{},"java.lang.constant.ConstantDesc"," is a sealed\ninterface with permitted types ",[19,1579,1580],{},"ClassDesc",[19,1582,1583],{},"MethodTypeDesc",[19,1585,1586],{},"MethodHandleDesc",", and\n",[19,1589,1590],{},"DynamicConstantDesc",". The constant pool representation of class files is modelled as\na closed hierarchy — a natural fit.",[10,1593,1595],{"id":1594},"runtime-inspection-getpermittedsubclasses","Runtime inspection: getPermittedSubclasses()",[15,1597,1598],{},"You can query the permitted subtypes at runtime, useful for frameworks that need to\nenumerate all variants for serialization or schema generation:",[52,1600,1602],{"className":54,"code":1601,"language":56,"meta":57,"style":57},"Class\u003C?>[] subs = Shape.class.getPermittedSubclasses();\n\u002F\u002F [class Circle, class Rectangle, class Triangle]\n",[19,1603,1604,1626],{"__ignoreMap":57},[61,1605,1606,1609,1612,1615,1618,1621,1624],{"class":63,"line":64},[61,1607,1608],{"class":87},"Class\u003C",[61,1610,1611],{"class":67},"?",[61,1613,1614],{"class":87},">[] subs ",[61,1616,1617],{"class":67},"=",[61,1619,1620],{"class":87}," Shape.class.",[61,1622,1623],{"class":77},"getPermittedSubclasses",[61,1625,819],{"class":87},[61,1627,1628],{"class":63,"line":102},[61,1629,1630],{"class":243},"\u002F\u002F [class Circle, class Rectangle, class Triangle]\n",[15,1632,1633],{},"This removes the need for classpath scanning or maintaining a hand-rolled registry of\nsubtypes.",[10,1635,1637],{"id":1636},"sealed-classes-replace-the-visitor-pattern","Sealed classes replace the Visitor pattern",[15,1639,1640],{},"The Visitor pattern existed to add new operations over a closed type hierarchy without\nmodifying each class. Sealed types + switch pattern matching achieve the same goal with\nless ceremony:",[52,1642,1644],{"className":54,"code":1643,"language":56,"meta":57,"style":57},"\u002F\u002F Visitor pattern — 30+ lines of boilerplate:\ninterface ShapeVisitor\u003CR> {\n    R visitCircle(Circle c);\n    R visitRectangle(Rectangle r);\n}\n\u002F\u002F Each Shape must implement accept(ShapeVisitor\u003CR>)...\n\n\u002F\u002F Sealed + switch — same result, no boilerplate:\ndouble area(Shape s) {\n    return switch (s) {\n        case Circle c    -> Math.PI * c.radius() * c.radius();\n        case Rectangle r -> r.w() * r.h();\n    };\n}\n",[19,1645,1646,1651,1666,1683,1698,1702,1707,1711,1716,1724,1732,1759,1782,1786],{"__ignoreMap":57},[61,1647,1648],{"class":63,"line":64},[61,1649,1650],{"class":243},"\u002F\u002F Visitor pattern — 30+ lines of boilerplate:\n",[61,1652,1653,1656,1659,1661,1664],{"class":63,"line":102},[61,1654,1655],{"class":67},"interface",[61,1657,1658],{"class":77}," ShapeVisitor",[61,1660,258],{"class":87},[61,1662,1663],{"class":67},"R",[61,1665,264],{"class":87},[61,1667,1668,1671,1674,1677,1680],{"class":63,"line":109},[61,1669,1670],{"class":87},"    R ",[61,1672,1673],{"class":77},"visitCircle",[61,1675,1676],{"class":87},"(Circle ",[61,1678,1679],{"class":1421},"c",[61,1681,1682],{"class":87},");\n",[61,1684,1685,1687,1690,1693,1696],{"class":63,"line":135},[61,1686,1670],{"class":87},[61,1688,1689],{"class":77},"visitRectangle",[61,1691,1692],{"class":87},"(Rectangle ",[61,1694,1695],{"class":1421},"r",[61,1697,1682],{"class":87},[61,1699,1700],{"class":63,"line":163},[61,1701,307],{"class":87},[61,1703,1704],{"class":63,"line":424},[61,1705,1706],{"class":243},"\u002F\u002F Each Shape must implement accept(ShapeVisitor\u003CR>)...\n",[61,1708,1709],{"class":63,"line":444},[61,1710,106],{"emptyLinePlaceholder":105},[61,1712,1713],{"class":63,"line":460},[61,1714,1715],{"class":243},"\u002F\u002F Sealed + switch — same result, no boilerplate:\n",[61,1717,1718,1720,1722],{"class":63,"line":477},[61,1719,122],{"class":67},[61,1721,770],{"class":77},[61,1723,773],{"class":87},[61,1725,1726,1728,1730],{"class":63,"line":482},[61,1727,778],{"class":67},[61,1729,781],{"class":67},[61,1731,784],{"class":87},[61,1733,1734,1736,1739,1741,1743,1745,1747,1749,1751,1753,1755,1757],{"class":63,"line":502},[61,1735,789],{"class":67},[61,1737,1738],{"class":87}," Circle c    ",[61,1740,795],{"class":67},[61,1742,798],{"class":87},[61,1744,801],{"class":67},[61,1746,804],{"class":87},[61,1748,807],{"class":77},[61,1750,810],{"class":87},[61,1752,801],{"class":67},[61,1754,804],{"class":87},[61,1756,807],{"class":77},[61,1758,819],{"class":87},[61,1760,1761,1763,1766,1768,1770,1772,1774,1776,1778,1780],{"class":63,"line":1092},[61,1762,789],{"class":67},[61,1764,1765],{"class":87}," Rectangle r ",[61,1767,795],{"class":67},[61,1769,831],{"class":87},[61,1771,834],{"class":77},[61,1773,810],{"class":87},[61,1775,801],{"class":67},[61,1777,831],{"class":87},[61,1779,843],{"class":77},[61,1781,819],{"class":87},[61,1783,1784],{"class":63,"line":1097},[61,1785,881],{"class":87},[61,1787,1788],{"class":63,"line":1102},[61,1789,307],{"class":87},[15,1791,1792,1793,1795,1796,1798],{},"Adding a new operation means writing a new ",[19,1794,749],{},"; adding a new variant breaks every\nexisting ",[19,1797,749],{}," at compile time — the compiler tells you exactly what to update.",[10,1800,1802],{"id":1801},"recap","Recap",[15,1804,1805,1807,1808,1810,1811,1813,1814,1816,1817,1819,1820,1823,1824,1830,1831,1834],{},[39,1806,41],{}," (Java 17) close an inheritance hierarchy to a named ",[19,1809,232],{}," list.\nEach permitted subtype must be ",[19,1812,217],{}," (leaf), ",[19,1815,249],{}," (further restricted), or\n",[19,1818,348],{}," (deliberately re-opened). Sealed interfaces combine with ",[39,1821,1822],{},"records"," to\nmodel algebraic sum types — each variant carries different data. The payoff is\n",[39,1825,1826,1827,1829],{},"exhaustive ",[19,1828,749],{}," expressions"," (Java 21) where the compiler verifies every case is\nhandled, turning missed-variant runtime bugs into compile errors. The combination of\nsealed interfaces + records + switch pattern matching replaces the Visitor pattern with\nfar less boilerplate, and ",[19,1832,1833],{},"getPermittedSubclasses()"," lets frameworks discover the closed\ntype set at runtime.",[1836,1837,1838],"style",{},"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 .sVt8B, html code.shiki .sVt8B{--shiki-default:#24292E;--shiki-dark:#E1E4E8}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 .sJ8bj, html code.shiki .sJ8bj{--shiki-default:#6A737D;--shiki-dark:#6A737D}html pre.shiki code .sj4cs, html code.shiki .sj4cs{--shiki-default:#005CC5;--shiki-dark:#79B8FF}html pre.shiki code .s4XuR, html code.shiki .s4XuR{--shiki-default:#E36209;--shiki-dark:#FFAB70}html pre.shiki code .sZZnC, html code.shiki .sZZnC{--shiki-default:#032F62;--shiki-dark:#9ECBFF}",{"title":57,"searchDepth":102,"depth":102,"links":1840},[1841,1842,1843,1844,1845,1846,1847,1848,1849,1850,1851,1852],{"id":12,"depth":102,"text":13},{"id":49,"depth":102,"text":50},{"id":221,"depth":102,"text":222},{"id":320,"depth":102,"text":321},{"id":526,"depth":102,"text":527},{"id":742,"depth":102,"text":743},{"id":907,"depth":102,"text":908},{"id":1176,"depth":102,"text":1177},{"id":1569,"depth":102,"text":1570},{"id":1594,"depth":102,"text":1595},{"id":1636,"depth":102,"text":1637},{"id":1801,"depth":102,"text":1802},"How Java sealed classes work — the permits clause, final\u002Fsealed\u002Fnon-sealed modifiers, sealed interfaces, exhaustive switch expressions, algebraic data types with records, sealed vs enum vs abstract class, and inspecting permitted subtypes at runtime.","medium","md","Java",{},"\u002Fblog\u002Fjava-sealed-classes","\u002Fjava\u002Fmodern-java\u002Fsealed-classes",{"title":5,"description":1853},"blog\u002Fjava-sealed-classes","Sealed Classes","Modern Java","modern-java","2026-06-20","A8NW8DwqIzjwQILxWnfF8VfEPq9Al8BtEoGIrdTvi3Y",1782244089575]