Yes, you can write a static analysis to do this. I did something similar to myself and wrote mine in a software analytic tool called Atlas . Here: https://github.com/EnSoftCorp/java-toolbox-commons/.../ThrowableAnalysis.java is code that can be useful for what you need, it statically calculates matches for throwing sites and potential catch sites in terms of software (conservative in the sense that it does not consider the possibility of implementation). For your case, you are interested in throwing sites that do not have a matching catch block.
Here are the important bits of analysis.
- All marked or unchecked exceptions should extend Throwable . It seems that you are only interested in "unverified" metadata, so you should consider classes that are directly distributed or are children of a class that extends Error or RuntimeException .

In Atlas Shell, you can write the following queries to find all unverified objects.
var supertypeEdges = Common.universe().edgesTaggedWithAny(XCSG.Supertype) var errors = supertypeEdges.reverse(Common.typeSelect("java.lang", "Error")) var uncheckedExceptions = supertypeEdges.reverse(Common.typeSelect("java.lang", "RuntimeException")) show(errors.union(uncheckedExceptions))
Any exception that can be detected at runtime (checked or not checked) must have a corresponding “throw” to the site. Although the thrown checked exception must be declared in the method signature, it does not need to be declared for the thrown unchecked exception. However, this is not so important, because we can detect all the selected excluded exceptions by simply looking at the type hierarchy, as we discussed in step 1.
In order to correlate the throwing site with the corresponding catch block, we must remember that the excluded exception maintains a backup of the call stack until it is detected (or the program crashes if it is not caught by the main method or by writing a dot stream). For this analysis, you need a call graph (the more accurate the call graph, the more accurate your analysis will be). For each throw of an uncontrolled type of exception, step backward along the schedule of calls to the calling element of the method, which can cause an uncontrolled exception. Check if the call site is contained in the try block (or has a trap area if you are analyzing bytecode). If this is the case, you should check the compatibility of the catch / trap blocks and determine if an exception will be detected. If the exception is not caught, repeat the process, stepping back along the call schedule to each calling site until the exception is caught or there is no possible catch block.
Using the ThrowableAnalysis code I shared earlier, you could put it all together to find all non-displayable abandoned unchecked types.
public class Analysis { // execute show(Analysis.run()) on the Atlas shell public static Q run(){ Q supertypeEdges = Common.universe().edgesTaggedWithAny(XCSG.Supertype); Q errors = supertypeEdges.reverse(Common.typeSelect("java.lang", "Error")); Q uncheckedExceptions = supertypeEdges.reverse(Common.typeSelect("java.lang", "RuntimeException")); Q typeOfEdges = Common.universe().edgesTaggedWithAny(XCSG.TypeOf); Q thrownUncheckedThrowables = typeOfEdges.predecessors(errors.union(uncheckedExceptions)).nodesTaggedWithAny(XCSG.ThrownValue); AtlasSet<Node> uncaughtThrownUncheckedThrowables = new AtlasHashSet<Node>(); for(Node thrownUncheckedThrowable : thrownUncheckedThrowables.eval().nodes()){ if(ThrowableAnalysis.findCatchForThrows(Common.toQ(thrownUncheckedThrowable)).eval().nodes().isEmpty()){ uncaughtThrownUncheckedThrowables.add(thrownUncheckedThrowable); } } Q uncaughtThrownUncheckedThrowableMethods = Common.toQ(uncaughtThrownUncheckedThrowables).containers().nodesTaggedWithAny(XCSG.Method); Q callEdges = Common.universe().edgesTaggedWithAny(XCSG.Call); Q rootMethods = callEdges.reverse(uncaughtThrownUncheckedThrowableMethods).roots(); Q callChainToUncaughtThrowables = callEdges.between(rootMethods, uncaughtThrownUncheckedThrowableMethods); return callChainToUncaughtThrowables.union(Common.toQ(uncaughtThrownUncheckedThrowables)); } }
Here is a screenshot of the result of running this code in the following test example.
public class Test { public static void main(String[] args) { foo(); } private static void foo(){ throw new Pig("Pigs can fly!"); } public static class Pig extends RuntimeException { public Pig(String message){ super(message); } } }

Important warnings:. You must decide whether you want to do all the analysis of the program here. If you analyze your code, and not the full JDK (several million lines of code), then you will find only runtime exceptions that occur inside your application. For example, you will not catch "test" .substring (0.10), which throws an exception from the limits inside the substring method declared in the String class in the JDK. Although Atlas supports partial or full program analysis using a Java source or bytecode and can scale to a full JDK, you will need to allocate about an hour of preprocessing time and 20 gigabytes of memory if you plan to enable full JDK.