Backend Development 8 min read

How to Visualize Activiti Workflow Progress in Real-Time with Spring Boot

This guide shows how to extend a Spring Boot application with Activiti to query historic process data, generate highlighted process diagrams, and expose endpoints that display real‑time workflow progress, including code snippets for service methods, configuration, utilities, and a controller for image rendering.

Spring Full-Stack Practical Cases
Spring Full-Stack Practical Cases
Spring Full-Stack Practical Cases
How to Visualize Activiti Workflow Progress in Real-Time with Spring Boot

This article demonstrates how to view the current workflow progress in real time by extending a previous tutorial on integrating the Activiti workflow engine with Spring Boot.

Adding methods to HolidayService

<code>/**
 * Query historic process instance
 * @param instanceId Instance ID
 * @return HistoricProcessInstance
 */
public HistoricProcessInstance queryHistory(String instanceId) {
    return historyService.createHistoricProcessInstanceQuery()
                         .processInstanceId(instanceId)
                         .singleResult();
}

/**
 * Get historic activity instance query by instance ID
 * @param instanceId Instance ID
 * @return HistoricActivityInstanceQuery
 */
public HistoricActivityInstanceQuery getHistoryActivity(String instanceId) {
    return historyService.createHistoricActivityInstanceQuery()
                         .processInstanceId(instanceId);
}
</code>

Configure process diagram generator bean

<code>@Configuration
public class ActivitiConfig {

    @Bean
    @ConditionalOnMissingBean
    public ProcessDiagramGenerator processDiagramGenerator() {
        return new DefaultProcessDiagramGenerator();
    }
}
</code>

Utility to obtain highlighted flows

<code>public class ActivitiUtils {

    /**
     * Get highlighted flows (lines) that have been traversed
     */
    public static List<String> getHighLightedFlows(BpmnModel bpmnModel,
                                                   ProcessDefinitionEntity processDefinitionEntity,
                                                   List<HistoricActivityInstance> historicActivityInstances) {
        List<String> highFlows = new ArrayList<>();
        if (historicActivityInstances == null || historicActivityInstances.isEmpty()) {
            return highFlows;
        }
        // ... (logic omitted for brevity)
        return highFlows;
    }

    private static FlowNode getNextFlowNode(BpmnModel bpmnModel,
                                            List<HistoricActivityInstance> historicActivityInstances,
                                            int i,
                                            HistoricActivityInstance activityImpl_) {
        // ... (logic omitted for brevity)
    }
}
</code>

Controller for displaying the process diagram

<code>@RestController
@RequestMapping("/view")
public class ProcessViewController {

    private static Logger logger = LoggerFactory.getLogger(ProcessViewController.class);

    @Resource
    private HolidayService holidayService;
    @Resource
    private RepositoryService repositoryService;
    @Resource
    private ProcessDiagramGenerator processDiagramGenerator;

    @ResponseBody
    @GetMapping("/image")
    public void showImg(String instanceId, HttpServletResponse response) throws Exception {
        response.setContentType("text/html;charset=utf-8");
        if (StringUtils.isEmpty(instanceId)) {
            response.getWriter().write("error");
            return;
        }
        HistoricProcessInstance processInstance = holidayService.queryHistory(instanceId);
        if (processInstance == null) {
            logger.error("Process instance ID:{} not found!", instanceId);
            response.getWriter().write("error instance not exists");
            return;
        }
        BpmnModel bpmnModel = repositoryService.getBpmnModel(processInstance.getProcessDefinitionId());
        HistoricActivityInstanceQuery historyInstanceQuery = holidayService.getHistoryActivity(instanceId);
        List<HistoricActivityInstance> historicActivityInstanceList = historyInstanceQuery
                .orderByHistoricActivityInstanceStartTime().asc().list();
        if (historicActivityInstanceList == null || historicActivityInstanceList.isEmpty()) {
            logger.error("Process instance ID: {}, no historic nodes!", instanceId);
            outputImg(response, bpmnModel, null, null);
            return;
        }
        List<String> executedActivityIdList = historicActivityInstanceList.stream()
                .map(item -> item.getActivityId())
                .collect(Collectors.toList());
        ProcessDefinitionEntity processDefinition = (ProcessDefinitionEntity) ((RepositoryServiceImpl) repositoryService)
                .getDeployedProcessDefinition(processInstance.getProcessDefinitionId());
        List<String> flowIds = ActivitiUtils.getHighLightedFlows(bpmnModel, processDefinition, historicActivityInstanceList);
        outputImg(response, bpmnModel, flowIds, executedActivityIdList);
    }

    private void outputImg(HttpServletResponse response, BpmnModel bpmnModel,
                           List<String> flowIds, List<String> executedActivityIdList) {
        InputStream imageStream = null;
        try {
            imageStream = processDiagramGenerator.generateDiagram(bpmnModel,
                    executedActivityIdList, flowIds, "宋体", "微软雅黑", "黑体", true, "png");
            byte[] buffer = new byte[10 * 1024];
            int len;
            while ((len = imageStream.read(buffer)) != -1) {
                response.getOutputStream().write(buffer, 0, len);
            }
            response.getOutputStream().flush();
        } catch (Exception e) {
            logger.error("Process diagram output error!", e);
        } finally {
            if (imageStream != null) {
                try { imageStream.close(); } catch (IOException e) { e.printStackTrace(); }
            }
        }
    }
}
</code>

Testing steps

Start a new holiday process: /holidays/start?processDefinitionId=holiday:1:...&amp;userId=10000

List tasks for the user to obtain the instance ID: /holidays/tasks?userId=10000

View the current process diagram: /view/image?instanceId=...

Proceed to the next step: /holidays/apply?days=3&amp;mgr=10001&amp;explain=ill&amp;instanceId=...

View the updated diagram again.

backendJavaworkflowSpring BootActivitiProcess Diagram
Spring Full-Stack Practical Cases
Written by

Spring Full-Stack Practical Cases

Full-stack Java development with Vue 2/3 front-end suite; hands-on examples and source code analysis for Spring, Spring Boot 2/3, and Spring Cloud.

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.