How to run Activiti BPMN with Spring Boot

This tutorial introduces you to Activiti BPMN and how to run it in a Spring Boot environment. We will demonstrate how to run a simple Process which includes user Tasks in it from a Spring Boot application..

Activiti is an open-source workflow engine written in Java that you can use to execute BPMN 2.0 compliant business processes. In this article we will mainly cover how run Activiti in a Spring Boot runtime.

Setting up the Project

Firstly, we will set up the project with Spring Boot initializr. In our example, we will use Spring Boot 2.6.11 API. Here is the list of dependencies we will need:

<?xml version="1.0" encoding="UTF-8"?>

	<name>Activiti :: Examples :: API Basic Process and Tasks Example No Bean</name>

As you can see, we are using Activiti version 7.4.1 through the activiti-spring-boot-starter. We also need to include in the repositories section the URL where Activiti releases are available.

In the next section, we will learn how to design the BPMN process using an online Process designer.

Designing the BPMN Process

In order to design the BPMN 2.0 process there are multiple options. You can try the an online Process designer

On the other hand, if you want to learn how to install a Business Process Designer as Eclipse plugin, then check this Activiti tutorial.

In our sample process, there’s an Human Task “Process Content” where the Task owner will choose if a Content is appropriate or not:

The decision will be taken from our example application as soon as the Task owner claims the Task.

Coding the Spring Boot application

The application logic is contained in the following @SpringBootApplication Class:

public class DemoApplication implements CommandLineRunner {

    private Logger logger = LoggerFactory.getLogger(DemoApplication.class);

    private final ProcessRuntime processRuntime;

    private final TaskRuntime taskRuntime;

    private final SecurityUtil securityUtil;

    private final ObjectMapper objectMapper;

    public DemoApplication(ProcessRuntime processRuntime,
                           TaskRuntime taskRuntime,
                           SecurityUtil securityUtil,
                           ObjectMapper objectMapper) {
        this.processRuntime = processRuntime;
        this.taskRuntime = taskRuntime;
        this.securityUtil = securityUtil;
        this.objectMapper = objectMapper;

    public static void main(String[] args) {, args);


    public void run(String... args) {

        Page<ProcessDefinition> processDefinitionPage = processRuntime.processDefinitions(Pageable.of(0, 10));"> Available Process definitions: " + processDefinitionPage.getTotalItems());
        for (ProcessDefinition pd : processDefinitionPage.getContent()) {
  "\t > Process definition: " + pd);


    @Scheduled(initialDelay = 1000, fixedDelay = 5000)
    public void processText() {


        LinkedHashMap content = pickRandomString();

        SimpleDateFormat formatter = new SimpleDateFormat("dd-MM-yy HH:mm:ss");"> Starting process to process content: " + content + " at " + formatter.format(new Date()));

        ProcessInstance processInstance = processRuntime.start(ProcessPayloadBuilder
                .withName("Processing Content: " + content)
                .withVariable("content", objectMapper.convertValue(content, JsonNode.class))
                .build());">>> Created Process Instance: " + processInstance);


    @Scheduled(initialDelay = 1000, fixedDelay = 5000)
    public void checkAndWorkOnTasksWhenAvailable() {

        Page<Task> tasks = taskRuntime.tasks(Pageable.of(0, 10));
        if (tasks.getTotalItems() > 0) {
            for (Task t : tasks.getContent()) {

      "> Claiming task: " + t.getId());

                List<VariableInstance> variables = taskRuntime.variables(TaskPayloadBuilder.variables().withTaskId(t.getId()).build());
                VariableInstance variableInstance = variables.get(0);
                if (variableInstance.getName().equals("content")) {
                    LinkedHashMap contentToProcess = objectMapper.convertValue(variableInstance.getValue(), LinkedHashMap.class);
          "> Content received inside the task to approve: " + contentToProcess);

                    if (contentToProcess.get("body").toString().contains("activiti")) {
              "> User Approving content");
                    } else {
              "> User Discarding content");
                            .withTaskId(t.getId()).withVariable("content", contentToProcess).build());


        } else {
  "> There are no task for me to work on.");


    public Connector tagTextConnector() {
        return integrationContext -> {
            LinkedHashMap contentToTag = (LinkedHashMap) integrationContext.getInBoundVariables().get("content");
            contentToTag.put("tags", singletonList(" :) "));
            integrationContext.addOutBoundVariable("content", contentToTag);
  "Final Content: " + contentToTag);
            return integrationContext;

    public Connector discardTextConnector() {
        return integrationContext -> {
            LinkedHashMap contentToDiscard = (LinkedHashMap) integrationContext.getInBoundVariables().get("content");
            contentToDiscard.put("tags", singletonList(" :( "));
            integrationContext.addOutBoundVariable("content", contentToDiscard);
  "Final Content: " + contentToDiscard);
            return integrationContext;

    private LinkedHashMap pickRandomString() {
        String[] texts = {"hello from london", "Hi there from activiti!", "all good news over here.", "I've tweeted about activiti today.",
                "other boring projects.", "activiti cloud - Cloud Native Java BPM"};
        LinkedHashMap<Object,Object> content = new LinkedHashMap<>();
        content.put("body",texts[new Random().nextInt(texts.length)]);
        return content;


As you can see, within this application there are two @Scheduled activities:

The method processText will create a new Process Instance with some random content for the process variable “content”

The method checkAndWorkOnTasksWhenAvailable will claim the active Tasks and complete them, based on the value of the “content” process variable.

We also need a Configuration Bean which contains the list of Users and Roles you can use to access the Spring Boot application:

public class DemoApplicationConfiguration {

    private Logger logger = LoggerFactory.getLogger(DemoApplicationConfiguration.class);

    public UserDetailsService myUserDetailsService() {

        InMemoryUserDetailsManager inMemoryUserDetailsManager = new InMemoryUserDetailsManager();

        String[][] usersGroupsAndRoles = {
                {"bob", "password", "ROLE_ACTIVITI_USER", "GROUP_activitiTeam"},
                {"john", "password", "ROLE_ACTIVITI_USER", "GROUP_activitiTeam"},
                {"hannah", "password", "ROLE_ACTIVITI_USER", "GROUP_activitiTeam"},
                {"other", "password", "ROLE_ACTIVITI_USER", "GROUP_otherTeam"},
                {"system", "password", "ROLE_ACTIVITI_USER"},
                {"admin", "password", "ROLE_ACTIVITI_ADMIN"},

        for (String[] user : usersGroupsAndRoles) {
            List<String> authoritiesStrings = asList(Arrays.copyOfRange(user, 2, user.length));
  "> Registering new user: " + user[0] + " with the following Authorities[" + authoritiesStrings + "]");
            inMemoryUserDetailsManager.createUser(new User(user[0], passwordEncoder().encode(user[1]),
           -> new SimpleGrantedAuthority(s)).collect(Collectors.toList())));

        return inMemoryUserDetailsManager;

    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();


Finally, to set the SecurityContext in the Activiti Engine, we will add a Component Class, which contains a SecurityContextImpl:

public class SecurityUtil {

    private Logger logger = LoggerFactory.getLogger(SecurityUtil.class);

    private UserDetailsService userDetailsService;

    public void logInAs(String username) {

        UserDetails user = userDetailsService.loadUserByUsername(username);
        if (user == null) {
            throw new IllegalStateException("User " + username + " doesn't exist, please provide a valid user");
        }"> Logged in as: " + username);
        SecurityContextHolder.setContext(new SecurityContextImpl(new Authentication() {
            public Collection<? extends GrantedAuthority> getAuthorities() {
                return user.getAuthorities();

            public Object getCredentials() {
                return user.getPassword();

            public Object getDetails() {
                return user;

            public Object getPrincipal() {
                return user;

            public boolean isAuthenticated() {
                return true;

            public void setAuthenticated(boolean isAuthenticated) throws IllegalArgumentException {


            public String getName() {
                return user.getUsername();

You can run the application as follows:

mvn install spring-boot:run

You will observe that every 5 seconds a new Process Instance starts and available Tasks are claimed accordingly:

spring boot activiti tutorial

The Spring Boot project is available under the Activiti examples: