Camunda

Product

Migrating from Camunda 7 to Camunda 8: What Support Does AI Bring?

AI can make Camunda 7-to-8 migrations faster and less costly—but cleaner output doesn’t guarantee correctness. In a four-run comparison on one sample application, a fully agentic Sonnet run used 40% fewer input tokens and finished in about half the time of the converter-and-OpenRewrite path, yet introduced two semantically significant BPMN errors. The takeaway: AI can accelerate migration, but teams still need to validate process behavior against deterministic tooling.

By Jens Brauckhoff

Some weeks ago Lana and Peter wrote a blog post explaining how to migrate a Camunda 7 solution to Camunda 8 using agentic AI with the Camunda migration skill to support the migration. This blog post made me curious and I wanted to check what this AI skill is able to achieve. I especially wanted to compare how good the skill is to not only operate the diagram converter and the OpenRewrite recipes but to perform a truly agentic migration using AI to migrate the BPMN models and the Java code. I wanted to compare the results of the systematic vs. the agentic migration and even compare the results of agentic migrations with different AI models.

The skill: AI as an operating aid

The tool is the skill camunda-migration:migrate-c7-to-c8-code, officially provided by Camunda, here in version 0.1.0 from the GitHub repo camunda/camunda-7-to-8-migration-tooling. It's installed in Claude Code and pointed at the project directory of the C7 solution.

What happens next is initially unspectacular, and yet it's the part that saves the most time. The skill takes inventory of the project: it goes through the Java classes, classifies them into glue code, client code, and configuration, reads the BPMN files, collects the C7 APIs in use, and estimates the complexity of each file. For the example solution, this produces an inventory that captures the essence in a few lines:

CalculateDoWait.java (JavaDelegate + Client Code, High). Implements JavaDelegate and injects RuntimeService to run createProcessInstanceQuery()...list().size(). In C8, this becomes a process instance search via the CamundaClient against the orchestration cluster, and that search is eventually consistent.

Next, the skill fetches the tools itself: it downloads the matching version of the Diagram Converter CLI, checks the Java version, wires the OpenRewrite plugin into the Maven build if needed, runs the conversions, reads in the result lists, and works through the findings. At the end, it writes a MIGRATION_REPORT.md with the inventory, a decision log, a table of all changes, and a list of open items.

Up to this point, the AI is purely an operating aid for tools that already exist. It’s a difference that's noticeable in practice, but not yet the interesting part.

Two paths, four runs

The interesting part is that the skill offers two paths for each of the two major work areas:

AreaDeterministic + AI cleanupPurely agentic

Models (BPMN/DMN)

Diagram Converter CLI, then AI cleanup

the AI rewrites the XML directly

Java code

OpenRewrite recipes, then AI cleanup

the AI migrates everything itself

To find out what this choice means in practice, we migrated an example solution four times, each starting from exactly the same C7 state:

RunModelsCodeAI Model

1

Converter CLI

OpenRewrite + AI cleanup

Sonnet 5

2

Converter CLI

AI only

Sonnet 5

3

agentic

AI only

Sonnet 5

4

agentic

AI only

Opus 5

This setup answers two separate questions. Runs 1 through 3 all ran on the same model and vary only the approach – that's the method comparison, and since tokens translate directly into money, it's also a cost comparison. Run 4 is configured identically to run 3 and differs only in the AI model used – that's the model comparison.

First question: what each path costs

Cheaper isn’t always better, so here’s what you get for the money.

With code, AI clearly wins

Let's start where the difference is most obvious. The SampleJavaDelegate once after OpenRewrite followed by AI cleanup (run 1), and once purely agentic (run 2):

// Run 1 – output of the OpenRewrite recipes, untouched by the AI cleanup
@JobWorker(type = "sampleJavaDelegate", autoComplete = true)
public Map<String, Object> executeJobMigrated(ActivatedJob job) throws Exception {
  Map<String, Object> resultMap = new HashMap<>();
  Object x = job.getVariable("x");
  System.out.println("SampleJavaDelegate " + x);
  resultMap.put("y", "hello world");
  return resultMap;
}

// Run 2 – purely agentic
@JobWorker(type = "sampleJavaDelegate")
public Map<String, Object> sampleJavaDelegate(@Variable Object x) {
  System.out.println("SampleJavaDelegate " + x);
  return Map.of("y", "hello world");
}

Both work. But executeJobMigrated is a tool artifact that describes nothing; accessing job.getVariable("x") bypasses the variable injection that the client brings along in C8; the HashMap construct is unnecessary. None of it is wrong, it's just code nobody would write this way. And anyone reading it in two years first has to figure out that a tool was at work here. What's notable here is less the recipe output itself – looking generic is its good right – than the fact that the promised AI cleanup didn't touch these spots.

The picture is mixed with models

For the process models, the comparison initially looks the same. The agentically generated models are noticeably tidier:

Converter, runs 1 and 2agentic, runs 3 and 4

Lines / bytes

185 / 11,812

164 / ~8,860

conversion:message notices in the model

8

0

zeebe:header with the old C7 expressions

4

0

xmlns:camunda, expressionLanguage="XPath"

remain

removed

Extra files in src/main/resources

analysis-results.csv / .md

none

The conversion:message elements are the converter's review notices. They don't interfere with execution, but they do end up in the deployed model and in the repository – and here too, the AI cleanup in run 1 didn't remove them. Likewise, the analysis-results files stay in the resource directory and end up in the JAR.

Except: cleanliness isn't the most important criterion for a process model. More on that shortly.

And what about the tokens?

The following numbers don't come from the context display, but from the session transcripts. Summed across all responses in a session, this gives the actual consumption:

RunModelResponsesTool callsInput tokensOutput tokensDuration

1 – deterministic

Sonnet 5

81

96

10.08M

50,800

29 min

2 – BPMN converter / Java agentic

Sonnet 5

70

84

7.87M

44,600

25 min

3 – purely agentic

Sonnet 5

54

70

6.02M

46,400

15 min

4 – purely agentic

Opus 5

52

61

5.69M

61,700

26 min

With the same model, the fully agentic path needs 40% fewer input tokens than the doubly deterministic one and it finishes in half the time. The in-between step of deterministic models and agentic code falls neatly in the middle.

The reason is hidden in the “Responses” column. Every response resends the entire context accumulated so far, so input consumption doesn't grow linearly with the number of steps, but roughly quadratically. Run 1 needed 81 responses instead of 54, because the outputs from the converter and the recipes first had to be read in, then interpreted, then reworked – and each of these outputs then stays in the context for the rest of the session. The agentic path reads the source files once and writes the result directly.

So after the first three runs, a fairly clear thesis emerges: purely agentic is cheaper, faster, and delivers cleaner results.

Second question: is the result correct?

The thesis holds for the code. Not for the models.

For model conversion, the Diagram Converter is the benchmark, because it implements the official rule set. And at two semantically critical points, the agentic run 3 deviates from it:

LocationConverterRun 3 - SonnetRun 4 - Opus

Conditional flow at the gateway

=decision.wait

decision.wait (missing the =)

=decision.wait

Call activity

propagateAllChildVariables="false"

attribute missing

set

Both are real bugs, not style issues.

Zeebe only evaluates an expression as FEEL if it starts with an =. Without the equals sign, decision.wait is a static string, not a boolean – the condition at the gateway is never evaluated correctly. The language="feel" attribute that run 3 leaves in place instead is a C7 construct, and Zeebe simply ignores it. The model deploys cleanly, and the process still runs incorrectly.

The second point is even quieter. In Camunda 8, a call activity propagates all variables of the subprocess back into the parent process by default. The C7 original had no camunda:out, so it returned nothing. Without the explicit propagateAllChildVariables="false", this behavior changes – no error message, no warning, just different variables showing up in the parent process.

Run 4 makes the same decision as the deterministic converter at both points. And with that, the actual finding of this test series is stated: the quality of the agentic path depends on the model; the quality of the deterministic path doesn't.

The job worker for the Spring bean

The difference continues in the code. The example solution contains a JUEL expression that calls a Spring bean (#{sampleBean.someMethod(y)}). FEEL can't do that, so it needs a job worker. All four runs recognize this – but only one implements it cleanly.

Runs 1 through 3 attach the @JobWorker annotation directly to the SampleBean class. That gives a purely domain class a Camunda dependency. Run 4 instead creates its own, thin adapter and leaves SampleBean unchanged:

@Component
public class SampleBeanWorker {
  @Autowired private SampleBean sampleBean;

  @JobWorker(type = "sampleBean")
  public Map<String, Object> someMethod(@Variable(name = "y") String y) {
    return Map.of("theAnswer", sampleBean.someMethod(y));
  }
}

That's exactly the clean-delegate principle explained in the Camunda migration documentation, applied here in the reverse direction: the engine-specific layer stays thin, the domain logic stays free of Camunda types. A model that draws this connection on its own has understood more than just the syntax.

One caveat remains: run 4 renames the job type from sampleBeanSomeMethod to sampleBean in the process. Model and code match each other, but the name deviates from what the official converter would assign – and thus from what a team would expect if it had migrated parts of the same solution with the converter.

The instance counter

The second critical spot in the code is the class that uses runtimeService.createProcessInstanceQuery() to count running instances and derive an even-odd decision from it. All four runs recognize that this query only returns running instances in C7, and add the .state(ProcessInstanceState.ACTIVE) filter in C8. When it comes to reading out the result, they diverge:

// Runs 1 through 3
.items().size()

// Run 4
.page().totalItems()

items() only returns the hits from the current page, and the default limit for a search request from the Java client is 100. The example solution starts exactly 100 instances. So the counter runs exactly into this limit, and the decision flips as soon as more instances are running than fit on one page. page().totalItems(), by contrast, returns the total number of hits.

That's the kind of bug that no compiler catches, that turns no test red, and that never shows up in a development environment with five instances.

Run 4 is also the only one to document that this search runs against the secondary storage in C8 and is therefore only eventually consistent. Instances that were just started might be missing. That can't be fixed during migration, it's a design question. But it's exactly the kind of note that's needed during review.

There are also smaller differences: run 4 marks the variable x in SampleJavaDelegate as optional = true, and documents every class with a Javadoc comment. Run 3, by contrast, includes converted-c8-*.dmn in the deployment list, even though the project doesn't have a single DMN file.

And the consumption?

The obvious worry, that a stronger model drives consumption up, doesn't hold up. Run 4 uses the fewest input tokens of all four, at 5.69M. It needs fewer, but longer and more thorough steps, visible in only 52 responses combined with the highest output volume. In exchange, it takes noticeably longer at 26 minutes, compared to run 3's 15 minutes.

Still, that doesn't translate directly into a cost comparison, because Opus tokens cost more than Sonnet tokens. Within runs 1 through 3, the token counts translate directly into cost; across the boundary to run 4, they're only a measure of the efficiency of the approach.

What all four got right

With so much differentiation, it’s easy to lose sight of how much worked the same way across all four runs. Those shared parts matter most for the tasks that cost a whole afternoon when done by hand.

All four recognized that Camunda 8 no longer has implicit classpath deployment and needs an explicit @Deployment that specifically includes only the converted models. All four saw that this would put the process starter into a race with the deployment, and switched it from ApplicationRunner to @EventListener(CamundaPostDeploymentEvent.class).

And all four stumbled on the same dependency conflict and resolved it: Spring Boot 3.5.16's BOM manages httpclient5 at a version that doesn't match the Camunda 8.9 client, so without an override, creating the CamundaClient bean fails at runtime with a NoSuchMethodError. Found, verified via mvn dependency:tree, documented.

That's the part that works regardless of the chosen approach or the model used – and at the same time, the part that eats up the most thankless time in a manual migration.

Putting it in perspective

The agentic path wins on cost and code quality, but whether that pays off depends on project size and how much review your team can staff.

One number for scale: the complete migration of this solution (inventory, model conversion, code, configuration, validation, and report) took between 15 and 29 minutes. No manual reference run was performed, so this isn't a measured time saving. But anyone who has ever moved a Spring Boot application from Camunda 7 to 8 can gauge the order of magnitude themselves: the dependency conflict above alone costs half a day without prior knowledge.

For weighing the approaches against each other, everything’s now on the table. For code, the purely agentic path is clearly superior in this test series: more idiomatic, cleaner, cheaper, faster. For models, the agentic output is cleaner, the deterministic one more reliable. Anyone who can’t or doesn’t want to work with the stronger model is safer with the converter, and pays for it with rework.

The same goes for weighing the models against each other. The workflow is the same, and the run uses even fewer input tokens, but the token price is higher. On the credit side are two avoided modeling errors, a cleanly separated job worker, and a correctly read-out instance counter.

Whether that pays off depends on the project. With two BPMN files and five classes, the difference is fixed in an hour of review, provided someone in the review knows that a FEEL expression needs an =. With a solution of thirty processes and two hundred classes, the same math looks different, in both directions: the review effort grows, but so does the probability that one of these silent deviations slips through. We deliberately don’t make this decision for anyone here. The metrics and the concrete findings are on the table; the trade-off belongs to each individual project.

One last point that holds regardless of approach and model: review is never optional. No run in this test series answered the genuinely hard questions: whether decisions still makes sense as business logic under eventual consistency, what happens to transaction boundaries, whether a historyTimeToLive of 180 days was relevant to the business. The runs correctly asked these questions and recorded them in the report. They have to be answered by people who understand the domain.

Key takeaways

The migrated process runs on Zeebe, the process automation engine behind Camunda 8. Zeebe kept the migrated processes running through the test runs, so the question was never whether the engine could run them, only whether the models told it the right thing. The conversion does the mechanical work. Review decides whether the migrated semantics are still the ones you intended.

  • The skill camunda-migration:migrate-c7-to-c8-code reliably takes care of the mechanical part: inventory, tool acquisition, conversion, dependency conflicts, report. This worked in all four runs.
  • For code, purely agentic migration delivers better results than OpenRewrite followed by AI cleanup and needs 40% fewer input tokens at half the runtime with the same model.
  • For models, the deterministic Diagram Converter is the more reliable path, unless the stronger model is used. The two deviations in run 3 would have been silent bugs in production.
  • The quality of the agentic path depends on the model; the quality of the deterministic path doesn't. This asymmetry is the central finding of the test series.
  • That an agentic migration reaches this level is new. Sonnet 5 and Opus 5 weren't released far apart, but Anthropic classifies them as different capability tiers, and accordingly their token costs differ by roughly a factor of two. That tier difference shows up in the result (“two silent semantic bugs” vs. “none”). Whether it's worth the price difference is something everyone has to answer for themselves.
  • Review remains mandatory. Tools and AI handle the conversion; the decision of whether the migrated semantics are still the ones intended stays with the team.

AI support in software development is currently evolving very fast. The test for this blog post showed that a newer, or more capable, model has a clear effect on the quality of the migrated code with an otherwise identical configuration. It will likely remain helpful in the future to rely on the most current models available, or even on models specifically tailored to programming in Java. In any case, it's worth comparing the results of different current AI models on a representative sample of the code to be migrated before a large-scale migration, exactly as was done here on a small scale.

This post was written by Camunda’s partner ilume Informatik AG.

Start the discussion at forum.camunda.io

Ready to get started?