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

/**
 * 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);
}

Configure process diagram generator bean

@Configuration
public class ActivitiConfig {

    @Bean
    @ConditionalOnMissingBean
    public ProcessDiagramGenerator processDiagramGenerator() {
        return new DefaultProcessDiagramGenerator();
    }
}

Utility to obtain highlighted flows

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)
    }
}

Controller for displaying the process diagram

@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(); }
            }
        }
    }
}

Testing steps

Start a new holiday process:

/holidays/start?processDefinitionId=holiday:1:...&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&mgr=10001&explain=ill&instanceId=...

View the updated diagram again.

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

javaworkflowSpring 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

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.