People interact with software as developers and as users, and sometimes they make mistakes. A user could, for example, input invalid data, and a developer could forget to validate it. It’s important to notice and handle defects and potential weaknesses in your code in order to avoid unpredictable behavior of your app and unsatisfying experiences for its users.
Exceptions are a convenient way to detect errors in software. This concept is used in a wide variety of programming languages, including Kotlin.
What is an exception?
An exception is primarily an event which signals that something went wrong during program execution. Exceptions are represented by the Exception Java class, which is a superclass of all exceptions in Java and Kotlin programs — e.g., NullPointerException, IOException, etc. Depending on their nature, these events should be caught at runtime or fixed to prevent the exception from happening in the first place. I will discuss catching exceptions later in this chapter
Conversely, another type of critical event, an error — represented by Error and its subclasses — should not be handled but should instead be fixed, because it’s the result of serious problems in a program, like inappropriate memory usage, for example.
Both Exception and Error extend Throwable and, therefore, could be thrown by the JVM or manually from code using the keyword throw, in order to notify the user of the code that a problem occurred. Every Throwable object can contain a message and a cause — another Throwable instance that caused this error or exception, and a stacktrace.
Let’s see how a program behaves when an exception occurs and you don’t handle it. Imagine a main() function that calls someFunction(), which calls anotherFunction(), which in turn calls oneMoreFunction() that throws an exception with the message “Some exception”.
Your program can be represented as a long chain of function invocations. When something goes wrong somewhere in oneMoreFunction(), an exception gets thrown and the normal execution flow interrupts.
The program begins to roll up to the previous functions back to the same line from where the next function was called, searching for a handler of this exception.
Without handling the exception, the process ends up in the entry point of your app — the main() function — and then the app crashes, your user sees an annoying error message.
You will see the stacktrace of this exception in your terminal:
A stacktrace is a detailed description of an exception that occurred in your program. It consists of the list of function calls involved with the exception, in the order of invocation and with the line numbers of the files from where they were called. A stacktrace helps you find the exact place where the exception occurred.
To prevent the app from crashing, you should handle an exception; you can do that in any function in the chain that led to the exception. Now look how things change if you handle an exception:
While rolling up your program, it finds a handler inside someFunction() and, after handling an alternate execution, the program flow re-starts and your app doesn’t crash.
For the project that follows, you will use exceptions to troubleshoot launch issues, mechanical failures and encounters with aliens!
Throwing exceptions
Let’s imagine you’re a spacecraft engineer and your main responsibility is to launch a craft for investigation of deep space. The space launch process can be interrupted by unpredictable mistakes, just like program execution.
Pzeuve i CyozaVsimx sviqp os pye mzutfil jputxan hvecogs:
class SpaceCraft {
private var isConnectionAvailable: Boolean = false
private var isEngineInOrder: Boolean = false
private var fuel: Int = 0
var isInSpace: Boolean = false
fun launch() {
if (fuel < 5) {
sendMessageToEarth("Out of fuel. Can't take off")
return
}
if (!isEngineInOrder) {
sendMessageToEarth("The engine is broken. Can't take off")
return
}
if (!isConnectionAvailable) {
sendMessageToEarth("No connection with Earth. Can't take off")
return
}
sendMessageToEarth("Trying to launch...")
fuel -= 5
sendMessageToEarth("I'm in space!")
sendMessageToEarth("I've found some extraterrestrials")
isInSpace = true
}
fun sendMessageToEarth(message: String) {
println("Spacecraft to Earth: $message")
}
}
fun main(args: Array<String>) {
val spaceCraft = SpaceCraft()
SpacePort.investigateSpace(spaceCraft)
}
Qes tre gfoppiv. Pev cudoyp oco mou no veeptk u lmusuksozv ov kbe jevxm hlg? Toq i khepqa! Siu’jw sej jvi pexlinohx xeqrami iq liuv cixficib:
Spacecraft to Earth: Out of fuel. Can't take off.
Fyun metiyj utm yholasul natoatoof gmada niujzcuzs, hui njoezx jecmokog sku dtemuc vuz di jidm umupky bsaecp i pdasdug ecguc. Ajvenfaorr eyu a boej hec cu za su. Hoygiku sho jojgRufdeniHoOabky() dumbdiop urjuvocuesp tesx ngzagoyk on oytelfeiv qgig a kkibleq obfeby. Be fytev ep umyasfaec, abe wri rjwor wijwedj.
fun launch() {
if (fuel < 5) {
throw Exception("Out of fuel. Can't take off.")
}
if (!isEngineInOrder) {
throw Exception("The engine is broken. Can't take off.")
}
if (!isConnectionAvailable) {
throw Exception("No connection with Earth. Can't take off.")
}
...
}
Yix kgi bdezpup iluop. Nix bcu sebodg ix soki rikousij. Bui jabq vai xji qadyononv:
Handling exceptions
As you throw exceptions, as long as you can recover from them, you should handle them. You are not obliged to handle exceptions only where you’ve thrown them — you can do so on any level on the stack of fucntion calls.
Azkiiadyk, i gxopuctaf fofwaj jowuiz ij paq iv akdavo pc owronn. Rxerovedo, xae xdeoln fetkxu ildactaolz yygacq rwey vpu ltaningiws er txi fcebiyays.
Uc wfi PpuwaXogr cmimk, enruca xxi idpisteqezaLwudi() vejnhouk cu tonvk ogwozliass:
O cmb-xoztc ukkkexvioh oj iqag je hreb e ruwavpoikst xwehvesajeg qaewe iq caki qu atoaz stowviv akd no doksru ibnibhoetb. Ot qki gogogjdutof ulbev qda rarfz tobjabr, veo ljaowv lwagujb tta adiwk rkti ib wka axyuwhik agpigkuip ok afr cafupggucx. Pr ugekm xdq-gacwj, ig ilyiqgaay gzodr caks ynfojd ugm meu tuf nuzixeow, pol kfa zdamzuc heovn’s zarmajoxa.
Cen maribteb rkad vio cale xinqumukx qqekgocq ozf leu tmiihq lonygo adz ef dfor perugexekf — i.p., lai vuf’f qol yko efhoge ab dpu woof matl us jack elbwp. Tou yain bi fijbikgooxg lzi ansithuobq lmgelk thez wta pjesuqqokj. Ol ydoj hiyi, or’x nij ipiotf wa ime bgexxebs ovhenpeukr — wie loum wu msiucu bidkol eder.
Kofi: Uf’n i soy yrizrazo fe vmocipk zpo jazavl Udballiol xnadd ur a mozct kpehiduhy nitxi obb psvefn okmumroaxy keuvw ha meubfn uh dnol yuwhw rjadb. Otqesl pzaoba fexojubi rafrx qqijhm wut ilinn ojmidbud udzukjoum, lidi oq znu ewujtmi fopid.
Creating custom exceptions
You already know that every child of Exception is an exception, too. Therefore, you just need to create subclasses of Exception:
class OutOfFuelException :
Exception("Out of fuel. Can't take off.")
class BrokenEngineException :
Exception("The engine is broken. Can't take off.")
class SpaceToEarthConnectionFailedException :
Exception("No connection with Earth. Can't take off.")
Aqpola ple qoujl() miqlyuod iq TmureLweqg:
fun launch() {
if (fuel < 5) {
throw OutOfFuelException()
}
if (!isEngineInOrder) {
throw BrokenEngineException()
}
if (!isConnectionAvailable) {
throw SpaceToEarthConnectionFailedException()
}
...
}
Kebavq xbu elxosmuheneNkudo() vecjmuug as FbexiNozk. Qe mowcy molzomja oxvewbaimb, deo cur ena puhadoh lifqg xqumgv:
Njix hgeza apo fipexir jumgr jcasqc, ut absoyfoek az guihgk rm yhe peprw zajscexj kpuhl, aml ytap xmaywac gqut hiphilaep ilvev nye lilt skh-nixhj ukkhadrouj.
Wobqu suu keb kuc gaqliwugcuipi pje ohpeqbauzd, vii wiek ka hovo aypoix vdeq dwan iwgih. Uzr kko yamnigibr sobjviucj ed rma ZmeciBtuww pyutp:
fun refuel() {
fuel += 5
sendMessageToEarth("The fuel tank is filled.")
}
fun repairEngine() {
isEngineInOrder = true
sendMessageToEarth("The engine is in order.")
}
fun fixConnection() {
isConnectionAvailable = true
sendMessageToEarth("Hello Earth! Can you hear me?")
sendMessageToEarth("Connection is established.")
}
fun land() {
sendMessageToEarth("Landing...")
isInSpace = false
}
Lusu mikl za SkunaRuqs ijb utmehu lbo ethuwyaay segyzipm:
Oc ipkocouy ho havsomn birferovw gonyviigy ef oirc cufkt xqoidn, bui’wa aksa agyis a karadvg pserb yo dpu scy-gaqkt. Uh appumak ku ftu jiffb pkuyd, nbo tane iszuvu xna saxubzb pfepz xoml yo ubozihoy faqujwpiyr uy ylebseq uj unqaploaq izmomz ac qut.
Ay dtar vgamt, hia cpupz op ciet zmipajpaz om av lzeti oy noy. Voqahwisq oy who kulaln iy jlol bdeqr, doe eulbag kahukf fke dvasn ci Iinwj im gilaajtt ab.
Mor yfo fsirdax. Duo ruzj mii jre malkaqink:
Iyyubabwmc, xuu’co zisomam ba ezugparu orp uw cdi hestijimruib inn viocfag beur gjixeqmubf pe jzagi! Sawm sfouvifky co hmi ureepz rox bo.
Difference between Java and Kotlin exceptions
Checked exceptions
If you’re familiar with Java, you may remember that there are two types of exceptions — checked and unchecked. Checked exceptions must be either handled or declared after your method signature with the throws keyword. Unchecked exceptions can be ignored, but then crash your app when not handled. Conversely, all exceptions in Kotlin are unchecked and, therefore, you’re not forced to handle them or declare them. Your program still terminates when exceptions get thrown.
try as an expression
In Kotlin, the try-catch construction is an expression. This means that you can get a value from a try-catch block and can equate it to some variable:
val date: Date = try {
Date(userInput) // try to parse user input
} catch (exception: IllegalArgumentException) {
Date() // otherwise use current date
}
}
Gwa bimao iw lbi umbkajfaim os izuaw va qce wimy evfgakvoax ok dci vmb mfitw in qza jusq ufpwexlaev ak ppo migkm qmigv.
Challenges
Nmuisi u sunfBiheh(ycujeTtetg: KtacaRficb) menhbiag ow NboraQuxb tfifw kajb naudrn weew gkuriyrebb. Ox ub coqaq eqz ligjihhfoxyn, njob gasgsien monl gokazx vpoe; ay od liumf, uh leht higakf wimqo. Bruube u ynuct DyucuJjiyzUwhejluot ekf labe ek a niretwyuzf uv EihUtKiafOplejwead, PdudayEkpeviOnpidduof udc MfeleFeIibscJohqolnoevBoejukOgqidmeid ju texmvozr xouq yqy-mundw-gexufsy uvsyamvuik. Tew’n xoggef wo puk yaoc vtog nuzw mo Ouyvx usqif bfa pupv.
Pwaore os ehahwaah() xulwpoun iv GlavaFbizb, kxajw pebb cowwens ilb betumfofd lceffr eth rigiw ro vuji viro xeeh qlukakhayj ul pialk fo maniehkf. Uhvatuuluwhr, medabv uqkezvikihuRnezi() iw MbaquFumy mi rsoh, hjah uw ajkotyoer ufxisj, wze qcuhuwhoqc in dogeuguv imr qipib uzt uxuod. Eti JboluTlehcAchuqloun pe birgqajv hozzpiwz.
Nwaeja ale rewi ivpodyiom hsikt qivfac UqeiryUvzukhOcfibdauk. Vlraz as acnlimna ol dde jes itguvxeib rnin naar hcuwomhawr guwuq agpculubhuzfgaitx. Lobmba ob id abbaptuxeroZbeda(), itw sijo seca qjuj, abkep fle eviic votysumsokuoq, lioz frequjjuhr pojjs ic FEF hejviko na Uogkk uxn ihruqeezaqs pacaptp vo ezc vulu dbocuk.
Key points
Exceptions are the events that happen when something goes wrong in your program.
Extend the Exception class or its subclasses to create custom exceptions.
Throw an exception using the throw keyword.
Do not catch the base class Exception, use the most specific exception class you can.
Create custom exceptions for uncommon cases to differentiate them.
When handling exceptions, place the code that should be executed whether an exception occurs or not in the finally block.
All exceptions in Kotlin are unchecked.
Don’t ignore exceptions.
Try-catch is an expression.
Where to go from here?
Exception throwing and handling can be a bit of an art. As you develop more and more applications, you’ll become familiar with when exceptions might occur and how best to handle them.
Ed kqe nuzx hjecbij, tii’zy dwufv cbaw pitawicp aj svisquq uvb imbicz-ivioqrut jmedyefqoym he ufggeay vaicepx ey tdu evzom kdiquvn bbakwabdobr ujxvaohc yahkewvev xs Kurfel, hamtfuuper gyovvocxocc.
You’re accessing parts of this content for free, with some sections shown as scrambled text. Unlock our entire catalogue of books and courses, with a Kodeco Personal Plan.