Read your estate before you price it.

A command-line tool that parses TIBCO BusinessWorks 5 .process files, builds an intermediate model of every activity and transition, and generates Apache Camel routes from it. One jar, two commands, and neither of them reaches the network, which is why measuring an estate does not need an NDA.

What it does

  • Parses a BusinessWorks 5 project: .process files, their activities, transitions, groups and the shared resources they reference.
  • Builds an intermediate model: a typed step list per process, independent of both BusinessWorks and Camel, which is what makes the same input emit either Java DSL or YAML.
  • Generates Camel routes, one Maven module per component or per entry operation, plus the Spring beans the routes reference.
  • Emits a coverage report: for every activity type it met, whether a rule exists, whether that rule emitted a marker, and how often the type occurs. The catalog on this site and the cost calculator both read that report.
  • Marks what it cannot translate, in the code rather than in a side document. An activity with no rule becomes a TODO_HUMAN comment and a warning that names the activity and its type, so an unported step is visible in the generated source.

Getting the jar

There is no binary release yet, so build it. Java 21 is what the tool itself needs.

export JAVA_HOME=$(/usr/libexec/java_home -v 21)
mvn -q -DskipTests package

The jar lands in esbexit-bootstrap/target/esbexit-bootstrap-0.1.0-SNAPSHOT.jar. Everything below assumes it is aliased:

alias esbexit='java -jar esbexit-bootstrap/target/esbexit-bootstrap-0.1.0-SNAPSHOT.jar'

Building the generated modules needs a JDK matching platform.java.version, which defaults to 25. Nothing else: no TIBCO installation, no running engine, no network.

Migrating a component

esbexit migrate \
    --source /path/to/tibco/sources \
    --component sample-adapter \
    --out ./out/components/sample-adapter \
    --config ./my-estate.properties
OptionRequiredMeaning
--sourceyesRoot of the TIBCO source tree: the directory holding the components and the project libraries.
--componentyesComponent name. If the name is not unique in the tree, give it as group/component.
--outyesOutput directory for the generated module.
--confignoProperties file with generation settings. Defaults apply when omitted.
--granularitynocomponent, the default, or entry.
--operationwith entryThe entry operation: a process starter or a SOAP service.
--librarynoThe source is a project library rather than a component.
--tibco-versionnoBusinessWorks version. Defaults to 5.15.

It prints what it produced and exits 0. Exit 2 means a missing or contradictory option.

Generated <n> files in ./out/components/sample-adapter
Coverage: <percent>%   report: ./out/components/sample-adapter/docs/migration-report.md
Open issues: <n>

Choosing the granularity

--granularity component, the default, makes the whole TIBCO component one Maven module. This is what you want for an estate-wide run: the module map matches the component map, so people who know the old system can find their way around the new one.

--granularity entry makes one module per entry operation, pulling in everything reachable from it. Use it when a single component is too large to deploy as a unit, or when one operation has to move to production before the rest. It needs --operation.

--library is for project libraries, the shared .projlib sources. Their routes are published under global addresses derived from their paths, so components that call into them resolve. Generate the libraries before the components that depend on them.

What you get

sample-adapter/
  pom.xml
  src/main/java/<package.prefix>/sample_adapter/...   routes, mappers, endpoint config
  src/main/resources/
    application.yml
    mappers/*.xslt
    tibco/...                                          schemas and WSDLs, as in the source
  docs/migration-report.md
  docs/operation-summary.md

migration-report.md is the one to read. It carries step coverage, field-mapping coverage and every open issue with its location. An activity the generator could not translate is a TODO_HUMAN marker in the route and a line in that report. It is never silently dropped or approximated.

A whole estate

Once the modules exist, scaffold writes the parent pom at the root and rebuilds the module list from what is actually on disk. It expects components/ and shared/projlibs/ under --root, which is where migrate puts them. Run it again after adding modules; it rewrites the module list rather than appending to it.

# libraries first, so the components that call into them resolve
for lib in $(ls /path/to/tibco/sources/projlibs); do
  esbexit migrate --source /path/to/tibco/sources/projlibs --component "$lib" \
      --out "./out/shared/projlibs/$lib" --config ./my-estate.properties --library
done

for comp in $(cd /path/to/tibco/sources/components && find . -mindepth 2 -maxdepth 2 -type d | sed 's|^\./||'); do
  esbexit migrate --source /path/to/tibco/sources --component "$comp" \
      --out "./out/components/$(echo "$comp" | tr '/ ' '__')" --config ./my-estate.properties
done

esbexit scaffold --root ./out --config ./my-estate.properties
cd out && mvn -q compile

Each migrate is independent, so this parallelises with xargs -P without any coordination.

The config file

Everything that varies per estate lives in one properties file. Give only the keys you override; the rest keep their defaults. A misspelled key stops the run with a list of unknown keys, because a silently ignored setting would hand you a module that looks right and is built on the wrong coordinates.

package.prefix=org.acme.integration
maven.group-id=org.acme.integration
maven.parent.artifact-id=acme-integration-parent
KeyDefaultSets
package.prefixcom.example.camelJava package prefix in the generated module, and the logging.level entry in application.yml.
maven.group-idcom.example.camelgroupId of the parent and of dependencies between generated modules.
maven.parent.artifact-idintegration-parentartifactId of the parent pom.
platform.spring-boot.version4.1.0Spring Boot version in a module with no parent from your estate.
platform.camel.version4.20.0Camel version in the same case.
platform.java.version25maven.compiler.release of the generated module.
layout.components-dircomponentsWhere components sit inside --source. If the directory is absent, components are taken to sit in the source root.
layout.libraries-dirprojlibsWhere project libraries sit. Relative to --source, or an absolute path when they live outside the component tree.
jms.client.dependencyspring-boot-starter-artemisJMS client added alongside camel-jms, which brings no ConnectionFactory of its own. Empty disables it.
route.dsljavajava or yaml. The YAML renderer is experimental.

Those are the ones worth knowing before the first run. Maven coordinates, parent paths, snapshot repositories and the diagnostics dependencies have defaults that work, and the full list ships with the tool.

package.prefix and maven.group-id default to com.example.camel on purpose: example is the namespace IANA reserves for placeholders, so no real project can collide with it. Set your own before generating anything you intend to keep. Values may reference other keys, and substitution is recursive and detects cycles.

Where your sources sit

A source tree holding components/ and projlibs/ is one estate's convention rather than a rule, so both names are settings. If the libraries live somewhere else entirely, give an absolute path.

layout.components-dir=apps
layout.libraries-dir=/srv/tibco/shared-libraries

If the libraries directory does not exist, no libraries are resolved. Components still generate, but calls into library processes end up as direct: addresses with no route behind them, so check this setting first if a module comes out with dangling routes.

Driver and library coordinates

When a process calls the estate's own Java classes, or a JDBC resource names a driver, the generated pom needs coordinates for them. Give one java-library. entry per package prefix.

java-library.oracle.jdbc=com.oracle.database.jdbc:ojdbc11
java-library.org.acme.common=org.acme:acme-common:2.4.0

Without the entry the module still generates, but the report says so and the dependency is missing from the pom.

What has actually been verified

Two things. The output compiles: on the last full run every generated component built under javac with no errors, none excepted. And a generated Spring Boot application starts: the Camel context comes up and its routes reach the started state, which means every component URI resolved, every property placeholder was found and every bean reference bound. That is a class of generation bug compiling does not catch. The figures here are from a run against one private estate.

It is still not equivalence. No message has been pushed through a started route, and no output has been compared against the BusinessWorks original. Compiling is not running, running is not correct, and correct is not equivalent. Until a harness exists this page will not claim otherwise.

The two coverage figures measure something earlier than that, and neither is a success rate. 98.64% of activity occurrences are of a type the generator has a rule for, and 96.37% of the Camel steps it emitted are real steps rather than a TODO_HUMAN marker. Both are counted at generation time: they say how much of the estate the generator had an answer for, not how much of it compiles, and certainly not how much of it behaves. The gap between the two is rules that fire but flag a condition they cannot resolve, and the catalog says which, type by type.