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.excludedPathPatterns plus the src/it/java convention.

  • JMH benchmark classes, recognised by an org.openjdk.jmh.annotations.* annotation on the class or on one of its methods. A benchmark’s main is a two-line new Runner(opts).run() whose RunnerException is the harness’s own diagnostic; catching it to call System.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.

© 2026 Xygeni. All rights reserved