Repository: kestra-io/kestra
File: webserver/src/main/java/io/kestra/webserver/controllers/api/ExecutionController.java (Java)

Code before:
            } else {
                // Without wait, always return JSON (responseContentType only applies with returnOutputs)
                return Mono.just(HttpResponse.ok(WebhookResponse.fromExecution(result, executionUrl(result))));
            }
        } catch (QueueException e) {
            log.error(e.getMessage(), e);
            return Mono.just(HttpResponse.serverError());
        }
    }

    public record WebhookResponse(String tenantId, String id, String namespace, String flowId, Integer flowRevision,
                                  ExecutionTrigger trigger, Map<String, Object> outputs, List<Label> labels,
                                  State state, URI url) {
        public static WebhookResponse fromExecution(Execution execution, URI url) {
            return new WebhookResponse(execution.getTenantId(), execution.getId(), execution.getNamespace(), execution.getFlowId(), execution.getFlowRevision(), execution.getTrigger(), execution.getOutputs(), execution.getLabels(), execution.getState(), url);
        }
    }

    /**
     * Build webhook response with optional custom content type.
     * When responseContentType is set, the response will use that content type instead of the default application/json.
     */
    private HttpResponse<?> buildWebhookResponse(Object body, String responseContentType) {
        if (responseContentType != null && responseContentType.equals(MediaType.TEXT_PLAIN)) {
            String responseBody;
            if (body instanceof String) {
                responseBody = (String) body;
            } else if (body instanceof Map<?, ?> map && map.size() == 1) {
                // Extract single value from outputs map for plain text response
                responseBody = String.valueOf(map.values().iterator().next());
            } else {
                responseBody = String.valueOf(body);
            }
            return HttpResponse.ok(responseBody).contentType(MediaType.TEXT_PLAIN_TYPE);
        }
        // Default: application/json (or no responseContentType set)
        return HttpResponse.ok(body);
    }

    @ExecuteOn(TaskExecutors.IO)
    @Post(uri = "/trigger/{namespace}/{id}", consumes = MediaType.MULTIPART_FORM_DATA)
    @Operation(tags = {"Executions"}, summary = "Trigger a new execution for a flow")
    @ApiResponse(responseCode = "409", description = "if the flow is disabled")
    @SingleResult
    @Deprecated
    public Publisher<ExecutionResponse> triggerExecution(
        @Parameter(description = "The flow namespace") @PathVariable String namespace,
        @Parameter(description = "The flow id") @Nullable @PathVariable String id,
        @RequestBody(description = "The inputs") @Nullable @Body MultipartBody inputs,
        @Parameter(description = "The labels as a list of 'key:value'") @Nullable @QueryValue @Format("MULTI") List<String> labels,
        @Parameter(description = "If the server will wait the end of the execution") @QueryValue(defaultValue = "false") Boolean wait,

Review comment (suggestion):
```suggestion
            if (body instanceof String s) {
```

Use pattern matching to avoid the cast

Code after:
            } else {
                // Without wait, always return JSON (responseContentType only applies with returnOutputs)
                return Mono.just(HttpResponse.ok(WebhookResponse.fromExecution(result, executionUrl(result))));
            }
        } catch (QueueException e) {
            log.error(e.getMessage(), e);
            return Mono.just(HttpResponse.serverError());
        }
    }

    public record WebhookResponse(String tenantId, String id, String namespace, String flowId, Integer flowRevision,
                                  ExecutionTrigger trigger, Map<String, Object> outputs, List<Label> labels,
                                  State state, URI url) {
        public static WebhookResponse fromExecution(Execution execution, URI url) {
            return new WebhookResponse(execution.getTenantId(), execution.getId(), execution.getNamespace(), execution.getFlowId(), execution.getFlowRevision(), execution.getTrigger(), execution.getOutputs(), execution.getLabels(), execution.getState(), url);
        }
    }

    /**
     * Build webhook response with optional custom content type.
     * When responseContentType is set, the response will use that content type instead of the default application/json.
     */
    private HttpResponse<?> buildWebhookResponse(Object body, String responseContentType) {
        if (responseContentType != null && responseContentType.equals(MediaType.TEXT_PLAIN)) {
            String responseBody;
            if (body instanceof String s) {
                responseBody = s;
            } else {
                try {
                    responseBody = objectMapper.writeValueAsString(body);
                } catch (Exception e) {
                    responseBody = String.valueOf(body);
                }
            }
            return HttpResponse.ok(responseBody).contentType(MediaType.TEXT_PLAIN_TYPE);
        }
        // Default: application/json (or no responseContentType set)
        return HttpResponse.ok(body);
    }

    @ExecuteOn(TaskExecutors.IO)
    @Post(uri = "/trigger/{namespace}/{id}", consumes = MediaType.MULTIPART_FORM_DATA)
    @Operation(tags = {"Executions"}, summary = "Trigger a new execution for a flow")
    @ApiResponse(responseCode = "409", description = "if the flow is disabled")
    @SingleResult
    @Deprecated
    public Publisher<ExecutionResponse> triggerExecution(
        @Parameter(description = "The flow namespace") @PathVariable String namespace,
        @Parameter(description = "The flow id") @Nullable @PathVariable String id,
        @RequestBody(description = "The inputs") @Nullable @Body MultipartBody inputs,
        @Parameter(description = "The labels as a list of 'key:value'") @Nullable @QueryValue @Format("MULTI") List<String> labels,