Creating jira workflow using java APIs

Hello,

I’m trying to create jira workflows using Java APIs, and followed the following article - Create Workflow through Java API - Jira Development / Jira Server - The Atlassian Developer Community

I implemented it with help of Rest plugin module, with a post method, when called triggers the below code, in which I’ve hard coded the new steps and the transitions of the new workflow.

public Response createWorkflow(@PathParam("workflowName") String workflowName){
        WorkflowManager workflowManager = ComponentAccessor.getWorkflowManager();
        ApplicationUser user = ComponentAccessor.getJiraAuthenticationContext().getLoggedInUser();
        String workflowDescription = "A custom workflow created using Jira API";
        try{
            // Create a new workflow based on the default Jira workflow template
            JiraWorkflow baseWorkflow = workflowManager.getWorkflow("jira");

            // Create a draft workflow from the base workflow to modify
            JiraWorkflow newWorkflow = workflowManager.copyWorkflow(user, workflowName, workflowDescription, baseWorkflow);

            // Get the workflow descriptor to add steps/actions
            WorkflowDescriptor workflowDescriptor = newWorkflow.getDescriptor();

            String[] statuses = {"To Do(Test)", "Done(Test)"};
            Map<String, StepDescriptor> stepMap = new HashMap<>();
            List<StepDescriptor> steps = new ArrayList<>(workflowDescriptor.getSteps());
            StepDescriptor lastStep = steps.get(steps.size()-2);
            stepMap.put(lastStep.getName(), lastStep);
            for(String status : statuses){
                // Create a new step descriptor
                StepDescriptor newStep = DescriptorFactory.getFactory().createStepDescriptor();
                newStep.setId(WorkflowUtil.getNextId(workflowDescriptor.getSteps()));
                newStep.setName(status);
                // Add the step to the workflow descriptor
                workflowDescriptor.addStep(newStep);
                stepMap.put(status, newStep);  // Map status name to StepDescriptor
            }
            
            Map<String, String> transitions = new HashMap<>();
            transitions.put("Reopened -> To Do(Test)", "Work In Progress");
            transitions.put("To Do(Test) -> Done(Test)", "Work done");

            for (Map.Entry<String, String> transition : transitions.entrySet()) {
                String[] transitionSteps = transition.getKey().split("->");
                if (transitionSteps.length == 2) {
                    String fromStatus = transitionSteps[0].trim();
                    String toStatus = transitionSteps[1].trim();
                    String transitionName = transition.getValue();
                    // Get the "from" and "to" steps from the map
                    StepDescriptor fromStep = stepMap.get(fromStatus);
                    StepDescriptor toStep = stepMap.get(toStatus);
                    if (fromStep != null) {
                        // Create a new action (transition)
                        ActionDescriptor actionDescriptor = DescriptorFactory.getFactory().createActionDescriptor();
                        actionDescriptor.setId(fromStep.getActions().size() + 1);  // Increment action ID
                        actionDescriptor.setName(transitionName);
                        // Define the result of this action: transition to the next step
                        ResultDescriptor resultDescriptor = DescriptorFactory.getFactory().createResultDescriptor();
                        resultDescriptor.setStep(toStep.getId());
                        resultDescriptor.setOldStatus(fromStep.getName());
                        resultDescriptor.setStatus(toStep.getName());
                        // Add result to the action
                        actionDescriptor.setParent(fromStep);
                        actionDescriptor.setUnconditionalResult(resultDescriptor);
                        // Link the action to the step by adding it to the step's action list
                        fromStep.getActions().add(actionDescriptor);
                    }
                }
            }

            // Save the workflow
            workflowManager.updateWorkflow(user, newWorkflow);

            System.out.println("Workflow created: " + workflowName);
            return Response.ok("Workflow created successfully: " + newWorkflow.getName()).build();

        } catch (Exception e) {
            e.printStackTrace();
            return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
                    .entity("Error occurred while creating workflow: " + e.getMessage())
                    .build();
        }
    }

The post methods response is success without any error and also checked in DB the new workflow got created, but the workflows get disappeared from the UI, even the previous workflows, which are already present.

Then I checked the logs file in the target folder where I found a Runtime exception as shown below

2024-11-26 12:11:52,406+0530 http-nio-2990-exec-7 url: /jira/rest/workflowplugin/1.0/new-workflow/TP-Test-Workflow; user: admin ERROR admin 731x2176x1 1v63t1m 0:0:0:0:0:0:0:1 /rest/workflowplugin/1.0/new-workflow/TP-Test-Workflow [c.a.event.internal.AsynchronousAbleEventDispatcher] There was an exception thrown trying to dispatch event [com.atlassian.jira.event.WorkflowUpdatedEvent@b0f1a9ac] from the invoker [com.atlassian.event.internal.ComparableListenerInvoker@2cecdf61]
java.lang.RuntimeException: Step with id '7' does not have a valid linked status.. Listener: com.atlassian.jira.auditing.AuditingEventListener event: com.atlassian.jira.event.WorkflowUpdatedEvent
	at com.atlassian.event.internal.SingleParameterMethodListenerInvoker.invoke(SingleParameterMethodListenerInvoker.java:53)
	at com.atlassian.diagnostics.internal.platform.monitor.event.EventSystemMonitor.invokeMonitored(EventSystemMonitor.java:105)
	at com.atlassian.diagnostics.internal.platform.monitor.event.MonitoredListenerInvoker.invoke(MonitoredListenerInvoker.java:38)
	at com.atlassian.event.internal.ComparableListenerInvoker.invoke(ComparableListenerInvoker.java:48)
	at com.atlassian.event.internal.AsynchronousAbleEventDispatcher.lambda$null$0(AsynchronousAbleEventDispatcher.java:37)
	at com.atlassian.event.internal.AsynchronousAbleEventDispatcher.dispatch(AsynchronousAbleEventDispatcher.java:85)
	at com.atlassian.diagnostics.internal.platform.monitor.event.MonitoredEventDispatcher.dispatch(MonitoredEventDispatcher.java:36)
	at com.atlassian.event.internal.EventPublisherImpl.publish(EventPublisherImpl.java:114)
	at com.atlassian.event.internal.LockFreeEventPublisher.publish(LockFreeEventPublisher.java:40)
	at com.atlassian.jira.workflow.OSWorkflowManager.updateWorkflow(OSWorkflowManager.java:627)
	at com.examplecom.plugins.rest.WorkflowRestResource.createWorkflow(WorkflowRestResource.java:363)
	at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
	at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
	at java.base/java.lang.reflect.Method.invoke(Method.java:566)
	... 19 filtered
	at com.atlassian.plugins.rest.module.RestDelegatingServletFilter$JerseyOsgiServletContainer.doFilter(RestDelegatingServletFilter.java:171)
	... 1 filtered
	at com.atlassian.plugins.rest.module.RestDelegatingServletFilter.doFilter(RestDelegatingServletFilter.java:75)
	... 36 filtered
	at com.atlassian.jira.plugin.mobile.web.filter.MobileAppRequestFilter.doFilter(MobileAppRequestFilter.java:59)
	... 4 filtered
	at com.atlassian.jira.plugin.mobile.login.MobileLoginSuccessFilter.doFilter(MobileLoginSuccessFilter.java:54)
	... 3 filtered
	at com.atlassian.diagnostics.internal.platform.monitor.http.HttpRequestMonitoringFilter.doFilter(HttpRequestMonitoringFilter.java:54)
	... 8 filtered
	at com.atlassian.web.servlet.plugin.request.RedirectInterceptingFilter.doFilter(RedirectInterceptingFilter.java:21)
	... 24 filtered
	at com.atlassian.labs.httpservice.resource.ResourceFilter.doFilter(ResourceFilter.java:59)
	... 22 filtered
	at com.atlassian.oauth2.scopes.web.ReadWriteScopeFilter.doFilter(ReadWriteScopeFilter.java:46)
	... 3 filtered
	at com.atlassian.ratelimiting.internal.filter.RateLimitFilter.doFilter(RateLimitFilter.java:73)
	... 3 filtered
	at com.atlassian.troubleshooting.thready.filter.AbstractThreadNamingFilter.doFilter(AbstractThreadNamingFilter.java:46)
	... 17 filtered
	at com.atlassian.jira.security.JiraSecurityFilter.lambda$doFilter$0(JiraSecurityFilter.java:66)
	... 1 filtered
	at com.atlassian.jira.security.JiraSecurityFilter.doFilter(JiraSecurityFilter.java:64)
	... 16 filtered
	at com.atlassian.plugins.rest.module.servlet.RestSeraphFilter.doFilter(RestSeraphFilter.java:38)
	... 3 filtered
	at com.atlassian.pats.web.filter.TokenBasedAuthenticationFilter.doFilter(TokenBasedAuthenticationFilter.java:82)
	... 3 filtered
	at com.atlassian.oauth2.provider.core.web.AccessTokenFilter.doFilter(AccessTokenFilter.java:82)
	... 19 filtered
	at com.atlassian.jira.servermetrics.CorrelationIdPopulatorFilter.doFilter(CorrelationIdPopulatorFilter.java:30)
	... 5 filtered
	at com.atlassian.plugins.authentication.impl.web.filter.loginform.DisableNativeLoginAuthFilter.doFilter(DisableNativeLoginAuthFilter.java:55)
	... 3 filtered
	at com.atlassian.plugins.authentication.impl.basicauth.filter.DisableBasicAuthFilter.doFilter(DisableBasicAuthFilter.java:70)
	... 8 filtered
	at com.atlassian.ratelimiting.internal.filter.RateLimitPreAuthFilter.doFilter(RateLimitPreAuthFilter.java:71)
	... 3 filtered
	at com.atlassian.web.servlet.plugin.request.RedirectInterceptingFilter.doFilter(RedirectInterceptingFilter.java:21)
	... 4 filtered
	at com.atlassian.troubleshooting.thready.filter.AbstractThreadNamingFilter.doFilter(AbstractThreadNamingFilter.java:46)
	... 3 filtered
	at com.atlassian.web.servlet.plugin.LocationCleanerFilter.doFilter(LocationCleanerFilter.java:36)
	... 26 filtered
	at com.atlassian.jira.servermetrics.MetricsCollectorFilter.doFilter(MetricsCollectorFilter.java:25)
	... 24 filtered
	at org.apache.tomcat.util.threads.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1191)
	at org.apache.tomcat.util.threads.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:659)
	at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:63)
	at java.base/java.lang.Thread.run(Thread.java:829)
Caused by: java.lang.IllegalStateException: Step with id '7' does not have a valid linked status.
	at com.atlassian.jira.workflow.AbstractJiraWorkflow.getLinkedStatusId(AbstractJiraWorkflow.java:248)
	at com.atlassian.jira.workflow.AbstractJiraWorkflow.getLinkedStatus(AbstractJiraWorkflow.java:225)
	at java.base/java.util.stream.ReferencePipeline$3$1.accept(ReferencePipeline.java:195)
	at java.base/java.util.ArrayList$ArrayListSpliterator.forEachRemaining(ArrayList.java:1655)
	at java.base/java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:484)
	at java.base/java.util.stream.ReduceOps$ReduceOp.evaluateSequential(ReduceOps.java:913)
	at java.base/java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:234)
	at java.base/java.util.stream.ReferencePipeline.collect(ReferencePipeline.java:578)
	at com.atlassian.jira.workflow.AbstractJiraWorkflow.getLinkedStatusObjects(AbstractJiraWorkflow.java:260)
	at java.base/java.util.Optional.map(Optional.java:265)
	at com.atlassian.jira.auditing.handlers.WorkflowEventHandlerImpl.getLinkedStatuses(WorkflowEventHandlerImpl.java:120)
	at com.atlassian.jira.auditing.handlers.WorkflowEventHandlerImpl.computeChangedValues(WorkflowEventHandlerImpl.java:99)
	at com.atlassian.jira.auditing.handlers.WorkflowEventHandlerImpl.workflowUpdated(WorkflowEventHandlerImpl.java:86)
	at com.atlassian.jira.auditing.handlers.WorkflowEventHandlerImpl.onWorkflowUpdatedEvent(WorkflowEventHandlerImpl.java:51)
	at com.atlassian.jira.auditing.AuditingEventListener.onWorkflowDeletedEvent(AuditingEventListener.java:483)
	at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
	at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
	at java.base/java.lang.reflect.Method.invoke(Method.java:566)
	at com.atlassian.event.internal.SingleParameterMethodListenerInvoker.invoke(SingleParameterMethodListenerInvoker.java:42)
	... 299 more

Please feel free to point out anything that is not valid or any other additions to the code.

Please help me with creating a new workflow from scratch using JAVA APIs, if anyone has success in creating workflows using java apis.