Skip to content

Entry points

An entry point is a method the runtime (or a framework) can invoke without an in-program caller: a main, a REST handler, a servlet, a scheduled job. They matter because they anchor the WALA call graph and seed reachability — a taint path has to start somewhere.

codeanalyzer-java detects entry points through an EntrypointsFinderFactory that dispatches to per-framework finders. Detected entry points surface in the schema as:

  • type.is_entrypoint_classtrue on the type when it’s a recognized entry-point class (including any class with a main(String[])).
  • callable.is_entrypointtrue on the callable for the specific entry-point method.

Class-level annotations: @RestController, @Controller, @HandlerInterceptor, @SpringBootApplication, @Configuration, @Component, @Service, @Repository.

Interfaces: classes implementing CommandLineRunner or ApplicationRunner.

Method-level annotations: @GetMapping, @PostMapping, @PutMapping, @DeleteMapping, @PatchMapping, @RequestMapping, @EventListener, @Scheduled, @KafkaListener, @RabbitListener, @JmsListener, @PreAuthorize, @PostAuthorize, @PostConstruct, @PreDestroy, plus AOP advice (@Around, @Before, @After) and batch scopes (@JobScope, @StepScope).

Independent of any framework, a class declaring public static void main(String[] args) is an entry point. Its type gets is_entrypoint_class: true and the main callable gets is_entrypoint: true. This is the baseline anchor for ordinary applications.

Why entry points matter for the call graph

Section titled “Why entry points matter for the call graph”

WALA builds the call graph by traversing outward from entry points. If a project has no main and none of the framework patterns above match, WALA may have nothing to anchor on and the call_graph can come back empty. When that happens, confirm the project actually has a recognized entry point — see Troubleshooting.

Once analyzed, you can filter on the flags to drive reachability. Via the Python SDK, against an in-process analysis:

analysis = CLDK(language="java").analysis(
project_path="my-web-app",
analysis_level=AnalysisLevel.call_graph,
)
# Every method flagged as an entry point
entrypoints = [
(cls, sig)
for cls in analysis.get_classes()
for sig, m in analysis.get_methods_in_class(cls).items()
if m.is_entrypoint
]

Seed a networkx reachability query from these to ask whether a sink is reachable from any externally-invocable method.

When you project to a Neo4j property graph with --emit neo4j, the is_entrypoint / is_entrypoint_class properties are still carried on the :JCallable and :JType nodes — but the projection also layers a marker label, :JEntrypoint, onto the owning callable (and entry-point class). That turns reachability seeding into a one-line MATCH instead of a property filter:

// Every entry-point method in one application — the reachability seeds
MATCH (a:JApplication {name: 'daytrader8'})-[:J_HAS_UNIT]->(:JCompilationUnit)
-[:J_DECLARES_TYPE]->(:JType)-[:J_HAS_CALLABLE]->(c:JCallable:JEntrypoint)
RETURN c.signature

From there you traverse J_CALLS edges (present once you analyze at level 2) to ask the same reachability question entirely in Cypher, across every application in the database. Reading the seeds back through the SDK is identical to the in-process path above — point the facade at the graph and the same typed JCallable objects come back, with no JDK, native binary, or project source on the consumer:

from cldk import CLDK
from cldk.analysis import AnalysisLevel
from cldk.analysis.commons.backend_config import Neo4jConnectionConfig
analysis = CLDK.java(
analysis_level=AnalysisLevel.call_graph,
backend=Neo4jConnectionConfig(
uri="bolt://localhost:7687",
username="neo4j",
password="neo4j", # or set NEO4J_PASSWORD
application_name="daytrader8", # must match the --app-name the graph was loaded with
),
)
entrypoints = analysis.get_entry_point_methods()