{"record":{"id":"4fa375a56747b6a7","repo":"apache/beam","slug":"class-is-not-exposed","errorCode":null,"errorMessage":"Class  is not exposed.","messagePattern":"Class  is not exposed\\.","errorType":"exception","errorClass":"IllegalArgumentException","httpStatus":null,"severity":"error","filePath":"sdks/java/core/src/main/java/org/apache/beam/sdk/util/ApiSurface.java","lineNumber":430,"sourceCode":"      return path;\n    }\n  }\n\n  /**\n   * Returns a path from an exposed class to a root class. There may be many, but this gives only\n   * one. It will not return a path that crosses the excluded classes.\n   *\n   * <p>If there are only cycles or paths through the excluded classes, returns null.\n   *\n   * <p>If the class is not actually in the exposure map, throws IllegalArgumentException\n   */\n  private List<Class<?>> getAnyExposurePath(Class<?> exposedClass, Set<Class<?>> excluded) {\n    List<Class<?>> exposurePath = Lists.newArrayList();\n    exposurePath.add(exposedClass);\n\n    Collection<Class<?>> exposers = getExposedToExposers().get(exposedClass);\n    if (exposers.isEmpty()) {\n      throw new IllegalArgumentException(\"Class \" + exposedClass + \" is not exposed.\");\n    }\n\n    for (Class<?> exposer : exposers) {\n      if (excluded.contains(exposer)) {\n        continue;\n      }\n\n      // A null exposer means this is already a root class.\n      if (exposer == null) {\n        return exposurePath;\n      }\n\n      List<Class<?>> restOfPath =\n          getAnyExposurePath(exposer, Sets.union(excluded, Sets.newHashSet(exposer)));\n\n      if (restOfPath != null) {\n        exposurePath.addAll(restOfPath);\n        return exposurePath;","sourceCodeStart":412,"sourceCodeEnd":448,"githubUrl":"https://github.com/apache/beam/blob/12126d8942aaf848030c478b4c6a28c6af861c66/sdks/java/core/src/main/java/org/apache/beam/sdk/util/ApiSurface.java#L412-L448","documentation":"The private getAnyExposurePath(class, excluded) overload assumes the queried class has at least one 'exposer' (a class it is exposed through). If the exposure map has no entry, the class was never actually exposed, and the method throws IllegalArgumentException('Class X is not exposed.'). It is a precondition check protecting the graph walk from querying unindexed classes.","triggerScenarios":"The private path walk is entered with a class absent from getExposedToExposers() — practically reached when the public entry point (or recursive walk) is invoked for a class never registered as exposed on this ApiSurface instance.","commonSituations":"Querying an ApiSurface built for a different root set than the class of interest; classes filtered out by predicates (e.g. only-public-classes filters) but still passed to path lookups in custom tooling.","solutions":["Check membership in the exposed set before requesting an exposure path for a class.","Rebuild the ApiSurface with roots/predicates that include the queried class (see ApiSurface.of(rootClasses, predicates)).","Treat this exception as 'not exposed' in tooling and report accordingly instead of crashing."],"exampleFix":"// before\nList<Class<?>> path = apiSurface.getAnyExposurePath(candidate);\n// after\nSet<Class<?>> exposed = apiSurface.getExposedClasses();\nif (!exposed.contains(candidate)) { return; } // not exposed, skip\nList<Class<?>> path = apiSurface.getAnyExposurePath(candidate);","handlingStrategy":"try-catch","validationCode":"if (!surface.getExposedClasses().contains(candidate)) { return; }","typeGuard":null,"tryCatchPattern":"try {\n  return surface.getAnyExposurePath(candidate);\n} catch (IllegalArgumentException e) {\n  return null; // not exposed\n}","preventionTips":["Verify membership in the exposed set before path lookups.","Include the class's package in the surface roots/predicates.","In tooling, model 'not exposed' as a normal result, not an exception path."],"tags":["java","apache-beam","api-surface","internal-state"],"backgroundTag":"internal-invariant-violation","analyzedSha":"12126d8942aaf848030c478b4c6a28c6af861c66","analyzedAt":"2026-09-13T01:50:10.254Z","contentChangedAt":"2026-09-13T01:50:10.254Z","schemaVersion":2},"datasetVersion":"2026-09-20T03:17:13.778Z"}