Error handler kotlin

Library support for Kotlin coroutines . Contribute to Kotlin/kotlinx.coroutines development by creating an account on GitHub.

This section covers exception handling and cancellation on exceptions.
We already know that a cancelled coroutine throws CancellationException in suspension points and that it
is ignored by the coroutines’ machinery. Here we look at what happens if an exception is thrown during cancellation or multiple children of the same
coroutine throw an exception.

Exception propagation

Coroutine builders come in two flavors: propagating exceptions automatically (launch and actor) or
exposing them to users (async and produce).
When these builders are used to create a root coroutine, that is not a child of another coroutine,
the former builders treat exceptions as uncaught exceptions, similar to Java’s Thread.uncaughtExceptionHandler,
while the latter are relying on the user to consume the final
exception, for example via await or receive
(produce and receive are covered in Channels section).

It can be demonstrated by a simple example that creates root coroutines using the GlobalScope:

GlobalScope is a delicate API that can backfire in non-trivial ways. Creating a root coroutine for the
whole application is one of the rare legitimate uses for GlobalScope, so you must explicitly opt-in into
using GlobalScope with @OptIn(DelicateCoroutinesApi::class).

{type=»note»}

import kotlinx.coroutines.*

//sampleStart
@OptIn(DelicateCoroutinesApi::class)
fun main() = runBlocking {
    val job = GlobalScope.launch { // root coroutine with launch
        println("Throwing exception from launch")
        throw IndexOutOfBoundsException() // Will be printed to the console by Thread.defaultUncaughtExceptionHandler
    }
    job.join()
    println("Joined failed job")
    val deferred = GlobalScope.async { // root coroutine with async
        println("Throwing exception from async")
        throw ArithmeticException() // Nothing is printed, relying on user to call await
    }
    try {
        deferred.await()
        println("Unreached")
    } catch (e: ArithmeticException) {
        println("Caught ArithmeticException")
    }
}
//sampleEnd

{kotlin-runnable=»true» kotlin-min-compiler-version=»1.3″}

You can get the full code here.

{type=»note»}

The output of this code is (with debug):

Throwing exception from launch
Exception in thread "DefaultDispatcher-worker-2 @coroutine#2" java.lang.IndexOutOfBoundsException
Joined failed job
Throwing exception from async
Caught ArithmeticException

CoroutineExceptionHandler

It is possible to customize the default behavior of printing uncaught exceptions to the console.
CoroutineExceptionHandler context element on a root coroutine can be used as a generic catch block for
this root coroutine and all its children where custom exception handling may take place.
It is similar to Thread.uncaughtExceptionHandler.
You cannot recover from the exception in the CoroutineExceptionHandler. The coroutine had already completed
with the corresponding exception when the handler is called. Normally, the handler is used to
log the exception, show some kind of error message, terminate, and/or restart the application.

CoroutineExceptionHandler is invoked only on uncaught exceptions — exceptions that were not handled in any other way.
In particular, all children coroutines (coroutines created in the context of another Job) delegate handling of
their exceptions to their parent coroutine, which also delegates to the parent, and so on until the root,
so the CoroutineExceptionHandler installed in their context is never used.
In addition to that, async builder always catches all exceptions and represents them in the resulting Deferred object,
so its CoroutineExceptionHandler has no effect either.

Coroutines running in supervision scope do not propagate exceptions to their parent and are
excluded from this rule. A further Supervision section of this document gives more details.

{type=»note»}

import kotlinx.coroutines.*

@OptIn(DelicateCoroutinesApi::class)
fun main() = runBlocking {
//sampleStart
    val handler = CoroutineExceptionHandler { _, exception -> 
        println("CoroutineExceptionHandler got $exception") 
    }
    val job = GlobalScope.launch(handler) { // root coroutine, running in GlobalScope
        throw AssertionError()
    }
    val deferred = GlobalScope.async(handler) { // also root, but async instead of launch
        throw ArithmeticException() // Nothing will be printed, relying on user to call deferred.await()
    }
    joinAll(job, deferred)
//sampleEnd    
}

{kotlin-runnable=»true» kotlin-min-compiler-version=»1.3″}

You can get the full code here.

{type=»note»}

The output of this code is:

CoroutineExceptionHandler got java.lang.AssertionError

Cancellation and exceptions

Cancellation is closely related to exceptions. Coroutines internally use CancellationException for cancellation, these
exceptions are ignored by all handlers, so they should be used only as the source of additional debug information, which can
be obtained by catch block.
When a coroutine is cancelled using Job.cancel, it terminates, but it does not cancel its parent.

import kotlinx.coroutines.*

fun main() = runBlocking {
//sampleStart
    val job = launch {
        val child = launch {
            try {
                delay(Long.MAX_VALUE)
            } finally {
                println("Child is cancelled")
            }
        }
        yield()
        println("Cancelling child")
        child.cancel()
        child.join()
        yield()
        println("Parent is not cancelled")
    }
    job.join()
//sampleEnd    
}

{kotlin-runnable=»true» kotlin-min-compiler-version=»1.3″}

You can get the full code here.

{type=»note»}

The output of this code is:

Cancelling child
Child is cancelled
Parent is not cancelled

If a coroutine encounters an exception other than CancellationException, it cancels its parent with that exception.
This behaviour cannot be overridden and is used to provide stable coroutines hierarchies for
structured concurrency.
CoroutineExceptionHandler implementation is not used for child coroutines.

In these examples, CoroutineExceptionHandler is always installed to a coroutine
that is created in GlobalScope. It does not make sense to install an exception handler to a coroutine that
is launched in the scope of the main runBlocking, since the main coroutine is going to be always cancelled
when its child completes with exception despite the installed handler.

{type=»note»}

The original exception is handled by the parent only when all its children terminate,
which is demonstrated by the following example.

import kotlinx.coroutines.*

@OptIn(DelicateCoroutinesApi::class)
fun main() = runBlocking {
//sampleStart
    val handler = CoroutineExceptionHandler { _, exception -> 
        println("CoroutineExceptionHandler got $exception") 
    }
    val job = GlobalScope.launch(handler) {
        launch { // the first child
            try {
                delay(Long.MAX_VALUE)
            } finally {
                withContext(NonCancellable) {
                    println("Children are cancelled, but exception is not handled until all children terminate")
                    delay(100)
                    println("The first child finished its non cancellable block")
                }
            }
        }
        launch { // the second child
            delay(10)
            println("Second child throws an exception")
            throw ArithmeticException()
        }
    }
    job.join()
//sampleEnd 
}

{kotlin-runnable=»true» kotlin-min-compiler-version=»1.3″}

You can get the full code here.

{type=»note»}

The output of this code is:

Second child throws an exception
Children are cancelled, but exception is not handled until all children terminate
The first child finished its non cancellable block
CoroutineExceptionHandler got java.lang.ArithmeticException

Exceptions aggregation

When multiple children of a coroutine fail with an exception, the
general rule is «the first exception wins», so the first exception gets handled.
All additional exceptions that happen after the first one are attached to the first exception as suppressed ones.

import kotlinx.coroutines.*
import java.io.*

@OptIn(DelicateCoroutinesApi::class)
fun main() = runBlocking {
    val handler = CoroutineExceptionHandler { _, exception ->
        println("CoroutineExceptionHandler got $exception with suppressed ${exception.suppressed.contentToString()}")
    }
    val job = GlobalScope.launch(handler) {
        launch {
            try {
                delay(Long.MAX_VALUE) // it gets cancelled when another sibling fails with IOException
            } finally {
                throw ArithmeticException() // the second exception
            }
        }
        launch {
            delay(100)
            throw IOException() // the first exception
        }
        delay(Long.MAX_VALUE)
    }
    job.join()  
}

{kotlin-runnable=»true» kotlin-min-compiler-version=»1.3″}

You can get the full code here.

{type=»note»}

Note: This above code will work properly only on JDK7+ that supports suppressed exceptions

{type=»note»}

The output of this code is:

CoroutineExceptionHandler got java.io.IOException with suppressed [java.lang.ArithmeticException]

Note that this mechanism currently only works on Java version 1.7+.
The JS and Native restrictions are temporary and will be lifted in the future.

{type=»note»}

Cancellation exceptions are transparent and are unwrapped by default:

import kotlinx.coroutines.*
import java.io.*

@OptIn(DelicateCoroutinesApi::class)
fun main() = runBlocking {
//sampleStart
    val handler = CoroutineExceptionHandler { _, exception ->
        println("CoroutineExceptionHandler got $exception")
    }
    val job = GlobalScope.launch(handler) {
        val inner = launch { // all this stack of coroutines will get cancelled
            launch {
                launch {
                    throw IOException() // the original exception
                }
            }
        }
        try {
            inner.join()
        } catch (e: CancellationException) {
            println("Rethrowing CancellationException with original cause")
            throw e // cancellation exception is rethrown, yet the original IOException gets to the handler  
        }
    }
    job.join()
//sampleEnd    
}

{kotlin-runnable=»true» kotlin-min-compiler-version=»1.3″}

You can get the full code here.

{type=»note»}

The output of this code is:

Rethrowing CancellationException with original cause
CoroutineExceptionHandler got java.io.IOException

Supervision

As we have studied before, cancellation is a bidirectional relationship propagating through the whole
hierarchy of coroutines. Let us take a look at the case when unidirectional cancellation is required.

A good example of such a requirement is a UI component with the job defined in its scope. If any of the UI’s child tasks
have failed, it is not always necessary to cancel (effectively kill) the whole UI component,
but if the UI component is destroyed (and its job is cancelled), then it is necessary to cancel all child jobs as their results are no longer needed.

Another example is a server process that spawns multiple child jobs and needs to supervise
their execution, tracking their failures and only restarting the failed ones.

Supervision job

The SupervisorJob can be used for these purposes.
It is similar to a regular Job with the only exception that cancellation is propagated
only downwards. This can easily be demonstrated using the following example:

import kotlinx.coroutines.*

fun main() = runBlocking {
//sampleStart
    val supervisor = SupervisorJob()
    with(CoroutineScope(coroutineContext + supervisor)) {
        // launch the first child -- its exception is ignored for this example (don't do this in practice!)
        val firstChild = launch(CoroutineExceptionHandler { _, _ ->  }) {
            println("The first child is failing")
            throw AssertionError("The first child is cancelled")
        }
        // launch the second child
        val secondChild = launch {
            firstChild.join()
            // Cancellation of the first child is not propagated to the second child
            println("The first child is cancelled: ${firstChild.isCancelled}, but the second one is still active")
            try {
                delay(Long.MAX_VALUE)
            } finally {
                // But cancellation of the supervisor is propagated
                println("The second child is cancelled because the supervisor was cancelled")
            }
        }
        // wait until the first child fails & completes
        firstChild.join()
        println("Cancelling the supervisor")
        supervisor.cancel()
        secondChild.join()
    }
//sampleEnd
}

{kotlin-runnable=»true» kotlin-min-compiler-version=»1.3″}

You can get the full code here.

{type=»note»}

The output of this code is:

The first child is failing
The first child is cancelled: true, but the second one is still active
Cancelling the supervisor
The second child is cancelled because the supervisor was cancelled

Supervision scope

Instead of coroutineScope, we can use supervisorScope for scoped concurrency. It propagates the cancellation
in one direction only and cancels all its children only if it failed itself. It also waits for all children before completion
just like coroutineScope does.

import kotlin.coroutines.*
import kotlinx.coroutines.*

fun main() = runBlocking {
//sampleStart
    try {
        supervisorScope {
            val child = launch {
                try {
                    println("The child is sleeping")
                    delay(Long.MAX_VALUE)
                } finally {
                    println("The child is cancelled")
                }
            }
            // Give our child a chance to execute and print using yield 
            yield()
            println("Throwing an exception from the scope")
            throw AssertionError()
        }
    } catch(e: AssertionError) {
        println("Caught an assertion error")
    }
//sampleEnd
}

{kotlin-runnable=»true» kotlin-min-compiler-version=»1.3″}

You can get the full code here.

{type=»note»}

The output of this code is:

The child is sleeping
Throwing an exception from the scope
The child is cancelled
Caught an assertion error

Exceptions in supervised coroutines

Another crucial difference between regular and supervisor jobs is exception handling.
Every child should handle its exceptions by itself via the exception handling mechanism.
This difference comes from the fact that child’s failure does not propagate to the parent.
It means that coroutines launched directly inside the supervisorScope do use the CoroutineExceptionHandler
that is installed in their scope in the same way as root coroutines do
(see the CoroutineExceptionHandler section for details).

import kotlin.coroutines.*
import kotlinx.coroutines.*

fun main() = runBlocking {
//sampleStart
    val handler = CoroutineExceptionHandler { _, exception -> 
        println("CoroutineExceptionHandler got $exception") 
    }
    supervisorScope {
        val child = launch(handler) {
            println("The child throws an exception")
            throw AssertionError()
        }
        println("The scope is completing")
    }
    println("The scope is completed")
//sampleEnd
}

{kotlin-runnable=»true» kotlin-min-compiler-version=»1.3″}

You can get the full code here.

{type=»note»}

The output of this code is:

The scope is completing
The child throws an exception
CoroutineExceptionHandler got java.lang.AssertionError
The scope is completed

Exception Handling in Kotlin Coroutines

Coroutines have become a new way for us to do asynchronous programming in Android using Kotlin. When building a production-ready app, we want to handle all our exceptions properly for users to have a smooth experience while using our app.

In this blog, we are going to talk about how we can handle exceptions properly in an android project when we are using Kotlin coroutines in the project.

If you want to master Kotlin coroutines, you can learn it from here.

We are going to divide this blog into the following sections,

  • What are the exceptions?
  • How do we handle exceptions in a general way?
  • How do we handle exceptions in Kotlin Coroutines efficiently?

What are the exceptions?

Exceptions are the unexpected events that come up while running or performing any program. Due to exceptions, the execution is disturbed and the expected flow of the application is not executed.

That is why we need to handle the exceptions in our code to execute the proper flow of the app.

How do we handle exceptions in a general way?

A generic way to handle exception in kotlin is to use a try-catch block. Where we write our code which might throw an exception in the try block, and if there is any exception generated, then the exception is caught in the catch block.

Let us understand by example,

try {
    val solution = 5 / 0
    val addition = 2 + 5
    Log.d("MainActivity", solution.toString())
    Log.d("MainActivity", addition.toString())
} catch (e: Exception) {
    Log.e("MainActivity", e.toString())
}

In this above code, we are trying to first divide

5

by

0

and also we want to add two numbers

2,5

. Then we want to print the solution in

Logcat

. When we run the app, the proper flow should be first we get the value in the solution variable and then assign the sum in addition variable. Later, we want to print the values in the

Log

statement.

But, when we run the app we would see the following output,

E/MainActivity: java.lang.ArithmeticException: divide by zero

Here, the

solution

and

addition

variables are not printed but the

Log

statement in the

catch

block is printed with an

Arithmetic


Exception

. The reason here is, that any number can’t be

divided

by

0

. So, when we got the exception you can see that no step was performed below the first line and it directly went to

catch

block.

The above code is an example of how an exception occurs and how we can handle it.

How do we handle exceptions in Kotlin Coroutines efficiently?

Now, we are going to discuss how we can handle exception efficiently while using Kotlin Coroutines in our project. There are the following ways to handle exceptions,

  • Generic way
  • Using CoroutineExceptionHandler
  • Using SupervisorScope

To discuss this further, we will use an example of fetching a list of users. We would have an interface,

interface ApiService {

    @GET("users")
    suspend fun getUsers(): List<ApiUser>

    @GET("more-users")
    suspend fun getMoreUsers(): List<ApiUser>

    @GET("error")
    suspend fun getUsersWithError(): List<ApiUser>

}

The code has been taken from this project, for complete implementation, you should check the project.

Here, we have three different

suspend

functions that we would use to fetch a list of users. If you notice, here only the first two functions i.e.

getUsers()

and

getMoreUsers()

will return a list but the third function,

getUserWithError()

will throw an Exception,

retrofit2.HttpException: HTTP 404 Not Found

We have intentionally created the

getUserWithError()

to throw an Exception for the sake of understanding.

Now, let’s discuss the ways to handle the exception using Kotlin Coroutines properly in our code.

1. Generic Way

Let us consider we have a ViewModel,

TryCatchViewModel

(

present in the project

) and I want to do my API call in the ViewModel.

class TryCatchViewModel(
    private val apiHelper: ApiHelper,
    private val dbHelper: DatabaseHelper
) : ViewModel() {

    private val users = MutableLiveData<Resource<List<ApiUser>>>()

    fun fetchUsers() {
        viewModelScope.launch {
            users.postValue(Resource.loading(null))
            try {
                val usersFromApi = apiHelper.getUsers()
                users.postValue(Resource.success(usersFromApi))
            } catch (e: Exception) {
                users.postValue(Resource.error("Something Went Wrong", null))
            }
        }
    }

    fun getUsers(): LiveData<Resource<List<ApiUser>>> {
        return users
    }
}

This will return a list of users in my activity without any exception. Now, let’s say we introduce an exception in our

fetchUsers()

function. We will modify the code like,

fun fetchUsers() {
    viewModelScope.launch {
        users.postValue(Resource.loading(null))
        try {
            val moreUsersFromApi = apiHelper.getUsersWithError()
            val usersFromApi = apiHelper.getUsers()
            
            val allUsersFromApi = mutableListOf<ApiUser>()
            allUsersFromApi.addAll(usersFromApi)
            allUsersFromApi.addAll(moreUsersFromApi)

            users.postValue(Resource.success(allUsersFromApi))
        } catch (e: Exception) {
            users.postValue(Resource.error("Something Went Wrong", null))
        }
    }
}

Here, you can see that we are fetching users from two different sources and now the flow of execution should be, first, we first get the output from

apiHelper.getUsersWithError()

and then from

apiHelper.getUsers()

Now, both should be added to the

mutableList

and then we change the

livedata

by adding the list to it.

We have all of this code inside the

try-catch block

to handle the exception we might get. If we don’t use a try-catch block then our app would crash when an exception occurs.

But, when we run this, we directly jump to the catch block as we get the

404 Not found exception

. So, what happened there,

We the execution reached the

getUsersWithError()

it got the exception, and it terminated the execution at that point and went to

catch block

directly.

Exception Handling in Kotlin Coroutines

If you see in the above image, you would see that, if in the scope any of the children get an exception then the following children will

not be executed

and the execution would be

terminated

.

Now, let’s say we want the execution to still continue even when we get an exception. Then we would have to update the code with individual

try-catch block

like,

fun fetchUsers() {
    viewModelScope.launch {
        users.postValue(Resource.loading(null))
        try {
            val moreUsersFromApi = try {
                apiHelper.getUsersWithError()
            } catch (e: Exception) {
                emptyList<ApiUser>()
            }
            val usersFromApi = try {
                apiHelper.getUsers()
            } catch (e: Exception) {
                emptyList<ApiUser>()
            }

            val allUsersFromApi = mutableListOf<ApiUser>()
            allUsersFromApi.addAll(usersFromApi)
            allUsersFromApi.addAll(moreUsersFromApi)

            users.postValue(Resource.success(allUsersFromApi))
        } catch (e: Exception) {
            users.postValue(Resource.error("Something Went Wrong", null))
        }
    }
}

Here, we have added an individual exception to both API calls so that, if there is an exception, then an empty list is assigned to the variable and the execution continues.

This is an example when we are executing the tasks in a sequential manner.

2. Using CoroutineExceptionHandler

If you consider the above example, you can see we are wrapping our code inside a try-catch exception. But, when we are working with coroutines we can handle an exception using a global coroutine exception handler called

CoroutineExceptionHandler

.

To use it, first, we create an exception handler in our ViewModel,

private val exceptionHandler = CoroutineExceptionHandler {   context, exception ->
    )
}

and then we attach the handler to the ViewModelScope.

So, our code looks like,

class ExceptionHandlerViewModel(
    private val apiHelper: ApiHelper,
    private val dbHelper: DatabaseHelper
) : ViewModel() {

    private val users = MutableLiveData<Resource<List<ApiUser>>>()

    private val exceptionHandler = CoroutineExceptionHandler { _, exception ->
        users.postValue(Resource.error("Something Went Wrong", null))
    }

    fun fetchUsers() {
        viewModelScope.launch(exceptionHandler) {
            users.postValue(Resource.loading(null))
            val usersFromApi = apiHelper.getUsers()
            users.postValue(Resource.success(usersFromApi))
        }
    }

    fun getUsers(): LiveData<Resource<List<ApiUser>>> {
        return users
    }

}

Here, we have created and

exceptionHandler

and attached it to the coroutine. So, now let’s say we introduce an exception in

fetchUsers()

function.

fun fetchUsers() {
    viewModelScope.launch(exceptionHandler) {
        users.postValue(Resource.loading(null))
        
        val usersFromApi = apiHelper.getUsers()
        val moreUsersFromApi = apiHelper.getUsersWithError()
        
        val allUsersFromApi = mutableListOf<ApiUser>()
        allUsersFromApi.addAll(usersFromApi)
        allUsersFromApi.addAll(moreUsersFromApi)
        
        users.postValue(Resource.success(usersFromApi))
    }
}

Here, we have added

getUsersWithError()

and it will throw an exception and handle will be given to the handler.

Now, notice that here we have not added a try-catch block and the exception would be handled in

CoroutineExceptionHandler

as it works as a global exception handler for coroutine.

3. Using SupervisorScope

When we get an exception, we don’t want the execution of our task to be terminated. But, till now what we have seen is that whenever we get an exception our execution fails and the task is terminated.

Till now, we saw how to keep the task going in sequential execution, in this section we are going to see how the task keeps executing in parallel execution.

So, before starting with

supervisorScope

, let us understand the problem with parallel execution,

Let’s say we do a parallel execution like,

private fun fetchUsers() {
    viewModelScope.launch {
        users.postValue(Resource.loading(null))
        try {
                val usersWithErrorFromApiDeferred = async { apiHelper.getUsersWithError() }
                val moreUsersFromApiDeferred = async { apiHelper.getMoreUsers() }

                val usersWithErrorFromApi = usersWithErrorFromApiDeferred.await()
                val moreUsersFromApi = moreUsersFromApiDeferred.await()

                val allUsersFromApi = mutableListOf<ApiUser>()
                allUsersFromApi.addAll(usersWithErrorFromApi)
                allUsersFromApi.addAll(moreUsersFromApi)

                users.postValue(Resource.success(allUsersFromApi))

        } catch (e: Exception) {
            users.postValue(Resource.error("Something Went Wrong", null))
        }
    }
}

Here, an exception will occur when

getUsersWithError()

is called and this will lead to the crash of our android application and the execution of our task would be terminated.

To overcome the crash of our application, we would run the code inside

coroutineScope

so that, if the exception occurs the app would not crash. So, the code would look like,

private fun fetchUsers() {
    viewModelScope.launch {
        users.postValue(Resource.loading(null))
        try {
         
            coroutineScope {
                val usersWithErrorFromApiDeferred = async { apiHelper.getUsersWithError() }
                val moreUsersFromApiDeferred = async { apiHelper.getMoreUsers() }

                val usersWithErrorFromApi = usersWithErrorFromApiDeferred.await()
                val moreUsersFromApi = moreUsersFromApiDeferred.await()

                val allUsersFromApi = mutableListOf<ApiUser>()
                allUsersFromApi.addAll(usersWithErrorFromApi)
                allUsersFromApi.addAll(moreUsersFromApi)

                users.postValue(Resource.success(allUsersFromApi))
            }
        } catch (e: Exception) {
            users.postValue(Resource.error("Something Went Wrong", null))
        }
    }
}

Here, in this, execution where we are doing parallel execution inside

coroutineScope

, which is inside the try block. We would get an exception from

getUsersWithError()

and once the exception occurs the execution will stop and it will move to the catch block.

The execution of the task will stop when an exception has occurred.

So, to overcome execution failure we can use

supervisorScope

in our task.

Exception Handling in Kotlin Coroutines

So, while using

supervisorScope

, when any one of the children throws an exception, then the other children would keep on executing.

Let’s consider the example, in our

fetchUsers()

function,

fun fetchUsers() {
    viewModelScope.launch {
        users.postValue(Resource.loading(null))
        try {
          
            supervisorScope {
                val usersFromApiDeferred = async { apiHelper.getUsersWithError() }
                val moreUsersFromApiDeferred = async { apiHelper.getMoreUsers() }

                val usersFromApi = try {
                    usersFromApiDeferred.await()
                } catch (e: Exception) {
                    emptyList<ApiUser>()
                }

                val moreUsersFromApi = try {
                    moreUsersFromApiDeferred.await()
                } catch (e: Exception) {
                    emptyList<ApiUser>()
                }

                val allUsersFromApi = mutableListOf<ApiUser>()
                allUsersFromApi.addAll(usersFromApi)
                allUsersFromApi.addAll(moreUsersFromApi)

                users.postValue(Resource.success(allUsersFromApi))
            }
        } catch (e: Exception) {
            users.postValue(Resource.error("Something Went Wrong", null))
        }
    }
}

In the above code, we have

getUsersWithError(),

and as we till now know that it will throw an error.

Here, in

supervisorScope

we have two jobs running parallel in which one child will throw an exception but the execution of the task will still happen.

We are using

async

here, which returns a


Deferred


that will deliver the result later. So, when we get the result using

await(),

we are using the try-catch block as an expression so that if an exception occurs, an empty list is returned, but the execution will be completed and we would get a list of users. The output would be,

Exception Handling in Kotlin Coroutines

So, this is how we can handle exceptions and still keep the execution going.


Conclusion:

  • While

    NOT

    using

    async

    , we can go ahead with the

    try-catch

    or the

    CoroutineExceptionHandler

    and achieve anything based on our use-cases.
  • While using

    async

    , in addition to

    try-catch

    , we have two options:

    coroutineScope

    and

    supervisorScope

    .
  • With

    async

    , use

    supervisorScope

    with the individual

    try-catch

    for each task in addition to the top-level

    try-catch

    , when you want to continue with other tasks if one or some of them have failed.
  • With

    async

    , use

    coroutineScope

    with the top-level

    try-catch

    , when you do

    NOT

    want to continue with other tasks if any of them have failed.


The major difference is that a coroutineScope will cancel whenever any of its children fail. If we want to continue with the other tasks even when one fails, we go with the supervisorScope. A supervisorScope won’t cancel other children when one of them fails.

This is how we can handle an exception efficiently while using Kotlin Coroutines in our project. We should always handle exceptions properly in our project to help our users have a smoother experience using our product.

You can find the complete project here

.

Happy learning.


Team MindOrks :)

article banner (priority)

A very important part of how coroutines behave is their exception handling. Just as a program breaks when an uncaught exception slips by, a coroutine breaks in the case of an uncaught exception. This behavior is nothing new: for instance, threads also end in such cases. The difference is that coroutine builders also cancel their parents, and each cancelled parent cancels all its children. Let’s look at the example below. Once a coroutine receives an exception, it cancels itself and propagates the exception to its parent (launch). The parent cancels itself and all its children, then it propagates the exception to its parent (runBlocking). runBlocking is a root coroutine (it has no parent), so it just ends the program (runBlocking rethrows the exception).

import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking

//sampleStart
fun main(): Unit = runBlocking {
launch {
launch {
delay(1000)
throw Error(«Some error»)
}

launch {
delay(2000)
println(«Will not be printed»)
}

launch {
delay(500) // faster than the exception
println(«Will be printed»)
}
}

launch {
delay(2000)
println(«Will not be printed»)
}
}
// Will be printed
// Exception in thread «main» java.lang.Error: Some error…
//sampleEnd

Adding additional launch coroutines wouldn’t change anything. Exception propagation is bi-directional: the exception is propagated from child to parent, and when those parents are cancelled, they cancel their children. Thus, if exception propagation is not stopped, all coroutines in the hierarchy will be cancelled.

Stop breaking my coroutines

Catching an exception before it breaks a coroutine is helpful, but any later is too late. Communication happens via a job, so wrapping a coroutine builder with a try-catch is not helpful at all.

import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking

//sampleStart
fun main(): Unit = runBlocking {
// Don’t wrap in a try-catch here. It will be ignored.
try {
launch {
delay(1000)
throw Error(«Some error»)
}
} catch (e: Throwable) { // nope, does not help here
println(«Will not be printed»)
}

launch {
delay(2000)
println(«Will not be printed»)
}
}
// Exception in thread «main» java.lang.Error: Some error…
//sampleEnd

SupervisorJob

The most important way to stop coroutines breaking is by using a SupervisorJob. This is a special kind of job that ignores all exceptions in its children.

SupervisorJob is generally used as part of a scope in which we start multiple coroutines (more about this in the Constructing coroutine scope chapter).

import kotlinx.coroutines.*

//sampleStart
fun main(): Unit = runBlocking {
val scope = CoroutineScope(SupervisorJob())
scope.launch {
delay(1000)
throw Error(«Some error»)
}

scope.launch {
delay(2000)
println(«Will be printed»)
}

delay(3000)
}
// Exception…
// Will be printed
//sampleEnd

A common mistake is to use a SupervisorJob as an argument to a parent coroutine, like in the code below. It won’t help us handle exceptions, because in such a case SupervisorJob has only one direct child, namely the launch defined at 1 that received this SupervisorJob as an argument. So, in such a case there is no advantage of using SupervisorJob over Job (in both cases, the exception will not propagate to runBlocking because we are not using its job).

import kotlinx.coroutines.*

//sampleStart
fun main(): Unit = runBlocking {
// Don’t do that, SupervisorJob with one children
// and no parent works similar to just Job
launch(SupervisorJob()) { // 1
launch {
delay(1000)
throw Error(«Some error»)
}

launch {
delay(2000)
println(«Will not be printed»)
}
}

delay(3000)
}
// Exception…
//sampleEnd

It would make more sense if we used the same job as a context for multiple coroutine builders because each of them can be cancelled, but they won’t cancel each other.

import kotlinx.coroutines.*

//sampleStart
fun main(): Unit = runBlocking {
val job = SupervisorJob()
launch(job) {
delay(1000)
throw Error(«Some error»)
}
launch(job) {
delay(2000)
println(«Will be printed»)
}
job.join()
}
// (1 sec)
// Exception…
// (1 sec)
// Will be printed
//sampleEnd

supervisorScope

Another way to stop exception propagation is to wrap coroutine builders with supervisorScope. This is very convenient as we still keep a connection to the parent, yet any exceptions from the coroutine will be silenced.

import kotlinx.coroutines.*

//sampleStart
fun main(): Unit = runBlocking {
supervisorScope {
launch {
delay(1000)
throw Error(«Some error»)
}

launch {
delay(2000)
println(«Will be printed»)
}
}
delay(1000)
println(«Done»)
}
// Exception…
// Will be printed
// (1 sec)
// Done
//sampleEnd

supervisorScope is just a suspending function and can be used to wrap suspending function bodies. This and other functionalities of supervisorScope will be described better in the next chapter. The common way to use it is to start multiple independent tasks.

suspend fun notifyAnalytics(actions: List<UserAction>) =
supervisorScope {
actions.forEach { action ->
launch {
notifyAnalytics(action)
}
}
}

Another way to stop exception propagation is to use coroutineScope. Instead of influencing a parent, this function throws an exception that can be caught using try-catch (in contrast to coroutine builders). Both will be described in the next chapter.

Beware, that supervisorScope cannot be replaced with withContext(SupervisorJob())! Take a look at the below snippet.

// DON’T DO THAT!
suspend fun sendNotifications(
notifications: List<Notification>
) = withContext(SupervisorJob()) {
for (notification in notifications) {
launch {
client.send(notification)
}
}
}

The problem here is that Job is the only context that is not inherited. Each coroutine needs its own job, and passing a job to a coroutine makes it a parent. So here SupervisorJob is a parent of withContext coroutine. When a child has an exception, it propagates to coroutine coroutine, cancels its Job, cancels children, and throws an exception. The fact that SupervisorJob is a parent changes nothing.

Await

So, we know how to stop exception propagation, but sometimes this is not enough. In the case of an exception, the async coroutine builder breaks its parent, just like launch and other coroutine builders that have a relation with their parents. However, what if this process is silenced (for instance, using SupervisorJob or supervisorScope) and await is called? Let’s look at the following example:

import kotlinx.coroutines.*

//sampleStart
class MyException : Throwable()

suspend fun main() = supervisorScope {
val str1 = async<String> {
delay(1000)
throw MyException()
}

val str2 = async {
delay(2000)
«Text2»
}

try {
println(str1.await())
} catch (e: MyException) {
println(e)
}

println(str2.await())
}
// MyException
// Text2
//sampleEnd

We have no value to return since the coroutine ended with an exception, so instead the MyException exception is thrown by await. This is why MyException is printed. The other async finishes uninterrupted because we’re using the supervisorScope.

CancellationException does not propagate to its parent

If an exception is a subclass of CancellationException, it will not be propagated to its parent. It will only cause cancellation of the current coroutine. CancellationException is an open class, so it can be extended by our own classes or objects.

import kotlinx.coroutines.*

object MyNonPropagatingException : CancellationException()

suspend fun main(): Unit = coroutineScope {
launch { // 1
launch { // 2
delay(2000)
println(«Will not be printed»)
}
throw MyNonPropagatingException // 3
}
launch { // 4
delay(2000)
println(«Will be printed»)
}
}
// (2 sec)
// Will be printed

In the above snippet, we start two coroutines with builders at 1 and 4. At 3, we throw a MyNonPropagatingException exception, which is a subtype of CancellationException. This exception is caught by launch (started at 1). This builder cancels itself, then it also cancels its children, namely the builder defined at 2. The launch started at 4 is not affected, so it prints «Will be printed» after 2 seconds.

Coroutine exception handler

When dealing with exceptions, sometimes it is useful to define default behavior for all of them. This is where the CoroutineExceptionHandler context comes in handy. It does not stop the exception propagating, but it can be used to define what should happen in the case of an exception (by default, it prints the exception stack trace).

import kotlinx.coroutines.*

//sampleStart
fun main(): Unit = runBlocking {
val handler =
CoroutineExceptionHandler { ctx, exception ->
println(«Caught $exception»)
}
val scope = CoroutineScope(SupervisorJob() + handler)
scope.launch {
delay(1000)
throw Error(«Some error»)
}

scope.launch {
delay(2000)
println(«Will be printed»)
}

delay(3000)
}
// Caught java.lang.Error: Some error
// Will be printed
//sampleEnd

This context is useful on many platforms to add a default way of dealing with exceptions. For Android, it often informs the user about a problem by showing a dialog or an error message.

Summary

Exception handling is an important part of the kotlinx.coroutines library. Over time, we will inevitably come back to these topics. For now, I hope that you understand how exceptions propagate from child to parent in basic builders, and how they can be stopped. Now it’s time for a long-awaited, strongly connected topic. Time to talk about coroutine scope functions.

1. Overview

In this tutorial, we’ll discuss exception handling in Kotlin.

2. Exceptions

Exceptions are problems that occur during the program execution and disrupt the conventional flow. This can occur due to various reasons like invalid arithmetic operation, a reference to a null object.

Exception handling is the technique to gracefully handle such problems and continue the program execution.

3. Kotlin Exceptions

In Kotlin, there are only unchecked exceptions that are thrown during the runtime execution of the program. All exception classes descend from the class Throwable. Kotlin uses the throw keyword to throw an exception object.

Although Kotlin inherits the concept of exception from Java, it doesn’t support checked exceptions like Java.

The checked exceptions are considered a controversial feature in Java. It decreases developer productivity without any additional increase in code quality. Among other issues, checked exceptions also lead to boilerplate code, difficulty while using lambda expressions.

So, like many other modern programming languages, the Kotlin developers also decided against including checked exceptions as a language feature.

4. Try-Catch Block

We can use the try-catch block for exception handling in Kotlin. In particular, the code that can throw an exception is put inside the try block. Additionally, the corresponding catch block is used to handle the exception.

As a matter of fact, the try block is always followed by a catch or finally block or both of them.

Let’s take a glance at a try-catch block:

try {
    val message = "Welcome to Kotlin Tutorials"
    message.toInt()
} catch (exception: NumberFormatException) {
    // ...
}

4.1. Try-Catch Block as an Expression

An expression may be a combination of one or more values, variables, operators, and functions that execute to provide another value. Hence, we can use the try-catch block as an expression in Kotlin.

Furthermore, the return value of the try-catch expression is the last expression of either the try or the catch block. In the event of an exception, the value of the catch block is returned. However, the results of the expression are not affected by the finally block.

Here’s how we can use try-catch as an expression:

val number = try {
    val message = "Welcome to Kotlin Tutorials"
    message.toInt()
} catch (exception: NumberFormatException) {
    // ...
}
return number

4.2. Multiple Catch Blocks

We can use multiple catch blocks together with the try block in Kotlin. Particularly, this is often required if we perform different kinds of operations in the try block, which increases the probability of catching multiple exceptions.

Besides, we must order all catch blocks from the most specific to the most general exception. As an example, the catch block for ArithmeticException must precede the catch block for Exception.

Let’s have a look at the way to use multiple catch blocks:

try {
    val result = 25 / 0
    result
} catch (exception: NumberFormatException) {
    // ...
} catch (exception: ArithmeticException) {
    // ...
} catch (exception: Exception) {
    // ...
}

4.3. Nested Try-Catch Block

We can use a nested try-catch block where we implement a try-catch block inside another try block. For instance, this can be required when a block of code may throw an exception and within that block of code, another statement may additionally throw an exception.

Let’s see how we can use the nested try-catch block:

try {
    val firstNumber = 50 / 2 * 0
    try {
        val secondNumber = 100 / firstNumber
        secondNumber
    } catch (exception: ArithmeticException) {
        // ...
    }
} catch (exception: NumberFormatException) {
    // ...
}

5. Finally Block

We can use the finally block to always execute code no matter whether an exception is handled or not. Besides, we can use the finally block with the try block by omitting the catch block.

Let’s take a look into the finally block:

try {
    val message = "Welcome to Kotlin Tutorials"
    message.toInt()
} catch (exception: NumberFormatException) {
    // ...
} finally {
    // ...
}

6. Throw Keyword

We can use the throw keyword in Kotlin to throw a certain exception or a custom exception.

Here’s how we can use the throw keyword in Kotlin:

val message = "Welcome to Kotlin Tutorials"
if (message.length > 10) throw IllegalArgumentException("String is invalid")
else return message.length

We can also use throw as an expression in Kotlin. For instance, it can be used as a part of the Elvis expression:

val message: String? = null
return message?.length ?: throw IllegalArgumentException("String is null")

The throw expression returns a value of type Nothing. This special type has no values and is used to indicate an unreachable code block. In addition, we can also use the Nothing type in a function to indicate that it will always throw an exception:

fun abstractException(message: String): Nothing {
    throw RuntimeException(message)
}

7. Throws Annotation

We can use the @Throws annotation to provide interoperability between Kotlin and Java. Since Kotlin doesn’t have checked exceptions, it doesn’t declare exceptions that are thrown. Let’s define a function in Kotlin:

fun readFile(): String? {
    val filePath = null
    return filePath ?: throw IOException("File path is invalid")
}

Now we can call the Kotlin function from Java and catch the exception. Here’s how we can write a try-catch in Java:

try {
    readFile();
}
catch (IOException e) {
    // ...
}

The Java compiler will display an error message because the Kotlin function didn’t declare the exception. In such cases, we can use the @Throws annotation in Kotlin to handle the error:

@Throws(IOException::class)
fun readFile(): String? {
    val filePath = null
    return filePath ?: throw IOException("File path is invalid")
}

8. Conclusion

In this tutorial, we discussed the various ways of exception handling in Kotlin.

As always, the code for these examples is available over on GitHub.

To learn more about Kotlin features, have a glance at one of our Kotlin tutorials.

If you have a few years of experience with the Kotlin language and server-side development, and you’re interested in sharing that experience with the community, have a look at our Contribution Guidelines.

Coroutines are a new way for us to do asynchronous programming in Kotlin in Android. When developing a production-ready app, we want to ensure that all exceptions are handled correctly so that users have a pleasant experience while using our app. In this article, we will discuss how to properly handle exceptions in an Android project when using Kotlin coroutines. If you want to learn how to use Kotlin coroutines, you can start here. This article will be divided into the following sections:

  1. What are some of the exceptions?
  2. How do we deal with exceptions in general?
  3. How do we efficiently handle exceptions in Kotlin Coroutines?

What are some of the exceptions?

Exceptions are unexpected events that occur while a program is being run or performed. The execution is disrupted by exceptions, and the expected flow of the application is not executed. That is why, in order to execute the app’s proper flow, we must handle exceptions in our code.

How do we deal with exceptions in general?

A try-catch block is a general way to handle exceptions in Kotlin. In the try block, we write code that may throw an exception, and if an exception is generated, the exception is caught in the catch block. Let us illustrate with an example:

Kotlin

try {

    val gfgAnswer = 5 / 0

    val addition = 2 + 5

    Log.d("GfGMainActivity", gfgAnswer.toString())

    Log.d("GfGMainActivity", addition.toString())

} catch (e: Exception) {

    Log.e("GfGMainActivity", e.toString())

}

In the preceding code, we are attempting to divide 5 by 0 while also adding two numbers, 2,5. The solution should then be printed in Logcat. When we run the app, we should first get the value in the solution variable and then assign the sum to the additional variable. The values will be printed later in the Log statement. The solution and addition variables are not printed in this case, but the Log statement in the catch block is with an Arithmetic Exception. The reason for this is that no number can be divided by 0. So, when we got the exception, you can see that no step was taken below the first line, and it went straight to the catch block. The preceding code demonstrates how an exception can occur and how we can handle it.

How do we efficiently handle exceptions in Kotlin Coroutines?

Now we’ll look at how we can handle exceptions effectively in our project using Kotlin Coroutines. There are several approaches to dealing with exceptions.

Generic method

Using SupervisorScope and CoroutineExceptionHandler. To illustrate this further, consider retrieving a list of users. We’d have an interface:

Kotlin

interface GfgService {

    @GET("courses")

    suspend fun getCourses(): List<GfgUser>

    @GET("more-courses")

    suspend fun getMoreCourses(): List<GfgUser>

    @GET("error")

    suspend fun getCoursesWithError(): List<GfgUser>

}

We have three different suspend functions here that we can use to get a list of users. If you look closely, you’ll notice that only the first two functions, getUsers() and getMoreUsers(), will return a list, whereas the third function, getUserWithError(), will throw an Exception.

gfg.HttpException: HTTP 404 Not Found

For the sake of clarity, we purposefully designed getUserWithError() to throw an Exception. Now, let’s go over how to properly handle exceptions in our code using Kotlin Coroutines.

1. Generic Approach

Consider the following scenario: we have a ViewModel, TryCatchViewModel (present in the project), and I want to perform my API call in the ViewModel.

Kotlin

class TryCatchViewModel(

    private val gfgUser: GfgUser,

    private val gfgCoursedb: DatabaseHelper

) : ViewModel() {

    private val gfg = MutableLiveData<Resource<List<GfgCourseUser>>>()

    fun fetchGfg() {

        viewModelScope.launch {

            gfg.postValue(Resource.loading(null))

            try {

                val gfgFromApi = gfgUser.getGfg()

                gfg.postValue(Resource.success(gfgFromApi))

            } catch (e: Exception) {

                gfg.postValue(Resource.error("Something Went Wrong", null))

            }

        }

    }

    fun getGfg(): LiveData<Resource<List<GfgCourseUser>>> {

        return gfg

    }

}

Without exception, this will return a list of users in my activity. Let’s say we add an exception to our fetchUsers() function. We will change the code as follows:

Kotlin

fun gfgGfgPro() {

    viewModelScope.launch {

        gfgPro.postValue(Resource.loading(null))

        try {

            val moreGfgProFromApi = apiHelper.getGfgProWithError()

            val gfgProFromApi = apiHelper.getGfgPro()

            val allGfgProFromApi = mutableListOf<ApiUser>()

            allGfgProFromApi.addAll(gfgProFromApi)

            allGfgProFromApi.addAll(moreGfgProFromApi)

            gfgPro.postValue(Resource.success(allGfgProFromApi))

        } catch (e: Exception) {

            gfgPro.postValue(Resource.error("Aw Snap!", null))

        }

    }

}

Without exception, this will return a list of users in my activity. Let’s say we add an exception to our fetchUsers() function. We will change the code as follows:

Kotlin

fun fetchGfgPro() {

    viewModelScope.launch {

        gfgPro.postValue(Resource.loading(null))

        try {

            val moreGfgProFromApi = try {

                apiHelper.getGfgProWithError()

            } catch (e: Exception) {

                emptyList<ApiGfgGfgUser>()

            }

            val gfgProFromApi = try {

                apiHelper.getGfgPro()

            } catch (e: Exception) {

                emptyList<ApiGfgGfgUser>()

            }

            val allGfgProFromApi = mutableListOf<ApiGfgGfgUser>()

            allGfgProFromApi.addAll(gfgProFromApi)

            allGfgProFromApi.addAll(moreGfgProFromApi)

            gfgPro.postValue(Resource.success(allGfgProFromApi))

        } catch (e: Exception) {

            gfgPro.postValue(Resource.error("Aw Snap", null))

        }

    }

}

In this case, we’ve added an individual exception to both API calls, so that if one occurs, an empty list is assigned to the variable and the execution continues.

This is an example of task execution in a sequential manner.

2. Making use of CoroutineExceptionHandler

In the preceding example, you can see that we are enclosing our code within a try-catch exception. When working with coroutines, however, we can handle exceptions by using a global coroutine exception handler called CoroutineExceptionHandler.

Kotlin

class ExceptionHandlerViewModel(

    private val gfgServerAPI: GfgServerAPI,

    private val gfgHelperDB: DatabaseHelper

) : ViewModel() {

    private val gfgUsers = MutableLiveData<Resource<List<ApiGfgUser>>>()

    private val exceptionHandler = CoroutineExceptionHandler { _, exception ->

        gfgUsers.postValue(Resource.error("Aw Snap!", null))

    }

    fun fetchGfgUsers() {

        viewModelScope.launch(exceptionHandler) {

            gfgUsers.postValue(Resource.loading(null))

            val gfgUsersFromApi = gfgServerAPI.getGfgUsers()

            gfgUsers.postValue(Resource.success(gfgUsersFromApi))

        }

    }

    fun getGfgUsers(): LiveData<Resource<List<ApiGfgUser>>> {

        return gfgUsers

    }

}

GeekTip: In this case, we’ve added getUsersWithError(), which will throw an exception and pass the handle to the handler.

Notice that we haven’t used a try-catch block here, and the exception will be handled by CoroutineExceptionHandler, which acts as a global exception handler for coroutine.

3. Employing SupervisorScope

We don’t want the execution of our task to be terminated because of an exception. But, so far, we’ve seen that whenever we encounter an exception, our execution fails and the task is terminated. We’ve already seen how to keep the task running in sequential execution; in this section, we’ll see how to keep the task running in parallel execution. So, before we get started with supervisorScope, let us first understand the issue with parallel execution. Assume we perform a parallel execution, such as:

Kotlin

private fun fetchGfgUsers() {

    viewModelScope.launch {

        gfgUsers.postValue(Resource.loading(null))

        try {

                val gfgUsersWithErrorFromGfgServerDeferred = async { gfgServerHelper.getGfgUsersWithError() }

                val moreGfgUsersFromGfgServerDeferred = async { gfgServerHelper.getMoreGfgUsers() }

                val gfgUsersWithErrorFromGfgServer = gfgUsersWithErrorFromGfgServerDeferred.await()

                val moreGfgUsersFromGfgServer = moreGfgUsersFromGfgServerDeferred.await()

                val allGfgUsersFromGfgServer = mutableListOf<GfgServerGfgUser>()

                allGfgUsersFromGfgServer.addAll(gfgUsersWithErrorFromGfgServer)

                allGfgUsersFromGfgServer.addAll(moreGfgUsersFromGfgServer)

                gfgUsers.postValue(Resource.success(allGfgUsersFromGfgServer))

        } catch (e: Exception) {

            gfgUsers.postValue(Resource.error(Aw Snap!", null))

        }

    }

}

When getUsersWithError() is called in this case, an exception will be thrown, resulting in the crash of our android application and the termination of our task’s execution. In this execution, we are performing parallel execution within coroutineScope, which is located within the try block. We would receive an exception from getUsersWithError(), and once the exception occurs, the execution would halt and the execution would proceed to the catch block.

GeekTip: When an exception occurs, the task’s execution will be terminated.

As a result, we can use supervisorScope in our task to overcome execution failure. In supervisorScope, we have two jobs running concurrently, one of which will throw an exception but the task will still be completed. In this case, we’re using async, which returns a Deferred that will deliver the result later. So, when we use await() to get the result, we use the try-catch block as an expression, so that if an exception occurs, an empty list is returned, but the execution is completed and we get a list of users.

Conclusion

  1. While not using async, we can use try-catch or the CoroutineExceptionHandler to achieve whatever we want to be based on our use-cases.
  2. When using async, we have two options in addition to try-catch: coroutineScope and supervisorScope.
  3. When using async, use supervisorScope with individual try-catch for each task in addition to the top-level try-catch if you want to continue with other tasks if one or more of them failed.
  4. Use coroutineScope with the top-level try-catch with async when you DO NOT want to continue with other tasks if any of them have failed.

Исключения

Классы исключений

Все исключения в Kotlin являются наследниками класса Throwable.
У каждого исключения есть сообщение, трассировка стека и (опционально) причина, по которой
это исключение вероятно было вызвано.

Для того чтобы возбудить исключение явным образом, используйте оператор throw.

throw Exception("Hi There!")

Чтобы перехватить исключение, используйте выражение trycatch.

try {
    // some code
} catch (e: SomeException) {
    // handler
} finally {
    // optional finally block
}

В коде может быть любое количество блоков catch (такие блоки могут и вовсе отсутствовать). Блоки finally
могут быть опущены. Однако, должен быть использован как минимум один блок catch или finally.

Try — это выражение

try является выражением, что означает, что оно может иметь возвращаемое значение.

val a: Int? = try { input.toInt() } catch (e: NumberFormatException) { null }

Возвращаемым значением будет либо последнее выражение в блоке try, либо последнее выражение
в блоке catch (или блоках). Содержимое finally блока никак не повлияет на результат try-выражения.

Проверяемые исключения

В Kotlin нет проверяемых исключений. Для этого существует целый ряд причин, но мы рассмотрим простой пример, который иллюстрирует причину этого.

Приведённый ниже фрагмент кода является примером простого интерфейса в JDK, который реализован в классе StringBuilder.

Appendable append(CharSequence csq) throws IOException;

Сигнатура говорит, что каждый раз, когда я присоединяю строку к чему-то (к StringBuilder, какому-нибудь логу, сообщению в консоль и т.п),
мне необходимо отлавливать исключения типа IOExceptions. Почему? Потому, что данная операция может вызывать IO (Input-Output: Ввод-Вывод) (Writer также
реализует интерфейс Appendable).
Данный факт постоянно приводит к написанию подобного кода:

try {
    log.append(message)
} catch (IOException e) {
    // Должно быть безопасно
}

И это плохо. См. Effective Java, Item 77: Don’t ignore exceptions (не игнорируйте исключения).

Брюс Эккель как-то сказал о проверяемых исключения:

Анализ небольших программ показал, что обязательная обработка исключений может повысить производительность разработчика и улучшить качество кода.
Однако, изучение крупных проектов по разработке программного обеспечения позволяет сделать противоположный вывод:
происходит понижение продуктивности и сравнительно небольшое улучшение кода (а иногда и без всякого улучшения).

Вот несколько других рассуждений по этому поводу:

  • Java’s checked exceptions were a mistake (Rod Waldhoff)
  • The Trouble with Checked Exceptions (Anders Hejlsberg)

Если вы хотите предупредить вызывающие объекты о возможных исключениях при вызове Kotlin кода из Java, Swift или Objective-C,
вы можете использовать аннотацию @Throws. Узнайте больше об использовании этой аннотации для Java,
а также для Swift и Objective-C.

Тип Nothing

throw в Kotlin является выражением, поэтому есть возможность использовать его, например, в качестве части Elvis-выражения:

val s = person.name ?: throw IllegalArgumentException("Name required")

Типом выражения throw является специальный тип под названием Nothing.
У этого типа нет никаких значений, он используется для того, чтобы обозначить те участки кода, которые могут быть не достигнуты никогда.
В своём коде вы можете использовать Nothing для того, чтобы отметить функцию, чей результат никогда не будет возвращён.

fun fail(message: String): Nothing {
    throw IllegalArgumentException(message)
}

При вызове такой функции компилятор будет в курсе, что исполнения кода далее не последует:

val s = person.name ?: fail("Name required")
println(s) // известно, что переменная 's' проинициализирована к этому моменту

Вы также можете столкнуться с этим типом при работе с выводом типов.
Nullable-вариант этого типа Nothing? имеет ровно одно возможное значение — null.
Если вы используете null для инициализации значения предполагаемого типа, и нет никакой другой информации,
которую можно использовать для определения более конкретного типа, компилятор определит тип Nothing?.

val x = null           // у 'x' тип `Nothing?`
val l = listOf(null)   // у 'l' тип `List<Nothing?>

Совместимость с Java

См. раздел, посвящённый исключениям, Страница совместимости Java для получения информации о совместимости с Java.

Понравилась статья? Поделить с друзьями:
  • Error handler is invoked сталкер тень чернобыля
  • Error handler is invoked сталкер народная солянка 2016
  • Error handler is invoked сталкер лост альфа
  • Error handler is invoked перевод
  • Error handler is invoked ogsr