@@ -144,8 +144,8 @@ Handlers are registered in a `Map<String, RequestHandler>` keyed by OpenAPI `ope
144144``` java
145145public class YourServerLauncher {
146146 public static void main (String [] args ) throws Exception {
147- // Gson is on the classpath , so we can load the spec in one line .
148- Spec spec = Spec . fromPath( Path . of( " openapi.json" ) );
147+ // openapi.json lives in src/main/resources/ , so it ships at the JAR root .
148+ Spec spec = Spec . fromClasspath( YourServerLauncher . class, " / openapi.json" );
149149
150150 Map<String , RequestHandler > handlers = new HashMap<> ();
151151 handlers. put(" get-data" , getDataHandler);
@@ -162,22 +162,35 @@ public class YourServerLauncher {
162162
163163## Spec loading
164164
165- ` Spec.fromPath(Path) ` picks the parser by file extension: ` .json ` is parsed by Gson, ` .yaml ` /
166- ` .yml ` by SnakeYAML. Both are optional dependencies — the same Gson that powers the built-in JSON
165+ ` Spec.fromClasspath(Class<?>, String) ` is the recommended way to load a spec packaged with your
166+ application. It picks the parser by file extension: ` .json ` is parsed by Gson, ` .yaml ` / ` .yml `
167+ by SnakeYAML. Both are optional dependencies — the same Gson that powers the built-in JSON
167168` TypeMapper ` , and the same SnakeYAML you'd add explicitly to parse YAML. If the required parser
168- isn't on the classpath the call fails with ` IllegalStateException ` ; parse the file yourself and
169- use ` Spec.from(Map<String, Object>) ` instead. Any other extension is rejected.
169+ isn't on the classpath the call fails with ` IllegalStateException ` ; parse the resource yourself
170+ and use ` Spec.from(Map<String, Object>) ` instead. Any other extension is rejected.
170171
171- To load a spec from the classpath (including from inside a JAR) use the ` InputStream ` overloads:
172+ ``` java
173+ // Spec at src/main/resources/openapi.json → JAR root → absolute path.
174+ Spec spec = Spec . fromClasspath(YourServerLauncher . class, " /openapi.json" );
175+ ```
176+
177+ ** Mind the leading slash.** ` fromClasspath ` resolves the resource via
178+ ` Class.getResourceAsStream ` , which is package-relative * unless* the name starts with ` / ` . So
179+ ` "/openapi.yaml" ` means "JAR root" (typical for ` src/main/resources/openapi.yaml ` ), while
180+ ` "openapi.yaml" ` means "next to ` YourServerLauncher.class ` " — i.e. the file must live under
181+ ` src/main/resources/<your/package>/openapi.yaml ` . Easy to miss; if you get
182+ ` IllegalArgumentException: classpath resource not found ` , the slash is the first thing to check.
183+
184+ If you already have the bytes or are loading from somewhere other than the classpath, the
185+ ` InputStream ` overloads work too — both close the stream before returning:
172186
173187``` java
174188Spec spec;
175- try (InputStream in = YourServerLauncher . class . getResourceAsStream( " / openapi.json" )) {
176- spec = Spec . fromJson(in); // Gson on the classpath
189+ try (InputStream in = Files . newInputStream( Path . of( " openapi.json" ) )) {
190+ spec = Spec . fromJson(in); // or Spec.fromYaml(in)
177191}
178192```
179193
180- The matching ` Spec.fromYaml(InputStream) ` uses SnakeYAML. Both close the stream before returning.
181194If you can't (or don't want to) depend on Gson, supply your own JSON parser:
182195
183196``` java
@@ -965,7 +978,7 @@ public final class App {
965978 static final ScopedValue<String> CORRELATION_ID = ScopedValue.newInstance();
966979
967980 public static void main(String[] args) throws Exception {
968- Spec spec = Spec.fromPath(Path.of(" openapi.yaml")); // SnakeYAML parses the spec
981+ Spec spec = Spec.fromClasspath(App.class, "/ openapi.yaml"); // SnakeYAML parses the spec
969982
970983 RequestHandler getPromotion = req -> {
971984 String id = req.pathParam("id");
@@ -998,8 +1011,8 @@ What the example demonstrates:
9981011
9991012- **Gson is the default JSON serializer.** No explicit `bodyMapper(...)` call — the library
10001013 auto-registers `GsonJsonMapper` for request and response JSON because Gson is on the classpath.
1001- - **SnakeYAML parses the spec.** `Spec.fromPath (...)` picks the parser by file extension; `.yaml`
1002- here means SnakeYAML, and Gson would handle `.json` the same way.
1014+ - **SnakeYAML parses the spec.** `Spec.fromClasspath (...)` picks the parser by file extension;
1015+ ` .yaml ` here means SnakeYAML, and Gson would handle `.json` the same way.
10031016- **One interceptor sets cross-cutting context.** `ScopedValue.where(...).call(next::proceed)`
10041017 runs the handler (and any inner interceptors and decorators) inside the binding, so
10051018 ` TENANT.get()` and `CORRELATION_ID.get()` work anywhere they're called.
0 commit comments