gosh...

        try {
            val user = authService.register(registrationRequest.email, registrationRequest.password)

            return user
        } catch (exception: Exception) {
            // log exception
            throw exception
        }


no, no, no!

the whole point of the exceptions (and moreso of the unchecked ones) is to be transparent!

if you don't know what to do with an exception do NOT try to handle it

that snippet should just be

    return authService.register(registrationRequest.email, registrationRequest.password)

I'm gonna plug my favorite note on this topic: https://ericlippert.com/2008/09/10/vexing-exceptions/

Both snippets suffer from being too limited. The first, as you point out, catches too many exceptions. But the second.... What happens if the email address is taken? That's hardly exceptional, but it's an exception that the caller has to handle. Your natural response might be to check if the email address is taken before calling register, but that's just a race condition now. So you really need a result-returning function, or to catch some (but probably not all) of the possible exceptions from the method.

The way I usually structure it is that the only exception would be some type of failure to connect. Any actual error thrown by the service comes back as a result.error, and any failure (like email address taken) comes back as result.fail. This way you can separate it into (1) connection problem, (2) backend error/bug/database offline/etc, (3) both of those are fine but the backend doesn't like the input.

I agree, this was just a sample code to show how usually imperative if / else / try / catch code is written. What is also possible is we catch the exception, log it and throw another one.