Main Method Declares Throws Clause
ID |
java.main_throw_anything |
Severity |
critical |
Remediation Complexity |
trivial |
Remediation Risk |
low |
Remediation Effort |
low |
Resource |
Reliability |
Language |
Java |
Tags |
reliability |
Description
Reports a public static void main(String[]) entry point that declares a throws clause, letting a checked or unchecked exception escape the JVM launcher instead of being handled.
Rationale
Nothing in the call stack can catch what main throws — it is the top of the stack. When the launcher receives the exception it prints a raw stack trace to standard error (or nothing at all, depending on how the process was started) and exits with a generic non-zero code, with no chance for the application to add context, log through its own logging setup, or choose a meaningful exit code for its callers.
public static void main(String[] args) throws IOException {
var config = ConfigLoader.load(args[0]);
run(config);
}
Remediation
Catch the exception inside main, log it with whatever context is available, and exit with a non-zero status explicitly.
public static void main(String[] args) {
try {
var config = ConfigLoader.load(args[0]);
run(config);
} catch (Exception e) {
LOGGER.error("startup failed", e);
System.exit(1);
}
}
What is not reported
Only the standard entry-point signature is in scope. A method named main that is not public, not static, does not return void, or does not take exactly one String[] parameter is not a JVM entry point, so a throws clause on it is left alone.
Two kinds of entry point are exempt, because for neither is there a user to report the failure to:
-
Test code. A launcher in a system-test or upgrade-test module exists to be run by a harness that reads the exit status and the stack trace — which is exactly what a propagating exception gives it. Decided by the user-configured
applicativeCode.excludedPathPatternsplus thesrc/it/javaconvention. -
JMH benchmark classes, recognised by an
org.openjdk.jmh.annotations.*annotation on the class or on one of its methods. A benchmark’smainis a two-linenew Runner(opts).run()whoseRunnerExceptionis the harness’s own diagnostic; catching it to callSystem.exit(1)would report strictly less.
A plain performance harness that merely lives in a benchmark module is not exempt: without a JMH annotation nothing distinguishes it structurally from any other production entry point, and the module name is not a signal.