[{"data":1,"prerenderedAt":1173},["ShallowReactive",2],{"blog-\u002Fblog\u002Fjava-custom-exceptions":3},{"id":4,"title":5,"body":6,"description":1159,"difficulty":1160,"extension":1161,"framework":1162,"frameworkSlug":89,"meta":1163,"navigation":131,"order":128,"path":1164,"qaPath":1165,"seo":1166,"stem":1167,"subtopic":1168,"topic":1169,"topicSlug":1170,"updated":1171,"__hash__":1172},"blog\u002Fblog\u002Fjava-custom-exceptions.md","Java Custom Exceptions — Design, Chaining & Best Practices",{"type":7,"value":8,"toc":1147},"minimark",[9,14,45,49,84,156,171,175,188,308,319,323,342,416,438,442,449,560,575,579,608,720,741,745,762,848,870,874,900,944,958,962,979,1050,1067,1071,1143],[10,11,13],"h2",{"id":12},"when-a-custom-exception-earns-its-keep","When a custom exception earns its keep",[15,16,17,18,22,23,27,28,31,32,36,37,40,41,44],"p",{},"A custom exception is not about decorating your error messages — it is about giving a\nfailure a ",[19,20,21],"strong",{},"name and a type"," that callers can act on. The moment a caller needs to\n",[24,25,26],"em",{},"react differently"," to one failure than to every other failure, or needs ",[19,29,30],{},"structured\ndata"," out of it (the offending id, an error code), a dedicated type pays for itself.\n",[33,34,35],"code",{},"throw new RuntimeException(\"order 42 not found\")"," forces every caller to parse a string;\n",[33,38,39],{},"throw new OrderNotFoundException(42)"," lets them write a precise ",[33,42,43],{},"catch"," and pull the id\nback out. This guide is about the design decisions around those classes — which parent to\nextend, how to preserve diagnostics, and the conventions that keep an exception hierarchy\nmaintainable rather than noisy.",[10,46,48],{"id":47},"checked-or-unchecked-pick-the-parent-first","Checked or unchecked: pick the parent first",[15,50,51,52,55,56,59,60,63,64,69,70,73,74,79,80,83],{},"The single most consequential decision is the ",[19,53,54],{},"superclass",", because it encodes whether\nthe error is ",[24,57,58],{},"expected"," or a ",[24,61,62],{},"defect",". Extend ",[19,65,66],{},[33,67,68],{},"Exception"," to make it ",[19,71,72],{},"checked"," —\nthe compiler forces callers to catch or declare it. Extend ",[19,75,76],{},[33,77,78],{},"RuntimeException"," to make\nit ",[19,81,82],{},"unchecked"," — no compiler obligation at all.",[85,86,91],"pre",{"className":87,"code":88,"language":89,"meta":90,"style":90},"language-java shiki shiki-themes github-light github-dark","\u002F\u002F checked: a recoverable, anticipated condition the caller should plan for\npublic class ConfigParseException extends Exception { }\n\n\u002F\u002F unchecked: a programming error or violated precondition — a bug to fix, not handle\npublic class InvalidOrderStateException extends RuntimeException { }\n","java","",[33,92,93,102,126,133,139],{"__ignoreMap":90},[94,95,98],"span",{"class":96,"line":97},"line",1,[94,99,101],{"class":100},"sJ8bj","\u002F\u002F checked: a recoverable, anticipated condition the caller should plan for\n",[94,103,105,109,112,116,119,122],{"class":96,"line":104},2,[94,106,108],{"class":107},"szBVR","public",[94,110,111],{"class":107}," class",[94,113,115],{"class":114},"sScJk"," ConfigParseException",[94,117,118],{"class":107}," extends",[94,120,121],{"class":114}," Exception",[94,123,125],{"class":124},"sVt8B"," { }\n",[94,127,129],{"class":96,"line":128},3,[94,130,132],{"emptyLinePlaceholder":131},true,"\n",[94,134,136],{"class":96,"line":135},4,[94,137,138],{"class":100},"\u002F\u002F unchecked: a programming error or violated precondition — a bug to fix, not handle\n",[94,140,142,144,146,149,151,154],{"class":96,"line":141},5,[94,143,108],{"class":107},[94,145,111],{"class":107},[94,147,148],{"class":114}," InvalidOrderStateException",[94,150,118],{"class":107},[94,152,153],{"class":114}," RuntimeException",[94,155,125],{"class":124},[15,157,158,159,162,163,166,167,170],{},"The litmus test is ",[19,160,161],{},"\"can the caller do something useful about it?\""," A missing config\nfile or a failed network call is recoverable — lean checked. A ",[33,164,165],{},"null"," argument or an\nillegal state is a defect the caller cannot meaningfully recover from — lean unchecked.\nChoosing the parent first means every later decision (whether to declare ",[33,168,169],{},"throws",", how\ncallers handle it) follows naturally.",[10,172,174],{"id":173},"the-four-standard-constructors","The four standard constructors",[15,176,177,178,180,181,183,184,187],{},"Mirror the four constructors that ",[33,179,68],{}," and ",[33,182,78],{}," expose, so callers\ncan supply a message, a cause, or both. The two cause-bearing forms are the ones people\nforget — and they are the most important, because they are what makes ",[19,185,186],{},"chaining","\npossible.",[85,189,191],{"className":87,"code":190,"language":89,"meta":90,"style":90},"public class PaymentException extends RuntimeException {\n  public PaymentException() { super(); }\n  public PaymentException(String message) { super(message); }\n  public PaymentException(String message, Throwable cause) {\n    super(message, cause);                 \u002F\u002F message + root cause — the workhorse\n  }\n  public PaymentException(Throwable cause) { super(cause); }\n}\n",[33,192,193,209,226,247,266,277,283,302],{"__ignoreMap":90},[94,194,195,197,199,202,204,206],{"class":96,"line":97},[94,196,108],{"class":107},[94,198,111],{"class":107},[94,200,201],{"class":114}," PaymentException",[94,203,118],{"class":107},[94,205,153],{"class":114},[94,207,208],{"class":124}," {\n",[94,210,211,214,216,219,223],{"class":96,"line":104},[94,212,213],{"class":107},"  public",[94,215,201],{"class":114},[94,217,218],{"class":124},"() { ",[94,220,222],{"class":221},"sj4cs","super",[94,224,225],{"class":124},"(); }\n",[94,227,228,230,232,235,239,242,244],{"class":96,"line":128},[94,229,213],{"class":107},[94,231,201],{"class":114},[94,233,234],{"class":124},"(String ",[94,236,238],{"class":237},"s4XuR","message",[94,240,241],{"class":124},") { ",[94,243,222],{"class":221},[94,245,246],{"class":124},"(message); }\n",[94,248,249,251,253,255,257,260,263],{"class":96,"line":135},[94,250,213],{"class":107},[94,252,201],{"class":114},[94,254,234],{"class":124},[94,256,238],{"class":237},[94,258,259],{"class":124},", Throwable ",[94,261,262],{"class":237},"cause",[94,264,265],{"class":124},") {\n",[94,267,268,271,274],{"class":96,"line":141},[94,269,270],{"class":221},"    super",[94,272,273],{"class":124},"(message, cause);                 ",[94,275,276],{"class":100},"\u002F\u002F message + root cause — the workhorse\n",[94,278,280],{"class":96,"line":279},6,[94,281,282],{"class":124},"  }\n",[94,284,286,288,290,293,295,297,299],{"class":96,"line":285},7,[94,287,213],{"class":107},[94,289,201],{"class":114},[94,291,292],{"class":124},"(Throwable ",[94,294,262],{"class":237},[94,296,241],{"class":124},[94,298,222],{"class":221},[94,300,301],{"class":124},"(cause); }\n",[94,303,305],{"class":96,"line":304},8,[94,306,307],{"class":124},"}\n",[15,309,310,311,314,315,318],{},"If you provide nothing else, provide at minimum ",[33,312,313],{},"(String message)"," and\n",[33,316,317],{},"(String message, Throwable cause)",". The latter is what lets you wrap a lower-level\nfailure without discarding the line that actually broke.",[10,320,322],{"id":321},"chaining-never-throw-away-the-cause","Chaining: never throw away the cause",[15,324,325,328,329,331,332,337,338,341],{},[19,326,327],{},"Exception chaining"," is wrapping a low-level exception inside a higher-level one while\nkeeping the original as the ",[19,330,262],{},". It lets you raise a meaningful domain exception\nwithout losing the root-cause diagnostics. The cause surfaces in the stack trace under a\n",[19,333,334],{},[33,335,336],{},"Caused by:"," line, so you see both the abstract failure ",[24,339,340],{},"and"," the SQL error that\ntriggered it.",[85,343,345],{"className":87,"code":344,"language":89,"meta":90,"style":90},"try {\n  return jdbc.query(sql);\n} catch (SQLException e) {\n  \u002F\u002F wrap to your abstraction, but pass e as the cause so the trace survives\n  throw new RepositoryException(\"failed to load user \" + id, e);\n}\n",[33,346,347,354,368,383,388,412],{"__ignoreMap":90},[94,348,349,352],{"class":96,"line":97},[94,350,351],{"class":107},"try",[94,353,208],{"class":124},[94,355,356,359,362,365],{"class":96,"line":104},[94,357,358],{"class":107},"  return",[94,360,361],{"class":124}," jdbc.",[94,363,364],{"class":114},"query",[94,366,367],{"class":124},"(sql);\n",[94,369,370,373,375,378,381],{"class":96,"line":128},[94,371,372],{"class":124},"} ",[94,374,43],{"class":107},[94,376,377],{"class":124}," (SQLException ",[94,379,380],{"class":237},"e",[94,382,265],{"class":124},[94,384,385],{"class":96,"line":135},[94,386,387],{"class":100},"  \u002F\u002F wrap to your abstraction, but pass e as the cause so the trace survives\n",[94,389,390,393,396,399,402,406,409],{"class":96,"line":141},[94,391,392],{"class":107},"  throw",[94,394,395],{"class":107}," new",[94,397,398],{"class":114}," RepositoryException",[94,400,401],{"class":124},"(",[94,403,405],{"class":404},"sZZnC","\"failed to load user \"",[94,407,408],{"class":107}," +",[94,410,411],{"class":124}," id, e);\n",[94,413,414],{"class":96,"line":279},[94,415,307],{"class":124},[15,417,418,419,422,423,425,426,429,430,433,434,437],{},"The classic mistake is ",[33,420,421],{},"throw new RepositoryException(\"failed to load user \" + id)"," with\nno ",[33,424,380],{},". The new exception's trace now starts at the ",[33,427,428],{},"throw"," site, and the line that truly\nfailed is gone forever. ",[19,431,432],{},"When you rethrow, always carry the cause."," If a class predates\nthe cause constructors, ",[33,435,436],{},"initCause(e)"," does the same job — but it may be called exactly\nonce, so the constructor form is strictly better when available.",[10,439,441],{"id":440},"carrying-structured-data-with-custom-fields","Carrying structured data with custom fields",[15,443,444,445,448],{},"Put any data a handler might need into ",[19,446,447],{},"typed, final fields"," populated through the\nconstructor and exposed via getters — not buried in the message string. This is half the\nreason custom exceptions exist.",[85,450,452],{"className":87,"code":451,"language":89,"meta":90,"style":90},"public class OrderNotFoundException extends RuntimeException {\n  private final long orderId;                 \u002F\u002F immutable: an exception is a one-shot value\n\n  public OrderNotFoundException(long orderId) {\n    super(\"order not found: \" + orderId);\n    this.orderId = orderId;\n  }\n  public long getOrderId() { return orderId; }\n}\n",[33,453,454,469,486,490,506,520,534,538,555],{"__ignoreMap":90},[94,455,456,458,460,463,465,467],{"class":96,"line":97},[94,457,108],{"class":107},[94,459,111],{"class":107},[94,461,462],{"class":114}," OrderNotFoundException",[94,464,118],{"class":107},[94,466,153],{"class":114},[94,468,208],{"class":124},[94,470,471,474,477,480,483],{"class":96,"line":104},[94,472,473],{"class":107},"  private",[94,475,476],{"class":107}," final",[94,478,479],{"class":107}," long",[94,481,482],{"class":124}," orderId;                 ",[94,484,485],{"class":100},"\u002F\u002F immutable: an exception is a one-shot value\n",[94,487,488],{"class":96,"line":128},[94,489,132],{"emptyLinePlaceholder":131},[94,491,492,494,496,498,501,504],{"class":96,"line":135},[94,493,213],{"class":107},[94,495,462],{"class":114},[94,497,401],{"class":124},[94,499,500],{"class":107},"long",[94,502,503],{"class":237}," orderId",[94,505,265],{"class":124},[94,507,508,510,512,515,517],{"class":96,"line":141},[94,509,270],{"class":221},[94,511,401],{"class":124},[94,513,514],{"class":404},"\"order not found: \"",[94,516,408],{"class":107},[94,518,519],{"class":124}," orderId);\n",[94,521,522,525,528,531],{"class":96,"line":279},[94,523,524],{"class":221},"    this",[94,526,527],{"class":124},".orderId ",[94,529,530],{"class":107},"=",[94,532,533],{"class":124}," orderId;\n",[94,535,536],{"class":96,"line":285},[94,537,282],{"class":124},[94,539,540,542,544,547,549,552],{"class":96,"line":304},[94,541,213],{"class":107},[94,543,479],{"class":107},[94,545,546],{"class":114}," getOrderId",[94,548,218],{"class":124},[94,550,551],{"class":107},"return",[94,553,554],{"class":124}," orderId; }\n",[94,556,558],{"class":96,"line":557},9,[94,559,307],{"class":124},[15,561,562,563,566,567,570,571,574],{},"Now a caller can write ",[33,564,565],{},"catch (OrderNotFoundException e) { retry(e.getOrderId()); }"," and\nget the id without scraping the message. Keep the fields ",[33,568,569],{},"final"," and defensively copy any\nmutable inputs (",[33,572,573],{},"List.copyOf(...)",") — an exception describes a moment in time and should\nnever be mutated after it is thrown.",[10,576,578],{"id":577},"naming-and-serialversionuid","Naming and serialVersionUID",[15,580,581,582,587,588,591,592,595,596,599,600,603,604,607],{},"Two conventions keep a hierarchy professional. First, ",[19,583,584,585],{},"name the class for the problem and\nsuffix it with ",[33,586,68],{}," — ",[33,589,590],{},"InsufficientFundsException",", not ",[33,593,594],{},"FundsProblem"," or\n",[33,597,598],{},"CheckBalanceException"," (name the condition, not the throwing method). It mirrors the JDK's\nown ",[33,601,602],{},"IllegalArgumentException"," \u002F ",[33,605,606],{},"IOException"," and makes exceptions instantly recognizable\nin stack traces.",[85,609,611],{"className":87,"code":610,"language":89,"meta":90,"style":90},"public class RemoteCallException extends RuntimeException {\n  private static final long serialVersionUID = 1L;   \u002F\u002F pin it so old instances deserialize\n  private final String endpoint;                      \u002F\u002F String is serializable — fine\n\n  public RemoteCallException(String endpoint, Throwable cause) {\n    super(\"call failed: \" + endpoint, cause);\n    this.endpoint = endpoint;\n  }\n}\n",[33,612,613,628,653,665,669,686,700,712,716],{"__ignoreMap":90},[94,614,615,617,619,622,624,626],{"class":96,"line":97},[94,616,108],{"class":107},[94,618,111],{"class":107},[94,620,621],{"class":114}," RemoteCallException",[94,623,118],{"class":107},[94,625,153],{"class":114},[94,627,208],{"class":124},[94,629,630,632,635,637,639,642,644,647,650],{"class":96,"line":104},[94,631,473],{"class":107},[94,633,634],{"class":107}," static",[94,636,476],{"class":107},[94,638,479],{"class":107},[94,640,641],{"class":124}," serialVersionUID ",[94,643,530],{"class":107},[94,645,646],{"class":221}," 1L",[94,648,649],{"class":124},";   ",[94,651,652],{"class":100},"\u002F\u002F pin it so old instances deserialize\n",[94,654,655,657,659,662],{"class":96,"line":128},[94,656,473],{"class":107},[94,658,476],{"class":107},[94,660,661],{"class":124}," String endpoint;                      ",[94,663,664],{"class":100},"\u002F\u002F String is serializable — fine\n",[94,666,667],{"class":96,"line":135},[94,668,132],{"emptyLinePlaceholder":131},[94,670,671,673,675,677,680,682,684],{"class":96,"line":141},[94,672,213],{"class":107},[94,674,621],{"class":114},[94,676,234],{"class":124},[94,678,679],{"class":237},"endpoint",[94,681,259],{"class":124},[94,683,262],{"class":237},[94,685,265],{"class":124},[94,687,688,690,692,695,697],{"class":96,"line":279},[94,689,270],{"class":221},[94,691,401],{"class":124},[94,693,694],{"class":404},"\"call failed: \"",[94,696,408],{"class":107},[94,698,699],{"class":124}," endpoint, cause);\n",[94,701,702,704,707,709],{"class":96,"line":285},[94,703,524],{"class":221},[94,705,706],{"class":124},".endpoint ",[94,708,530],{"class":107},[94,710,711],{"class":124}," endpoint;\n",[94,713,714],{"class":96,"line":304},[94,715,282],{"class":124},[94,717,718],{"class":96,"line":557},[94,719,307],{"class":124},[15,721,722,723,726,727,730,731,736,737,740],{},"Second, because ",[33,724,725],{},"Throwable"," already implements ",[33,728,729],{},"Serializable",", every exception is\nserializable by inheritance — and exceptions genuinely cross JVM boundaries (RMI,\ndistributed systems, app servers). Declare an explicit ",[19,732,733],{},[33,734,735],{},"serialVersionUID","; without one\nthe compiler-generated id shifts on any edit, breaking deserialization of older instances.\nMark any non-serializable custom field ",[33,738,739],{},"transient",".",[10,742,744],{"id":743},"exception-translation-at-layer-boundaries","Exception translation at layer boundaries",[15,746,747,750,751,754,755,758,759,761],{},[19,748,749],{},"Exception translation"," is catching a low-level exception and throwing a higher-level one\nthat fits ",[24,752,753],{},"your"," API's abstraction — so callers depend on your types, not on ",[33,756,757],{},"SQLException","\nor ",[33,760,606],{}," leaking up from the data layer. It is chaining applied deliberately at a\nboundary.",[85,763,765],{"className":87,"code":764,"language":89,"meta":90,"style":90},"public User findById(long id) {\n  try {\n    return jdbc.queryForObject(SQL, mapper, id);\n  } catch (SQLException e) {\n    throw new UserRepositoryException(\"findById \" + id, e);  \u002F\u002F translate + chain\n  }\n}\n",[33,766,767,784,791,804,817,840,844],{"__ignoreMap":90},[94,768,769,771,774,777,779,781],{"class":96,"line":97},[94,770,108],{"class":107},[94,772,773],{"class":124}," User ",[94,775,776],{"class":114},"findById",[94,778,401],{"class":124},[94,780,500],{"class":107},[94,782,783],{"class":124}," id) {\n",[94,785,786,789],{"class":96,"line":104},[94,787,788],{"class":107},"  try",[94,790,208],{"class":124},[94,792,793,796,798,801],{"class":96,"line":128},[94,794,795],{"class":107},"    return",[94,797,361],{"class":124},[94,799,800],{"class":114},"queryForObject",[94,802,803],{"class":124},"(SQL, mapper, id);\n",[94,805,806,809,811,813,815],{"class":96,"line":135},[94,807,808],{"class":124},"  } ",[94,810,43],{"class":107},[94,812,377],{"class":124},[94,814,380],{"class":237},[94,816,265],{"class":124},[94,818,819,822,824,827,829,832,834,837],{"class":96,"line":141},[94,820,821],{"class":107},"    throw",[94,823,395],{"class":107},[94,825,826],{"class":114}," UserRepositoryException",[94,828,401],{"class":124},[94,830,831],{"class":404},"\"findById \"",[94,833,408],{"class":107},[94,835,836],{"class":124}," id, e);  ",[94,838,839],{"class":100},"\u002F\u002F translate + chain\n",[94,841,842],{"class":96,"line":279},[94,843,282],{"class":124},[94,845,846],{"class":96,"line":285},[94,847,307],{"class":124},[15,849,850,851,854,855,858,859,861,862,865,866,869],{},"This decouples callers from the implementation: you could swap JDBC for an ORM and the\nexceptions they catch would not change. Always ",[19,852,853],{},"chain the cause"," so the low-level detail\nremains available for debugging. The same discipline separates ",[19,856,857],{},"business exceptions","\n(",[33,860,590],{}," — expected, mapped to a friendly 4xx) from ",[19,863,864],{},"technical\nexceptions"," (",[33,867,868],{},"ServiceUnavailableException"," — infrastructure broke, log\u002Falert\u002F5xx), and\nlets you handle each kind differently.",[10,871,873],{"id":872},"stay-inside-exception-and-runtimeexception","Stay inside Exception and RuntimeException",[15,875,876,877,879,880,882,883,889,890,895,896,899],{},"Subclass ",[33,878,68],{}," or ",[33,881,78],{}," and nothing higher. ",[19,884,885,886,888],{},"Never extend ",[33,887,725],{},"\ndirectly"," — it produces a type that is neither a clean exception nor an error and confuses\ncatch blocks. ",[19,891,885,892],{},[33,893,894],{},"Error"," — that is reserved for serious JVM-level failures\nlike ",[33,897,898],{},"OutOfMemoryError"," that applications are not meant to catch.",[85,901,903],{"className":87,"code":902,"language":89,"meta":90,"style":90},"class DataImportException extends Error { }            \u002F\u002F WRONG — implies the JVM is broken\n\nclass DataImportException extends RuntimeException { }  \u002F\u002F right level of abstraction\n",[33,904,905,924,928],{"__ignoreMap":90},[94,906,907,910,913,915,918,921],{"class":96,"line":97},[94,908,909],{"class":107},"class",[94,911,912],{"class":114}," DataImportException",[94,914,118],{"class":107},[94,916,917],{"class":114}," Error",[94,919,920],{"class":124}," { }            ",[94,922,923],{"class":100},"\u002F\u002F WRONG — implies the JVM is broken\n",[94,925,926],{"class":96,"line":104},[94,927,132],{"emptyLinePlaceholder":131},[94,929,930,932,934,936,938,941],{"class":96,"line":128},[94,931,909],{"class":107},[94,933,912],{"class":114},[94,935,118],{"class":107},[94,937,153],{"class":114},[94,939,940],{"class":124}," { }  ",[94,942,943],{"class":100},"\u002F\u002F right level of abstraction\n",[15,945,946,947,949,950,953,954,957],{},"Throwing an ",[33,948,894],{}," subtype is actively harmful: a ",[33,951,952],{},"catch (Exception e)"," handler will ",[24,955,956],{},"not","\ncatch it, so it slips past code designed to handle failures, while signalling a\ncatastrophe that did not occur.",[10,959,961],{"id":960},"modern-style-default-to-unchecked","Modern style: default to unchecked",[15,963,964,965,967,968,971,972,974,975,978],{},"Much of the modern ecosystem — Spring chief among it — defaults to ",[19,966,82],{}," domain\nexceptions. They keep method signatures clean, ",[19,969,970],{},"work inside lambdas and streams"," (whose\nfunctional interfaces cannot declare checked exceptions), and avoid rippling ",[33,973,169],{},"\nclauses up the entire call stack every time a method can fail. Handling moves from scattered\n",[33,976,977],{},"try\u002Fcatch"," blocks to one centralized place.",[85,980,982],{"className":87,"code":981,"language":89,"meta":90,"style":90},"\u002F\u002F a single global handler translates domain exceptions into HTTP responses\n@ExceptionHandler(OrderNotFoundException.class)\npublic ResponseEntity\u003C?> handle(OrderNotFoundException e) {\n  return ResponseEntity.status(404).body(e.getMessage());\n}\n",[33,983,984,989,1000,1016,1046],{"__ignoreMap":90},[94,985,986],{"class":96,"line":97},[94,987,988],{"class":100},"\u002F\u002F a single global handler translates domain exceptions into HTTP responses\n",[94,990,991,994,997],{"class":96,"line":104},[94,992,993],{"class":124},"@",[94,995,996],{"class":107},"ExceptionHandler",[94,998,999],{"class":124},"(OrderNotFoundException.class)\n",[94,1001,1002,1004,1007,1010,1013],{"class":96,"line":128},[94,1003,108],{"class":107},[94,1005,1006],{"class":124}," ResponseEntity",[94,1008,1009],{"class":107},"\u003C?>",[94,1011,1012],{"class":114}," handle",[94,1014,1015],{"class":124},"(OrderNotFoundException e) {\n",[94,1017,1018,1020,1023,1026,1028,1031,1034,1037,1040,1043],{"class":96,"line":135},[94,1019,358],{"class":107},[94,1021,1022],{"class":124}," ResponseEntity.",[94,1024,1025],{"class":114},"status",[94,1027,401],{"class":124},[94,1029,1030],{"class":221},"404",[94,1032,1033],{"class":124},").",[94,1035,1036],{"class":114},"body",[94,1038,1039],{"class":124},"(e.",[94,1041,1042],{"class":114},"getMessage",[94,1044,1045],{"class":124},"());\n",[94,1047,1048],{"class":96,"line":141},[94,1049,307],{"class":124},[15,1051,1052,1053,1059,1060,1062,1063,1066],{},"The trade-off is real: the compiler no longer reminds callers that an error can occur, so\nteams rely on ",[19,1054,1055,1058],{},[33,1056,1057],{},"@throws"," Javadoc and convention"," instead of enforcement. Most accept that\nfor the reduced ceremony. Whichever default you pick, resist over-creating: when a standard\nJDK type already says it exactly — ",[33,1061,602],{}," for a bad argument,\n",[33,1064,1065],{},"IllegalStateException"," for a bad state — reuse it. A custom exception only earns its place\nwhen callers will catch it by type or need data from it.",[10,1068,1070],{"id":1069},"recap","Recap",[15,1072,1073,1074,1077,1078,1089,1090,1093,1094,1097,1098,1101,1102,1104,1105,1108,1109,1115,1116,1120,1121,1124,1125,1127,1128,1130,1131,879,1133,1135,1136,1139,1140,1142],{},"A custom exception is a ",[19,1075,1076],{},"typed, named failure"," callers can target — create one only when\nthat targeting or its data is needed. Decide ",[19,1079,1080,1081,1084,1085,1088],{},"checked (",[33,1082,1083],{},"extends Exception",") vs unchecked\n(",[33,1086,1087],{},"extends RuntimeException",")"," first, since it encodes \"expected vs defect.\" Provide the\n",[19,1091,1092],{},"four standard constructors",", especially ",[33,1095,1096],{},"(String, Throwable)",", and ",[19,1099,1100],{},"always chain the\ncause"," so the stack trace and its ",[33,1103,336],{}," line survive. Carry context in ",[19,1106,1107],{},"final\nfields",", follow the ",[19,1110,1111,1114],{},[33,1112,1113],{},"...Exception"," naming"," convention, declare a ",[19,1117,1118],{},[33,1119,735],{},",\nand ",[19,1122,1123],{},"translate low-level exceptions to your abstraction"," at layer boundaries. Never reach\nabove ",[33,1126,68],{},"\u002F",[33,1129,78],{}," into ",[33,1132,725],{},[33,1134,894],{},". Modern code leans\n",[19,1137,1138],{},"unchecked by default"," with centralized handling and ",[33,1141,1057],{}," docs — get the design\nright and your failures stay debuggable instead of mysterious.",[1144,1145,1146],"style",{},"html pre.shiki code .sJ8bj, html code.shiki .sJ8bj{--shiki-default:#6A737D;--shiki-dark:#6A737D}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 .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":90,"searchDepth":104,"depth":104,"links":1148},[1149,1150,1151,1152,1153,1154,1155,1156,1157,1158],{"id":12,"depth":104,"text":13},{"id":47,"depth":104,"text":48},{"id":173,"depth":104,"text":174},{"id":321,"depth":104,"text":322},{"id":440,"depth":104,"text":441},{"id":577,"depth":104,"text":578},{"id":743,"depth":104,"text":744},{"id":872,"depth":104,"text":873},{"id":960,"depth":104,"text":961},{"id":1069,"depth":104,"text":1070},"How to create custom exceptions in Java the right way — checked vs unchecked design, the four standard constructors, exception chaining and translation, custom fields, serialVersionUID, and modern unchecked-default style.","medium","md","Java",{},"\u002Fblog\u002Fjava-custom-exceptions","\u002Fjava\u002Fexceptions\u002Fcustom-exceptions",{"title":5,"description":1159},"blog\u002Fjava-custom-exceptions","Custom Exceptions","Exceptions","exceptions","2026-06-20","inNPfT-VofGl_slLvH2Z7Ri9k5hLAFvlAsDy-2yEGSA",1782244090080]