Skip to content

Add ability to pass options to javaagents (#50)#379

Draft
ryandens wants to merge 1 commit into
mainfrom
worktree-javaagent-options-issue-50
Draft

Add ability to pass options to javaagents (#50)#379
ryandens wants to merge 1 commit into
mainfrom
worktree-javaagent-options-issue-50

Conversation

@ryandens

@ryandens ryandens commented Jul 7, 2026

Copy link
Copy Markdown
Owner

Closes #50.

What

Adds support for the -javaagent:<jar>=<options> form of the flag so agents like the Prometheus JMX Exporter (=12345:config.yaml) can be configured.

API

A new javaagent { } extension exposes an agentOptions map. Options are keyed by dependency coordinategroup:name for module dependencies, project path for project dependencies — so they are independent of the resolved jar file name and survive version bumps.

dependencies {
    javaagent("io.prometheus.jmx:jmx_prometheus_javaagent:0.20.0")
}

javaagent {
    agentOptions.put("io.prometheus.jmx:jmx_prometheus_javaagent", "12345:config.yaml")
}

How

  • JavaagentExtension — new agentOptions MapProperty, created once by JavaagentBasePlugin.
  • AgentOptionsResolver — resolves the coordinate-keyed options against the configuration's resolved artifacts (via ResolvedArtifactResult component identifiers) into a fileName -> options map. Lazy and configuration-cache safe; written in Java for the same task-input serialization reason as JavaForkOptionsConfigurer.
  • The map is threaded into all four -javaagent: sites: application run task, test task, application distribution start scripts, and jib container entrypoints. Agents without a matching entry get no suffix (existing behavior preserved).

Design decisions

Both settled up front (see the issue discussion):

  • Key by coordinate rather than a single global value or by resolved jar name — robust for multiple agents, not brittle to version bumps.
  • Single shared value across contexts for this first version; per-context overrides (tasks.run { javaagent { ... } }, etc.) are a deliberate follow-up.

Known limitation

Options containing spaces work for the run/test tasks (list-based JvmArgumentProvider) but may hit quoting issues in the single-token distribution start-script opt.

Tests

  • New functional tests for run-task options, multi-agent (options applied only to the keyed agent), and distribution start-script rendering (Unix + Windows).
  • New jib functional test asserting the container entrypoint carries =<options>.
  • simple-agent now echoes its agentArgs so options are observable end-to-end.
  • Existing no-options tests unchanged and green; plugin + jib functional/unit suites and spotlessCheck pass.

🤖 Generated with Claude Code

https://claude.ai/code/session_01FBTxQkVXBjJoBSfKPWyY3K

Note

Add options support to javaagent configuration via coordinate-keyed agentOptions map

  • Adds a new JavaagentExtension DSL with an agentOptions map property, where keys are dependency coordinates (e.g. group:name) and values are option strings appended as =<options> to -javaagent flags.
  • A new AgentOptionsResolver utility resolves coordinate keys to file-name-keyed maps in a configuration-cache-safe way.
  • Options are propagated to all plugin surfaces: JavaExec/Test tasks via JavaForkOptionsConfigurer, application run task via JavaagentApplicationRunPlugin, distribution start scripts via JavaagentAwareStartScriptGenerator, and Jib container entrypoints via JavaagentJibPlugin.
  • Agents without configured options are unaffected.
📊 Macroscope summarized 11e0ae1. 13 files reviewed, 0 issues evaluated, 0 issues filtered, 0 comments posted

🗂️ Filtered Issues

No issues evaluated.

Support the `-javaagent:<jar>=<options>` form of the flag by introducing a
`javaagent { }` extension with an `agentOptions` map keyed by dependency
coordinate (`group:name` for module dependencies, project path for project
dependencies). Keying by coordinate keeps options independent of the resolved
jar file name so they survive version bumps.

A shared `AgentOptionsResolver` resolves the coordinate-keyed options against
the configuration's resolved artifacts into a `fileName -> options` map, which
is threaded into all four contexts that build a `-javaagent:` argument: the
application `run` task, the `test` task, application distribution start
scripts, and jib container entrypoints. Agents without a matching entry are
attached with no suffix, preserving existing behavior.

Per the design discussion on the issue, options are a single project-level
value shared across contexts; per-context overrides are left as a follow-up.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FBTxQkVXBjJoBSfKPWyY3K
}

val localAgentPaths = extraConfig.get().javaagentFiles.get()
val agentOptions = extraConfig.get().agentOptions.get()

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟠 High jib/JavaagentJibExtension.kt:49

extendContainerBuildPlan() throws IllegalStateException whenever agentOptions is unset. For Jib integrations like tel.schich.tinyjib where agentOptions is never configured, this breaks previously working builds even when no agent options are needed. The call to extraConfig.get().agentOptions.get() on line 49 unconditionally finalizes the MapProperty, but no default is provided, so any consumer that doesn't explicitly set agentOptions fails. Consider calling .getOrElse(emptyMap()) instead of .get() so unset options default to an empty map.

Suggested change
val agentOptions = extraConfig.get().agentOptions.get()
val agentOptions = extraConfig.get().agentOptions.getOrElse(emptyMap())
🚀 Reply "fix it for me" or copy this AI Prompt for your agent:
In file @jib-common/src/main/kotlin/com/ryandens/javaagent/jib/JavaagentJibExtension.kt around line 49:

`extendContainerBuildPlan()` throws `IllegalStateException` whenever `agentOptions` is unset. For Jib integrations like `tel.schich.tinyjib` where `agentOptions` is never configured, this breaks previously working builds even when no agent options are needed. The call to `extraConfig.get().agentOptions.get()` on line 49 unconditionally finalizes the `MapProperty`, but no default is provided, so any consumer that doesn't explicitly set `agentOptions` fails. Consider calling `.getOrElse(emptyMap())` instead of `.get()` so unset options default to an empty map.

final String key = coordinateKey(artifact.getId().getComponentIdentifier());
final String options = coordinates.get(key);
if (options != null) {
byFileName.put(artifact.getFile().getName(), options);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Medium javaagent/AgentOptionsResolver.java:60

optionsByFileName keys the options map by artifact.getFile().getName(), so two agents whose resolved jars share the same basename (e.g. com.acme:agent:1.0 and org.other:agent:1.0, both agent-1.0.jar) collide. The second put overwrites the first, causing one agent's options to be lost and both -javaagent: arguments to receive whichever options survived. Using the file name as the join key cannot distinguish the two artifacts, so consider keying by a more stable identifier such as the coordinate or the full file path.

Also found in 1 other location(s)

plugin/src/main/kotlin/com/ryandens/javaagent/JavaagentAwareStartScriptGenerator.kt:111

JavaagentAwareStartScriptGenerator.Fake.write looks up options with options[jar.name], but the resolver map is keyed only by the jar file name. If two configured agents resolve to different artifacts that share the same basename (for example two project agents both published as agent.jar), one entry overwrites the other and the generated start script applies the wrong =&lt;options&gt; suffix or drops one agent's options entirely.

🚀 Reply "fix it for me" or copy this AI Prompt for your agent:
In file @plugin/src/main/java/com/ryandens/javaagent/AgentOptionsResolver.java around line 60:

`optionsByFileName` keys the options map by `artifact.getFile().getName()`, so two agents whose resolved jars share the same basename (e.g. `com.acme:agent:1.0` and `org.other:agent:1.0`, both `agent-1.0.jar`) collide. The second `put` overwrites the first, causing one agent's options to be lost and both `-javaagent:` arguments to receive whichever options survived. Using the file name as the join key cannot distinguish the two artifacts, so consider keying by a more stable identifier such as the coordinate or the full file path.

Also found in 1 other location(s):
- plugin/src/main/kotlin/com/ryandens/javaagent/JavaagentAwareStartScriptGenerator.kt:111 -- `JavaagentAwareStartScriptGenerator.Fake.write` looks up options with `options[jar.name]`, but the resolver map is keyed only by the jar file name. If two configured agents resolve to different artifacts that share the same basename (for example two project agents both published as `agent.jar`), one entry overwrites the other and the generated start script applies the wrong `=<options>` suffix or drops one agent's options entirely.

@ryandens ryandens force-pushed the worktree-javaagent-options-issue-50 branch from 11e0ae1 to 7072c0e Compare July 7, 2026 06:28
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add ability to pass arguments to a Java agent

1 participant