07 · Case study
A GDS booking applicationVB6 or VB.NET · XSD · XSLT · Java 21 · Spring Boot
The system
The problem
A booking desk written in Visual Basic against a global distribution system is two programs pretending to be one. The part you can see is the forms — controls, event handlers, and the rules that accumulated inside them across twenty years of fare and ticketing changes. The part that actually matters is the XML: the messages sent to and received from the GDS, the schemas that describe them, and the transforms in between.
Rewrites of these systems usually start from the forms, because that is what people point at. The XML then gets reconstructed from whatever sample messages someone can find in a log, which means the new system handles the fields that happened to appear in those samples and silently drops the ones that did not. The optional element that only shows up on an exchange ticket is exactly the element nobody had a sample of.
What ran
There is no tree-sitter grammar for Visual Basic, so the source front end is a deterministic scanner that ships inside the playbook — the same arrangement rewrite-vbscript-csharp already uses for classic ASP. It reads .vbp, .frm, .bas and .cls for classic VB, or .vbproj and .vb for VB.NET: forms and their controls, event handlers, standard and class modules, public routines, COM references, ADO or ODBC calls, and every SQL string written as a literal.
An XSD declares types, cardinality and enumerations. Where one exists, the Java model is generated from it, so an element that appears in no sample message still exists in the model with the right type. Where a message has no schema, the sample set is reported as the only evidence there is, and every type inferred from it is labelled as inferred rather than presented as declared. XSLT transforms are inventoried with the elements each one reads and writes.
The questionnaire captures target decisions and nothing else: the Spring Boot line and the Java level, Maven or Gradle, JPA or JDBC, and what becomes of the forms — an HTTP API with a new front end, a service behind the existing screens, or a scheduled job. Nothing the source already states is asked twice.
Each form, module, class, public routine, schema and transform becomes a checklist item carrying its source location and the Java file it will become. The plan is reviewable before the first file lands, and a unit nobody wants migrated is struck out there rather than deleted afterwards.
A unit is emitted and the project compiled before the next one starts, so a failure names the unit that caused it instead of arriving as a wall of errors at the end. This is the same discipline the stored-procedure and COBOL migrations use, and it is the reason a partial run leaves a project that still builds.
Deterministic gates, no model in the loop: fare and tax fields are BigDecimal and never double — the same rule that already blocks a COBOL to Java cutover — every element the schema declares is present in the generated model, no SQL literal survives outside a repository, and the project builds and its tests run. A gate at fail severity blocks cutover.
What it did
A typed model of the wire format
Java types generated from the XSDs, with the transforms expressed as mapping code rather than as XSLT carried forward into a new runtime. The messages the application sends and receives are then a compile-time contract instead of string handling.
The business rules, out of the event handlers
A rule that lived in a button click becomes a method on a service with a name and a test. What it does is preserved exactly; where the VB relied on a coercion the migration cannot prove is equivalent, the case is listed rather than quietly resolved.
An inventory of what did not come across
COM components with no Java equivalent, controls with no counterpart, and any message the schemas do not cover are named on the page. An estate this old always has some of these, and the migration that reports none is the one that has not looked.
Where it ended
The migration will not guess at fare rules. Where a VB routine rounds a number, the rounding it performs is carried across exactly as written — not corrected, even where it looks wrong — and flagged for a person to rule on. A migration that quietly improves behaviour is one you cannot reconcile against the system it replaced.
What happens next
The reason to start from the XML rather than the forms is that the XML is the part with a specification. Forms encode what someone decided one afternoon; a schema encodes what the GDS will actually accept. Build the model from the schema and the forms become a rendering question. Build it from the forms and you find out which elements you missed in production.