Skip to content

fix(jobs): trigger-now runs a job on the engine it declares (#6305) - #6625

Merged
delchev merged 1 commit into
masterfrom
fix/java-job-trigger-now
Aug 9, 2026
Merged

fix(jobs): trigger-now runs a job on the engine it declares (#6305)#6625
delchev merged 1 commit into
masterfrom
fix/java-job-trigger-now

Conversation

@delchev

@delchev delchev commented Aug 9, 2026

Copy link
Copy Markdown
Contributor

Closes #6305.

Where the issue stands

The issue asked for a decision between three directions, and option 1 (full integration) already shipped in #6375: ScheduledClassConsumer no longer keeps client-Java jobs on a private in-JVM ThreadPoolTaskScheduler — it registers a real per-tenant Job row on the platform's shared Quartz scheduler (under the synthetic RUNTIME_LOCATION_PREFIX location, so the job synchronizer does not reap it), and the jobs engine dispatches back through the JavaJobExecutor SPI at fire time. So listing, the execution log and cluster-safe firing are all in place, and JavaJobDecoratorSampleProjectIT covers it.

This PR closes the one part of that feature set which was still broken.

The defect

JobService.trigger — the manual path behind POST /services/jobs/trigger/{job}, i.e. the Jobs perspective's Trigger action and the Monitoring shell's "run now" — ran the handler through DirigibleJavascriptCodeRunner unconditionally, ignoring the job's engine:

String handler = job.getHandler();
Path handlerPath = Path.of(handler);
try (DirigibleJavascriptCodeRunner runner = new DirigibleJavascriptCodeRunner()) {
    runner.run(handlerPath);
}

A client-Java job's handler is a class name (app.jobs.CleanupJob, optionally #method), not a repository path to a JS module — so triggering one from the IDE answered 500, while the very same job ran fine on its cron. The scheduled path (JobExecutionService) had the engine branch; the manual one never got it.

The fix

The dispatch now lives in exactly one place — a small JobHandlerRunner — used by both ways a job can start. That is the actual defect: the two dispatches were written out separately and drifted, so re-uniting them is what keeps them from drifting again.

  • JobExecutionService (scheduled fire) delegates to it, unchanged in behavior, inside the same JobLog wrapping.
  • JobService.trigger delegates to it, so the manual trigger honors engine too. Parameter handling (the Configuration set/restore memento) is untouched.

JobService cannot depend on JobExecutionService directly — JobExecutionService → JobLogService → JobService would close a constructor cycle — which is the other reason the dispatch is its own tiny bean.

Deliberately not changed

  • A manual trigger still writes no JobLog entry, for a Java job exactly as for a JS one. Adding it would change existing JS behavior; if operators want manual runs in the execution log, that is its own change.
  • enabled is still not preserved across re-registration: ScheduledClassConsumer sets enabled=true whenever it registers, so an operator's Disable is undone on the next hot-reload or restart. .job artefacts behave the same way (the synchronizer rebuilds the row from the artefact), so this is platform-wide rather than client-Java-specific — happy to file it separately if you want it fixed.

Tests

  • JobHandlerRunnerTest — the Java branch reaches the executor, a missing executor fails loudly rather than silently no-op'ing, and a JS handler never touches the Java executor.
  • JavaJobDecoratorSampleProjectIT — picks a listed engine: java job and triggers it over REST. The status is the whole assertion: the Java dispatch either resolves the client bean and invokes it or throws, and the endpoint surfaces a throw as 500 — it cannot answer 200 without having run the job. Verified both ways locally against the sample project: Expected status code <200> but was <500> on the pre-fix code, green with the fix.

mvn formatter:validate, the release-profile javadoc pass and the engine-jobs unit suite are clean.

🤖 Generated with Claude Code

#6375 made client-Java `@Scheduled` / `JobHandler` jobs first-class `Job`
definitions on the shared Quartz scheduler, so they are listed, monitored
and manageable in the Jobs perspective like any `.job`. One half of that
was still missing: the Jobs perspective's Trigger action.

`JobService.trigger` — the manual path behind `POST /services/jobs/trigger`
— ran the handler through the JavaScript code runner unconditionally,
ignoring the job's `engine`. A client-Java job's handler is a class name,
not a repository path to a JS module, so triggering one answered 500 while
the same job ran fine on its cron.

The dispatch now lives in exactly one place, `JobHandlerRunner`, used by
both ways a job can start: the scheduled fire (`JobExecutionService`) and
the manual trigger. That is the actual defect — the two dispatches were
written out separately and drifted.

The engine-java guide's job bullet was also still describing the private
`ThreadPoolTaskScheduler` that #6375 replaced; it now records the shared
Quartz registration and this single dispatch point.

Verified against the client-Java job sample: `JavaJobDecoratorSampleProjectIT`
triggers a listed `engine: java` job over REST and fails on the pre-fix code
with "Expected status code <200> but was <500>", passing with the fix.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@delchev
delchev merged commit fa0d61a into master Aug 9, 2026
10 checks passed
@delchev
delchev deleted the fix/java-job-trigger-now branch August 9, 2026 08:56
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.

Client-Java scheduled jobs (@Scheduled / JobHandler) are not visible in the Jobs perspective

1 participant