@@ -90,10 +90,39 @@ For multi-class checks, and for negated exclusions, add a version comment **abov
9090name string** .
9191
9292- ** Positive floor class** in ` hasClassesNamed(...) ` : use ` // added in X.Y ` .
93+ When the landmark class comes from a different artifact than the instrumentation's usual
94+ library module, choose the form based on what the comment is documenting:
95+ - If the version boundary is for that different artifact itself, use
96+ ` // added in groupId:artifactId X.Y ` .
97+ - If the comment is documenting the main library-module boundary or an optional-component
98+ presence condition that happens via a different artifact, use
99+ ` // added in X.Y (via groupId:artifactId A.B) ` .
100+ - For ` javax.servlet:javax.servlet-api ` and ` jakarta.servlet:jakarta.servlet-api ` , use the
101+ shorthand ` Servlet ` instead of the full Maven coordinate, for example `// added in Servlet
102+ 5.0` or ` // added in X.Y (via Servlet 5.0)`.
93103- ** Positive ceiling class** in ` hasClassesNamed(...) ` : use ` // removed in Y.Z ` .
104+ When the landmark class comes from a different artifact than the instrumentation's usual
105+ library module, choose the form based on what the comment is documenting:
106+ - If the version boundary is for that different artifact itself, use
107+ ` // removed in groupId:artifactId Y.Z ` .
108+ - If the comment is documenting the main library-module boundary through a class that is
109+ supplied by a different artifact, use ` // removed in Y.Z (via groupId:artifactId A.B) ` .
110+ - For ` javax.servlet:javax.servlet-api ` and ` jakarta.servlet:jakarta.servlet-api ` , use the
111+ shorthand ` Servlet ` instead of the full Maven coordinate, for example `// removed in Servlet
112+ 5.0` or ` // removed in Y.Z (via Servlet 5.0)`.
94113- ** Negated exclusion class** in ` not(hasClassesNamed(...)) ` or
95114 ` .and(not(hasClassesNamed(...))) ` : use ` // added in Y.Z ` , because that class's first
96115 appearance is what starts excluding ` Y.Z+ ` .
116+ When the landmark class comes from a different artifact than the instrumentation's usual
117+ library module, choose the form based on what the comment is documenting:
118+ - If the excluded range begins when that different artifact appears, use
119+ ` // added in groupId:artifactId Y.Z ` .
120+ - If the excluded range is described in terms of the main library-module version but the
121+ landmark class is supplied by a different artifact, use
122+ ` // added in Y.Z (via groupId:artifactId A.B) ` .
123+ - For ` javax.servlet:javax.servlet-api ` and ` jakarta.servlet:jakarta.servlet-api ` , use the
124+ shorthand ` Servlet ` instead of the full Maven coordinate, for example `// added in Servlet
125+ 5.0` or ` // added in Y.Z (via Servlet 5.0)`.
97126
98127You may add brief parenthetical context, but only when it adds interesting, non-duplicative
99128signal beyond the version role itself, for example native instrumentation notes, backport
@@ -121,6 +150,29 @@ The compact form does **not** apply when matchers are chained (e.g., positive +
121150In that case, follow the multi-class rule: place each version comment directly above its
122151class name string.
123152
153+ Within a chain, each ` hasClassesNamed("...") ` call with a ** single inline class string** —
154+ whether used directly, as ` not(hasClassesNamed("...")) ` , or inside ` .and(...) ` — should
155+ keep the class on one line and place its version comment on the line immediately above
156+ that call:
157+
158+ ``` java
159+ // added in 4.0.0.Final
160+ return hasClassesNamed(" io.netty.handler.codec.http.HttpMessage" )
161+ // added in 4.1.0.Final
162+ .and(not(hasClassesNamed(" io.netty.handler.codec.http.CombinedHttpHeaders" )));
163+ ```
164+
165+ ``` java
166+ // removed in 4.0
167+ return not(hasClassesNamed(" io.vertx.core.Starter" ))
168+ // added in 5.0
169+ .and(not(hasClassesNamed(" io.vertx.core.http.impl.HttpClientConnectionInternal" )));
170+ ```
171+
172+ Only break a class onto its own line (with the comment indented above it) when that call
173+ has multiple class strings, or when the chained landmark requires a multi-line two-line
174+ form (such as the artifact-presence-gate two-liner).
175+
124176** How to identify ceiling classes** : look for a ** newer sibling module** for the same
125177library, such as ` mongo-4.0 ` next to ` mongo-3.7 ` . If the newer module's
126178` classLoaderMatcher() ` checks a different variant of the same class, such as
@@ -184,7 +236,8 @@ OpenTelemetry instrumentation and the agent should opt out.
184236public ElementMatcher . Junction<ClassLoader > classLoaderMatcher() {
185237 // added in 7.0.0
186238 return hasClassesNamed(" org.elasticsearch.client.RestClient$InternalRequest" )
187- // added in 8.10 (native OTel support)
239+ // artifact presence gate (native OTel support)
240+ // added in co.elastic.clients:elasticsearch-java 8.10
188241 .and(not(hasClassesNamed(
189242 " co.elastic.clients.transport.instrumentation.Instrumentation" )));
190243}
@@ -195,13 +248,117 @@ public ElementMatcher.Junction<ClassLoader> classLoaderMatcher() {
195248``` java
196249@Override
197250public ElementMatcher . Junction<ClassLoader > classLoaderMatcher() {
198- // added in azure-core 1.53
251+ // added in 1.53
199252 return hasClassesNamed(" com.azure.core.util.LibraryTelemetryOptions" )
200- // added in azure-core-tracing-opentelemetry 1.0.0-beta.47
253+ // artifact presence gate (native OTel support)
254+ // added in com.azure:azure-core-tracing-opentelemetry 1.0.0-beta.47
201255 .and(not(hasClassesNamed(" com.azure.core.tracing.opentelemetry.OpenTelemetryTracer" )));
202256}
203257```
204258
259+ #### Main target artifact
260+
261+ Bare version numbers like ` // added in 2.2.0 ` resolve to ** the ` InstrumentationModule ` 's main
262+ target artifact** , which is normally the artifact pinned by the module's muzzle
263+ ` pass { group + module } ` block in ` build.gradle.kts ` .
264+
265+ When a single gradle module hosts ** multiple ` InstrumentationModule ` classes targeting
266+ different artifacts** (rare — for example ` aws-sdk-2.2/javaagent ` has separate
267+ ` InstrumentationModule ` s for ` :aws-core ` , ` :sqs ` , ` :sns ` , ` :lambda ` , and ` :bedrock-runtime ` ),
268+ open each ` classLoaderMatcher() ` with a one-line comment naming the target artifact. Bare
269+ version numbers in the landmark comments then resolve unambiguously to that artifact.
270+
271+ ``` java
272+ @Override
273+ public ElementMatcher . Junction<ClassLoader > classLoaderMatcher() {
274+ // this instrumentation module targets software.amazon.awssdk:lambda
275+ return hasClassesNamed(
276+ // added in 2.2.0
277+ " software.amazon.awssdk.services.lambda.model.InvokeRequest" ,
278+ // added in 2.17.0 (via software.amazon.awssdk:json-utils 2.17.0)
279+ " software.amazon.awssdk.protocols.jsoncore.JsonNode" );
280+ }
281+ ```
282+
283+ ` (via groupId:artifactId A.B) ` still means the landmark lives in a required transitive of
284+ the target artifact. ` // added in groupId:artifactId X.Y ` (no ` via ` ) still means the landmark
285+ lives in an independent, opt-in artifact whose presence itself is the gate.
286+
287+ Single-` InstrumentationModule ` gradle modules do not need this header.
288+
289+ #### Artifact presence gate annotation
290+
291+ When a landmark class lives in an ** opt-in artifact that is not the module's target
292+ artifact** , use a two-line role comment: ` // artifact presence gate ` followed by
293+ ` // added in groupId:artifactId X.Y ` . Parenthetical context goes on whichever line it
294+ qualifies — attach it to ` artifact presence gate ` when it describes ** why** the gate
295+ exists (e.g. ` native OTel support ` ), and to ` added in … ` when it qualifies the ** coordinate
296+ or version** (e.g. ` renamed from extension.incubator ` ):
297+
298+ ``` java
299+ // artifact presence gate
300+ // added in groupId:artifactId X.Y
301+ ```
302+
303+ ``` java
304+ // artifact presence gate (why the gate exists)
305+ // added in groupId:artifactId X.Y
306+ ```
307+
308+ ``` java
309+ // artifact presence gate
310+ // added in groupId:artifactId X.Y (coordinate/version qualifier)
311+ ```
312+
313+ The ` artifact presence gate ` label signals "this check is not pinning a version
314+ boundary of the target artifact — it is detecting that an independent, opt-in dependency
315+ is on the classpath". The version X.Y is the first version of that opt-in artifact where
316+ the class existed, for completeness. "Artifact presence" distinguishes this from the
317+ ordinary class-presence role of ` hasClassesNamed ` itself.
318+
319+ Typical cases:
320+
321+ - ` .and(not(hasClassesNamed(...))) ` to back off when a separately-shipped native OTel
322+ bridge is present.
323+ - ` .and(hasClassesNamed(...)) ` to require an add-on that is not a required transitive of
324+ the target artifact.
325+
326+ Examples:
327+
328+ ``` java
329+ // artifact presence gate (native OTel support)
330+ // added in com.azure:azure-core-tracing-opentelemetry 1.0.0-beta.47
331+ .and(not(hasClassesNamed(" com.azure.core.tracing.opentelemetry.OpenTelemetryTracer" )));
332+ ```
333+
334+ ``` java
335+ // artifact presence gate (native OTel support)
336+ // added in co.elastic.clients:elasticsearch-java 8.10
337+ .and(not(hasClassesNamed(" co.elastic.clients.transport.instrumentation.Instrumentation" )));
338+ ```
339+
340+ Do ** not** use the ` artifact presence gate ` label when the landmark class lives in the
341+ target artifact or a required transitive — those are ordinary version boundaries and use
342+ the usual role comments (` // added in X.Y ` , ` // added in X.Y (via groupId:artifactId A.B) ` ).
343+ Conversely, do not omit the ` groupId:artifactId ` qualifier on the ` added in ` line when
344+ using the ` artifact presence gate ` label: the label and the qualifier always go together.
345+
346+ #### ` io.opentelemetry* ` groupId shorthand
347+
348+ For artifacts whose ` groupId ` starts with ` io.opentelemetry ` (for example
349+ ` io.opentelemetry:opentelemetry-api ` , ` io.opentelemetry:opentelemetry-api-incubator ` ,
350+ ` io.opentelemetry.instrumentation:opentelemetry-instrumentation-annotations ` ), omit the
351+ ` groupId ` and use just the artifact name in role comments:
352+
353+ ``` java
354+ // added in opentelemetry-api 1.38.0
355+ // artifact presence gate
356+ // added in opentelemetry-instrumentation-annotations 1.16.0
357+ ```
358+
359+ The shorthand does ** not** apply to non-OpenTelemetry artifacts — those always use the
360+ full coordinate.
361+
205362#### Rules for ` classLoaderMatcher() `
206363
207364- ** Do NOT add ` classLoaderMatcher() ` for optimization.** The "skip when library is absent"
@@ -218,11 +375,24 @@ public ElementMatcher.Junction<ClassLoader> classLoaderMatcher() {
218375 comment above the statement or expression. When matchers are chained, place each version
219376 comment directly above its class name string.
220377 - Positive floor class → ` // added in X.Y ` , optionally with brief, non-duplicative context
221- such as ` renamed from javax ` or ` backported to 2.12.3 ` .
378+ such as ` renamed from javax ` or ` backported to 2.12.3 ` . For a landmark class from a
379+ different artifact, use ` // added in groupId:artifactId X.Y ` when the boundary is for that
380+ artifact, or ` // added in X.Y (via groupId:artifactId A.B) ` when the boundary or presence
381+ condition is for the main library module. For ` javax.servlet:javax.servlet-api ` and
382+ ` jakarta.servlet:jakarta.servlet-api ` , use ` Servlet ` instead of the full Maven coordinate.
222383 - Positive ceiling class → ` // removed in Y.Z ` , optionally with brief, non-duplicative
223- context such as ` replaced by jakarta variant ` or ` moved to internal.async ` .
384+ context such as ` replaced by jakarta variant ` or ` moved to internal.async ` . For a landmark
385+ class from a different artifact, use ` // removed in groupId:artifactId Y.Z ` when the
386+ boundary is for that artifact, or ` // removed in Y.Z (via groupId:artifactId A.B) ` when the
387+ boundary is for the main library module. For ` javax.servlet:javax.servlet-api ` and
388+ ` jakarta.servlet:jakarta.servlet-api ` , use ` Servlet ` instead of the full Maven coordinate.
224389 - Negated exclusion class → ` // added in Y.Z ` , optionally with brief, non-duplicative
225- context such as ` native OTel support ` or ` shaded into core module ` .
390+ context such as ` native OTel support ` or ` shaded into core module ` . For a landmark class
391+ from a different artifact, use ` // added in groupId:artifactId Y.Z ` when the excluded range
392+ is driven by that artifact, or ` // added in Y.Z (via groupId:artifactId A.B) ` when the
393+ excluded range is described in terms of the main library module. For
394+ ` javax.servlet:javax.servlet-api ` and ` jakarta.servlet:jakarta.servlet-api ` , use ` Servlet `
395+ instead of the full Maven coordinate.
226396 - Single positive class serving as both floor and ceiling → include both boundaries, for
227397 example ` // added in X.Y, removed in Y.Z ` .
228398 Do not use ` // added in ` for a ** positive** ceiling class, because the upper bound depends on
0 commit comments