@@ -193,47 +193,49 @@ public void startAndBlock() {
193193 ? startedEvent .getParentTraceContext () : null ;
194194 String orchName = startedEvent != null ? startedEvent .getName () : "" ;
195195
196- // Start the orchestration span BEFORE execution so child spans
197- // (activities, timers) are nested under it in the trace hierarchy.
198- // Each dispatch creates its own orchestration span (matching JS/dotnet behavior).
199- Span orchestrationSpan = null ;
200- TraceContext orchestrationSpanContext = null ;
201- if (orchTraceCtx != null ) {
196+ // Pass parentTraceContext to executor so child spans
197+ // (activities, timers) reference the correct trace.
198+ TaskOrchestratorResult taskOrchestratorResult ;
199+ try {
200+ taskOrchestratorResult = taskOrchestrationExecutor .execute (
201+ orchestratorRequest .getPastEventsList (),
202+ orchestratorRequest .getNewEventsList (),
203+ orchTraceCtx );
204+ } catch (Throwable e ) {
205+ if (e instanceof Error ) {
206+ throw (Error ) e ;
207+ }
208+ throw new RuntimeException (e );
209+ }
210+
211+ // Emit a single orchestration span only on the completion dispatch.
212+ // Uses ExecutionStartedEvent timestamp as start time for full lifecycle.
213+ // Java OTel doesn't support SetSpanId() like .NET, so we emit one
214+ // span on completion rather than merging across dispatches.
215+ boolean isCompleting = taskOrchestratorResult .getActions ().stream ()
216+ .anyMatch (a -> a .getOrchestratorActionTypeCase () == OrchestratorAction .OrchestratorActionTypeCase .COMPLETEORCHESTRATION
217+ || a .getOrchestratorActionTypeCase () == OrchestratorAction .OrchestratorActionTypeCase .TERMINATEORCHESTRATION );
218+
219+ if (isCompleting && orchTraceCtx != null ) {
202220 Map <String , String > orchSpanAttrs = new HashMap <>();
203221 orchSpanAttrs .put (TracingHelper .ATTR_TYPE , TracingHelper .TYPE_ORCHESTRATION );
204222 orchSpanAttrs .put (TracingHelper .ATTR_TASK_NAME , orchName );
205223 orchSpanAttrs .put (TracingHelper .ATTR_INSTANCE_ID , orchestratorRequest .getInstanceId ());
206224
207225 Instant spanStartTime = null ;
208- if (startedHistoryEvent .hasTimestamp ()) {
226+ if (startedHistoryEvent != null && startedHistoryEvent .hasTimestamp ()) {
209227 spanStartTime = DataConverter .getInstantFromTimestamp (
210228 startedHistoryEvent .getTimestamp ());
211229 }
212230
213- orchestrationSpan = TracingHelper .startSpanWithStartTime (
231+ Span orchestrationSpan = TracingHelper .startSpanWithStartTime (
214232 TracingHelper .TYPE_ORCHESTRATION + ":" + orchName ,
215233 orchTraceCtx ,
216234 SpanKind .SERVER ,
217235 orchSpanAttrs ,
218236 spanStartTime );
219- orchestrationSpanContext = TracingHelper .getCurrentTraceContext (orchestrationSpan );
220- }
221-
222- TaskOrchestratorResult taskOrchestratorResult ;
223- try {
224- taskOrchestratorResult = taskOrchestrationExecutor .execute (
225- orchestratorRequest .getPastEventsList (),
226- orchestratorRequest .getNewEventsList (),
227- orchestrationSpanContext );
228- } catch (Throwable e ) {
229- if (e instanceof Error ) {
230- throw (Error ) e ;
231- }
232- throw new RuntimeException (e );
233- }
234237
235- // End the orchestration span, setting error status if the orchestration failed
236- if (orchestrationSpan != null ) {
238+ // Set error status if orchestration failed
237239 for (OrchestratorAction action : taskOrchestratorResult .getActions ()) {
238240 if (action .getOrchestratorActionTypeCase () == OrchestratorAction .OrchestratorActionTypeCase .COMPLETEORCHESTRATION ) {
239241 CompleteOrchestrationAction complete = action .getCompleteOrchestration ();
0 commit comments