I'm designing a simple toy interpreter, and I have a custom exception like so:
class ValError(varName: String) : Exception("$varName is val, cannot reassign.")
The problem is, when it's thrown, it prints out the whole call stacktrace like so:
ValError: foo is val, cannot reassign.
at com.github.me.dynamik.interpreter.Environment.get(Environment.kt:62)
at com.github.me.dynamik.interpreter.TreeWalker.visitVariableExpr(TreeWalker.kt:154)
at com.github.me.dynamik.expressions.VariableExpr.evaluateBy(Expression.kt:57)
but I only want it to print out the first line
ValError: foo is val, cannot reassign.
How do I accomplish this?
Short answer: catch the exception, and print its message field. You can do this in a try
…catch
block in your top-level function, or (better) in an UncaughtExceptionHandler.
Long boring answer:
An exception will get passed up from each function to its caller, until it finds one with an enclosing try
…catch
block.
So one approach is to catch the exception yourself, e.g.:
fun main() {
try {
// All your top-level code here…
} catch (x: Exception) {
System.err.println(x.message)
}
}
If you're using a logging framework, you can use that instead, of course.
If there's no enclosing try
…catch
, it will call the thread's UncaughtExceptionHandler — or, if the thread doesn't have one, its ThreadGroup's one, or failing that, the default one.
If you don't provide a default one, the system's one calls its printStackTrace() method, which prints the exception message and all its backtrace to the standard error stream — which is why you see the trace shown in the question.
So another approach is to set an UncaughtExceptionHandler at one of those levels, e.g.:
Thread.setDefaultUncaughtExceptionHandler { t, x ->
System.err.println(x.message)
}
That's a lot safer, because it works for all threads.
If you're writing a simple app that only uses a single thread, then it can be straightforward to catch the exception in your top-level method. But if you're writing something which uses a GUI toolkit or web framework, or uses coroutines or another execution framework, or start any threads manually, then there are likely to be other threads running. This has two effects:
First, you won't have access to the top-level methods of those threads, and so you won't be able to catch exceptions there. So an UncaughtExceptionHandler is the only way. (The default one is safest, as you probably won't have access to the Threads nor even ThreadGroups involved.)
Second, although the thread with the exception will stop running, any other threads won't. So the JVM won't shut down, and other threads will carry on running, unaware. This leaves your app in an inconsistent state, which could be disastrous: you could end up with jobs queueing up but never run, or other threads stalling waiting for data, or requests going unanswered, or just about anything at all depending how your app is structured and which thread died. And because the JVM continues, system-level monitors or service managers won't know there's anything wrong.
So after logging the exception, in some cases it can be a very good idea for your handler to shut down the whole app.
(BTW, a third approach would be to use a custom Exception
and override its printStackTrace()
method to print only the message. But that's not a good idea; it defeats the intent of that method, which could cause all sorts of problems for anything that uses it.)