A directory of 4GL becomes a Maven project, and a Maven project becomes one deployable artifact.
The compiler translates a program on its own when that is all you need. What a team actually deploys is a Maven project : the 4GL stays the source under src/main/4gl, the translator runs on every build, and mvn package produces one versioned jar carrying the application, its entry point and the runtime. That is what turns a directory of modules built on somebody's machine into software your delivery pipeline can ship and roll back.
A directory of 4GL, converted into a project an IDE can open
One command scaffolds the project. The .4gl and .per files move under src/main/4gl and stay the source ; the translator runs as a build step, so editing 4GL and pressing build is the whole workflow.
stores/
├── pom.xml j4gl-maven-plugin + j4gl-runtime + shade
├── README.md
├── .gitignore target/
└── src/
└── main/
└── 4gl/ the source — edit these
├── d4_main.4gl MAIN, the ring menu
├── d4_cust.4gl customer maintenance
├── d4_orders.4gl order entry
├── d4_stock.4gl stock enquiry
├── d4_report.4gl the reports
├── d4_globals.4gl GLOBALS shared across modules
├── custform.per the forms, beside the code
├── orderform.per that opens them
├── stock_sel.per
└── helpdemo.src the help file$ j4glc project stores # scaffold, in place
10 4GL source(s) moved into src/main/4gl
$ cd stores && mvn package # translate, compile, package
target/
├── generated-sources/j4gl/ the translated Java, a source
│ └── fgl/*.java root your IDE indexes
├── classes/
└── stores-0.1.0-SNAPSHOT.jar one artifact: your program,
the 4GL runtime, the forms
$ java -jar target/stores-0.1.0-SNAPSHOT.jar
# Edit the 4GL, build again, and the Java is regenerated.
# Set a breakpoint in it and step through a run.The terminal stays, and it is worth being direct about why.
This is a replacement of Informix 4GL with Java, not a move to the web. The screens, the keystrokes and the flow the business uses every day come across unchanged, and for high-volume data entry that is a genuine advantage — an operator who knows the form is faster than any mouse.
But the deeper reason is structural. 4GL was designed around the character terminal : the screen is a fixed grid of rows and columns, the dialog owns the keyboard, and the program blocks while a field is edited. Those assumptions are in the language itself, not in the runtime beneath it. A 4GL program can be rendered in a browser, and it will still be a terminal — one drawn with web technology. That is an emulation, and it is a legitimate thing to buy, but it should be recognised for what it is rather than counted as modernisation.
So we do not compete on it. What changes here is everything behind the screen : the terminal becomes what it always should have been, an input and output device in front of an ordinary Java system, and the rules that took decades to accumulate are carried across rather than re-derived. The estate keeps the interface that works and loses its dependency on a specialist hiring market. When the surface itself is the problem, that is a different exercise and it is described further down this page.
Where we do not compete : emulating a terminal in a browser.
There is an established category of product that renders 4GL character screens in a browser. It works, it is a legitimate piece of engineering, and for some estates it buys time. We do not build one, and the reason is architectural rather than competitive.
What arrives in the browser is still the terminal : 80 columns, one modal dialog at a time, tab order instead of navigation, a function-key legend at the foot of the screen. The rendering technology changes ; the interaction model does not, because the interaction model is written into the 4GL statements themselves.
It also settles nothing underneath. The estate still runs a proprietary runtime, still depends on a narrowing skills market, still cannot expose a rule as an API without a screen-scrape, and has added a rendering layer to keep in step with browser releases. The bill arrives twice — once for the emulation, and again for the modernisation it deferred.
Three layers, and a migration only moves one of them.
An Informix 4GL application is three things stacked on each other, and they are usually discussed as though they were one. At the bottom is the platform that executes it. In the middle are the rules the business actually depends on. At the top is the way those rules are put in front of a person.
A migration moves the bottom layer and leaves the middle one exactly as it was. That is the whole of the exercise — and stating it that way makes the top layer's status obvious : it is not part of the migration at all. Keep the character terminal indefinitely, because the rules do not care what is in front of them. Put a browser front end over the screens that earn one, and leave the others alone. Give the rules no interface and call them as an API, from a queue or on a schedule. Or do all three at once over one set of business logic, which is simply what a normal platform allows.
The reason to be exact about this is commercial rather than architectural. A programme that bundles the platform change and the interface redesign into one project has to justify both at once, schedule both at once and carry the risk of both at once — and it is usually the interface work that makes the estimate unacceptable. Separating them lets the continuity problem be solved on its own timetable, with the interface question still entirely open afterwards, and answered from better ground.
It sequences naturally, too : removing the runtime licence and the hiring constraint can happen while the estate runs exactly as it does today, and the surface can be taken on afterwards with the rules already reachable from whatever you choose to put in front of them. At the far end the question stops being about 4GL at all — if screens, validations and report layouts are to be described once and materialised by a platform, that is a platform decision rather than a language one, and it starts at forward path.
What a migration changes, and what it leaves open
The output has to survive review by someone who never wrote 4GL.
Every converter in this market emits Java that runs. The question that decides the next decade is whether a developer can open one of the files and understand it — because from cut-over onwards, that file is the specification of how the business works.
So legibility is treated as a compiler requirement rather than a
courtesy. One module becomes one class ; one FUNCTION
becomes one method, in source order, with its original name ; all three
4GL comment forms are carried through and re-emitted in place ; and
parameter and return types are recovered by analysis instead of
collapsing to Object. Nothing is merged, reordered or
obfuscated, so a reviewer can hold the 4GL beside the Java and follow it
line for line.
Proven against the compiler it replaces.
The question every migration turns on is whether the translation is faithful, so it is settled by measurement rather than by argument. Translated programs run against a real Informix 7.51 installation alongside the original 4GL binaries, on the same data, and the output is compared byte for byte — arithmetic, dates, NULL handling, string operations, cursors, arrays and report output alike. That is a differential test against the reference implementation, it runs on every build, and a difference is a defect.
The compiler emits Java that is meant to be read. It is not an obfuscated intermediate form, and not a runtime that interprets your 4GL behind a licence : it is code your team can review today and adopt as the source whenever they decide to — this year, in three years, or never. That option is the point of the exercise, and it is why the generated code has to be legible.
Make the application safe to change, before you change the application.
Informix 4GL was designed in the mid-1980s and matured through the 1990s, and it was very good at what it was asked to do. What did not exist then is the practice that has since become ordinary : a suite of automated tests, run on every change, by a machine. Systems written before that was normal were verified the way everything was verified then — by a person, at a terminal, working through a checklist.
What that costs an organisation today is not the testing. It is the caution. When the only way to know whether a change broke something is to have somebody exercise the screens, change becomes expensive, so less of it is attempted ; changes are batched into large releases, which makes each one riskier and the next one later ; and the people who can judge whether a change is safe are the few who remember how the system behaves. That is the arithmetic of testing by hand, not a failure of the team.
Once the application is Java, the whole of that changes at once — with tools the organisation already runs, on infrastructure it already has, and none of it built by us.
What the team gets on the first day, and keeps
These are ordinary capabilities of the Java ecosystem. That is the point : they arrive because the application is Java, so they will still be maintained in ten years by people who have never heard of 4GL.
Syntax colouring for Eclipse, IntelliJ IDEA and Visual Studio Code, generated from the compiler's own lexer rather than written by hand — so the words an editor colours are exactly the words the compiler accepts, and a test fails when the two drift.
A 4GL syntax error is reported as a marker on the statement, in the file that contains it, counted with everything else the project has to say about itself. The same feedback the rest of the organisation's code has had for fifteen years.
Maven holds what an application actually is : an entry point, linked modules, the forms that travel with it, a runtime dependency, a version and somewhere to deploy from. Every build re-translates whatever changed and produces one jar the delivery pipeline can ship and roll back.
A migrated screen runs with no terminal, driven by keystrokes queued ahead of the call, under an ordinary JUnit test in the build you already have. For most estates this is the first time the screens have been testable at all — and the tests are ordinary Java, so a developer who has never read 4GL can write them.
Breakpoints inside a screen handler, conditional breakpoints, watch expressions, hot code replace, sampling profilers and heap analysis. In 4GL the equivalent of most of this was a DISPLAY statement and a rerun.
The application builds where everything else builds. Which means a record, per release, that the pricing, credit and approval rules behaved as specified — generated by the build rather than attested by a person. Auditors are accustomed to asking for that of modern systems, and to being told it does not exist for the older ones.
The language, translated
Whole programs — functions, records, arrays, screens and reports — translate to Java, proven against the original compiler on a reference corpus. Multi-module programs link into a single Java application with an ordinary entry point — not a bundle of scripts.
Screens and reports carried across
Screen forms compile with their geometry recovered, and the interactive dialogs — menus, field input, array input, array display and query-by-example construct — are implemented. Reports carry groups, aggregates, margins, pagination and printer output.
Database access over JDBC
Cursors, transactions and host variables map onto JDBC against the same Informix instance. Column types resolve from the live schema, so a column's type is what the database says it is.
Scope is a number, not an estimate
Every construct the compiler hands to a person is marked in place and counted per program, so the work in front of you is a figure you can plan and price from the first day rather than something that emerges late. Files translate and compile around each marker, so progress is continuous and visible throughout.
Why Maven, when the compiler already translates on its own.
For a single program the compiler is enough, and it works the way a
4GL compiler has always worked : point it at a module with a
MAIN, it links what that module needs and produces
classes you can run. Nothing about the migration requires a build
tool, and for a utility or a nightly report that is the whole story.
An application is a different object. It has an entry point, a set of modules that must be linked together, forms that have to travel with it, a runtime it depends on, a version, and somewhere to be deployed from. Those are exactly the concerns a Maven project exists to hold — so the project is not ceremony added on top of the compiler, it is the thing that turns translated modules into an application.
The plugin runs the translator as a build step, so the 4GL under
src/main/4gl stays the single source while the programme
is running. A developer edits 4GL, presses build, and the Java in
target/generated-sources/j4gl is regenerated — registered
as a source root, so it is navigable, and breakpoints in it are live.
The packaged artifact is one versioned jar : the translated program, the runtime that gives 4GL its semantics, and the compiled forms. It deploys the way every other Java artifact in the estate deploys — a repository, a pipeline, a container image, a version number that can be rolled back. For an organisation whose 4GL currently ships as binaries built on one machine by one person, that alone changes the operational risk profile.
And if you want it, there is a defined moment at which the 4GL can
stop being the source : copy the generated Java into
src/main/java, remove the plugin from the POM, and the
project becomes an ordinary Java project maintained by hand. That step
is available from the first day and obligatory on none of them — an
estate can take it in year one, for a single module, or never.
The months between the decision and the cut-over.
No estate of consequence moves in a weekend. Between the day a migration is approved and the day the 4GL is switched off, there is a long middle in which both languages are real : the 4GL still runs the business and still gets changed, while the Java is reviewed, tested and learned. That period is measured in quarters, not sprints.
Most conversion tooling has nothing to say about it. It translates once and hands over a folder, which is exactly right on the last day and exactly wrong on every day before it — because the moment someone fixes a bug in the 4GL, the Java is stale and no build will tell you so.
One translator, one build
The project is a Maven project. The 4GL is its source.
Be clear about what does not change : your .4gl and
.per files remain the source. The team edits 4GL, the build
translates it, and the Java is what runs. Nobody is asked to learn a new
language in order to get the licence and the hiring constraint off the
estate.
What changes is the build. The sources sit inside an ordinary Maven
project, and mvn compile is the whole workflow : every build
re-runs the translator over whatever changed, compiles the result, and
produces a single runnable jar with its dependencies inside it. Run it
with java -jar and it lists the programs it contains ; name
one and it runs.
That is what makes this a migration rather than a translation. A 4GL estate that was a directory of binaries and a shell script becomes a versioned artifact, built by the pipeline the organisation already runs, promoted through the environments everything else is promoted through, and rolled back by replacing one file. The dependency management, the test and coverage tooling, the static analysis and the continuous integration all arrive with it, because they come with Maven rather than from us.
And because the project is an ordinary Maven project, an IDE imports it and indexes the generated Java with no further setup — which is where the debugger below comes in.
For a single program, there is the direct command.
Not everything needs a project. j4glc compiles and runs one
program, or a named set of them, straight from the command line — the
fastest way to see what a particular module translates to, to check a
change to one screen, or to run a batch program on its own during
development.
It is the same translator reading the same catalogue and producing the same Java as the build does, so nothing learned from it has to be re-checked later. It is a tool for a developer at a terminal ; the Maven project is how the application is built and shipped.
Whether to stop writing 4GL is a separate question, and “never” is an answer.
Nothing about the build forces that decision or delays it. Whenever the team is ready — on their own evidence, having read and debugged the output for months — the generated Java can be adopted as the source and the translation step taken out. Equally, it need not be.
4GL is a smaller and more direct language for this work : a
DECIMAL that behaves the way an accountant expects without
a library, cursors and reports in the language rather than bolted onto
it, a despatch rule in ten lines that Java needs a page for. A team
whose domain experts write it fluently has a good reason to go on
writing it, and a build that translates on every compile lets them.
What the migration ends is the runtime licence, the toolchain and the dependency on a language the market no longer teaches — on the first day, whatever you later decide about the language itself. Everything that runs is Java on a JDK from that day forward.
And the two mix on purpose. The generated Java is ordinary Java in an ordinary project, so new capability can be added in Java beside the rules — a REST endpoint over an existing rule, a scheduled job, a message consumer, a library the 4GL never had — while the rules stay in 4GL and keep being edited there. That is a working arrangement, not a transitional one, and for many estates it is the right permanent answer.
Why the Maven path is the one that builds confidence.
Both paths produce the same Java, so the argument for the Maven one is
not the output — it is that a team cannot take ownership of code it has
only ever read, and the Maven path keeps that code present, current and
indexed at the moment a developer is curious.
Reviewing twenty thousand lines of generated Java in a
pull request teaches almost nothing ; setting a breakpoint inside a
credit-limit check, watching a MONEY value carry its scale
through an arithmetic expression, and stepping into the runtime call
that reproduces a 4GL comparison teaches the whole design in an
afternoon.
That is only possible if the generated Java is present, current and indexed by the development environment at the moment the developer is curious. If regenerating it is a separate command somebody has to remember, it will be out of date exactly when it matters.
The 4GL stays the source
Sources sit inside the project and are what the team edits. The generated Java is written beneath the build directory, where the conventions of the ecosystem already say it is an output rather than something to be edited — the same arrangement a parser generator or a schema binder uses.
The build keeps them in step
Every build checks whether any source is newer than the generated code and retranslates when it is. A build with nothing to do says so and does nothing. Correctness here is deliberately conservative : one changed module regenerates the program, because modules share declarations and a cleverer rule that is wrong ships stale Java.
Every file says when it was produced
Generated files carry the source they came from and the moment they were generated, and say plainly that the next build will overwrite them. Nobody discovers by accident that an afternoon of edits is about to disappear.
The database is not optional
Column types decide the shape of the generated code, so translation reads the catalogue of the database each program declares — the same requirement the original Informix compiler has. There is no cached snapshot to drift out of date, because a wrong type that looks right is worse than a build that stops.
The 4GL is edited every day of the migration, so the editor has to know it.
A migration of any size is not a stop-the-world event. The business
keeps asking for changes, and for as long as the migration lasts those
changes are made in 4GL — by the same people, in whatever editor
they already have open. The distribution therefore carries syntax
colouring for .4gl files : a TextMate grammar that serves
Eclipse and Visual Studio Code, and a file type definition for IntelliJ
IDEA and the other JetBrains IDEs.
Both are generated from the compiler’s own lexer rather than written by hand, which is the part that matters. A hand-maintained keyword list is a second, informal statement of what the language is : it starts slightly wrong and drifts with every change, and its errors are invisible, because a word that should be coloured and is not looks merely like a word. Generating them makes the words an editor colours exactly the words the compiler accepts, and the build fails when the grammar gains a keyword the editor files do not know.
A syntax error is reported the way a Java syntax error is : as a marker on the statement, in the file that contains it, counted alongside everything else the project has to say about itself. Not a stack trace against the build file with no line to click.
A syntax error on the line that caused it

What the build does when nothing has changed.
The translator runs on every build, before the Java compiler, and the first thing it does is decide whether to translate at all. That decision is the difference between a development environment that responds to a saved file and one that appears to hang on it, so it is worth being precise about. Four questions, in order — the first that answers “yes, work is needed” ends the sequence.
Has a program been deleted?
A source that is gone must take its generated Java with it, and timestamps cannot see this : removing a program leaves every surviving file untouched. Generated code for a program that no longer exists is dead code that looks live, which during a migration is the expensive kind.
Does every program have output?
Or a recorded reason for having none. The existence of an output directory is not enough : an empty one used to mean “up to date” indefinitely, over a program that had never been generated at all, and the symptom surfaced layers away as a missing entry point.
Did the editor say a 4GL file changed?
Asked only inside an IDE, which rebuilds a whole project when one file is saved. The workspace already knows what was touched, so asking it turns “translate on every save” into “translate when a 4GL file was saved”.
Is any source newer than the generated code?
The command-line equivalent, and the question that decides a build outside an IDE. Form definitions count as sources here, not only programs.
The unit of decision is the source tree, not the individual program.
When the answer is that work is needed, every program under that source
directory is translated again — not only the one that changed.
This is a property of the language rather than a shortcut : a 4GL
program is linked from its call graph, and a module reached through
GLOBALS can belong to several programs at once, so working
out which programs a single edited module belongs to means parsing all
of them. The analysis that would avoid the work costs the same as the
work. What the build reliably avoids is translating when
nothing has changed, which is the case that dominates a working
day.
The catalogue is the other cost in that loop, because translation resolves declarations made against table columns. It is read once per database per build rather than once per program : a source tree of forty programs naming one database opens one connection and reads one catalogue. Database names are compared without regard to case, because Informix does not distinguish them. And a build that decides no translation is needed does not read the catalogue at all — which is what makes a no-op build independent of whether the server is reachable.
One module per subsystem, decided the day the project is scaffolded.
The build translates whatever source tree has changed, and it
translates all of it : a module reached through GLOBALS
can belong to several programs at once, so establishing which
programs one edited module belongs to costs the same as translating
them. On an estate of half a million lines and up, that arithmetic
decides how the project is laid out.
So a large estate is scaffolded as several Maven modules — one per subsystem, following the boundaries the business already recognises rather than the directory structure that accumulated. Each module carries its own 4GL and translates independently, so an edit in despatch retranslates despatch and nothing else. A single source tree holding three million lines retranslates three million lines every time somebody saves a file.
It is a scaffold-time decision and an uncomfortable one to retrofit, which is why it is the first thing we work through in the setup session rather than something discovered when the build gets slow. The same partition is what later lets waves go into production independently, so it pays twice.
What an IDE gives you the moment the project is imported.
The generated folder is registered with the build as a source root, so IntelliJ IDEA, Eclipse and Visual Studio Code index it on import with no further configuration. From there the developer has the full Java toolchain — not a viewer, not a trace file, but the debugger the rest of the industry uses every day.
The tooling a 4GL developer has never had
These are ordinary Java capabilities. That is the point : the migration’s real return is that the application becomes debuggable with tools the general market already knows.
Stop inside a screen handler, step into the runtime call that implements a 4GL construct, and walk back up the stack to the statement that caused it. In 4GL the equivalent was a DISPLAY statement and a rerun.
Stop only when a customer number matches, only on the two-hundredth iteration, or the moment a specific exception is constructed anywhere in the program. Investigations that took a day of narrowing take minutes.
Inspect any record or array at any point, and evaluate an arbitrary expression against live program state without recompiling — including calling a translated function to see what it returns for the case in front of you.
Change a method body and continue the same session without restarting the application or losing the state that took ten screens to reach.
Attach the same profilers used across the Java estate. Where a report took an unexplained twenty minutes, the query and the loop responsible are now visible rather than inferred.
Unit tests, coverage, static analysis and continuous integration apply to the migrated application as they do to any other Java codebase, on infrastructure the organisation already runs.
Changing your mind is a decision, not a project.
If and when the team wants it — and that decision is theirs, taken on their own evidence — the generated Java is adopted as the project’s source and the translation step is removed from the build. Nothing has to be rewritten or re-approved to make that happen ; the code that has been running in production is the code that stays, and the jar the build produces on the day after is the jar it produced on the day before.
That is what separates a source migration from converting to another proprietary language. The translator is not a dependency you acquire. It is a scaffold you take down when it has done its work, leaving Java your team already knows how to debug — because they have been debugging it for months.
Java is a modernisation route, not the option that leaves things as they are.
This path is sometimes read as the conservative one — the estate becomes Java and otherwise stands still. That is true of the first day, deliberately, because nobody should have to fund a user-interface project in order to fund a language migration. It is not true of the second year.
What a JVM application can do that a 4GL program could not is the whole reason the language question mattered. The same rules can be exposed as an HTTP API, published to a queue, called from a scheduled job, driven by a web or mobile front end, packaged into a container, and observed by whatever the estate already uses. Integration stops being a bespoke extract written for each request and becomes ordinary work that any Java developer can pick up. A modern interface, when the business wants one, is then a project in front of a codebase that can already answer it — not a rewrite of the rules.
And none of it has to happen at once, or at all. The 4GL can go on being the source while the build translates it ; a single module can be taken over in Java while the rest stays as it is ; new work can be written in Java against rules still authored in 4GL. The routes mix, and the mixture is a normal state rather than an unfinished migration.
The screens become automatable, which they never were.
A 4GL application assumes a human : MENU waits for a
keystroke, INPUT waits for a field, and nothing proceeds
without a terminal. That assumption is why most estates have no
automated tests of their screen programs, and no way to load data
through the paths a clerk uses.
Every translated application accepts keystrokes as an argument and can run with no terminal at all. The keys are queued into the same input path a keyboard feeds, so the dialog runs exactly as it runs for a person — every field validation fires, every lookup fills, every rule that refuses a despatch still refuses it.
That turns two problems into ordinary work. Data entry can be
automated through the rules rather than around them : a bulk
import, an acquisition's customer file or a nightly feed goes in the
way a clerk would enter it, instead of through INSERT
statements that bypass every check living in the screen. And
screen programs can be put under test, in a build,
for most estates the first time that has been possible — it is the
same mechanism the migration itself is verified with.
It is a Java API as well as a command line, so a migrated screen runs under an ordinary JUnit test : an in-memory screen, keystrokes queued ahead of the call, assertions on what appeared or on what reached the database. The test is ordinary Java — it runs in the build the organisation already has and can be written by a developer who has never read 4GL, which is usually the real constraint. Teams often start there, writing characterisation tests against the original behaviour and holding the translation to them.
The full treatment, including where it is the wrong tool, is in the paper.
Why a delivery lead should care more about this than about the translation.
The translation is the part that looks like the work. This is the part that changes what the organisation can do afterwards, and it is worth putting in the terms a steering committee weighs.
An estate with no automated tests is not badly run. It is a system built before automated testing was ordinary practice, verified the way everything was verified then. But the consequence today is a tax paid every quarter : changes are expensive because verifying them is manual, so fewer are attempted ; they are batched into large releases, which makes each release riskier and the next one later ; and the judgement of whether a change is safe sits with the few people who remember how the system behaves, so every departure takes some of it away.
Putting the screens under test moves that judgement from people to a build. A change is checked in minutes, on every commit, against the behaviour the business actually relies on. Releases get smaller and more frequent because each one is verified. A characterisation test is a written statement of what the system does in a form that fails when it stops being true, which is the documentation these estates have always been missing and the only kind that cannot go stale quietly. And because the tests are ordinary Java, a new developer can change the application with evidence rather than folklore — which is what makes them productive on the legacy system in their first month rather than their first year.
In regulated sectors there is usually a further argument, and it is often the one that carries : an automated suite produces evidence per release that the rules behaved as specified, generated by the build rather than attested by a person.
None of it requires the 4GL to be abandoned. Tests written this way exercise the rules wherever they live — in 4GL the build translates, in Java the team has adopted, or both at once — so the discipline can be established first and the language decisions taken later, on their own merits. For most estates that is the right order : make the system safe to change, then decide what to change.
The paper.
The engineering behind the compiler is written up in full — the constraint that shapes every design decision, the pipeline and its intermediate representation, how an untyped 4GL signature becomes a typed Java method, why decimal arithmetic is what a naive translation loses quietly, a verbatim credit-limit rule and its translation, the terminal surface reproduced on the JVM, what the build produces, and how fidelity is proven against the original binaries rather than asserted.
4GL to Java — the compiler, in full (PDF) — written for the architect who has to decide what happens to the estate.
The alternatives, and what this touches.
How this differs from the other routes in the market, and what it leaves alone in the estate.
How is this different from other 4GL-to-Java converters?
Chiefly in what the Java looks like on the day you inherit it. Automated conversion is not hard to demonstrate ; producing Java that a developer can read, review and maintain for a decade is the part that decides whether the investment holds. So structure is preserved rather than flattened — one module to one class, one function to one method in source order, comments carried through, types recovered by analysis instead of collapsing to Object. The second difference is how fidelity is established : translated programs are run against your original 4GL binaries on the same data and compared byte for byte, rather than summarised as an automation percentage.
Does the database change?
No. This migrates the application source, not the data tier. The schema, the data and the Informix instance stay exactly as they are, and the migrated Java reaches them over JDBC. Changing one variable at a time is what makes the result auditable.
Proven, on your source.
How fidelity is established, and how the answer for your own estate is produced before anything is committed.
How do we know the translated program behaves as the original?
By running both. The same programs are compiled by the original Informix toolchain and by this compiler, executed against the same database, and the output is compared byte for byte — arithmetic, dates, NULL handling, cursors, screens and reports alike. That differential test runs against a real Informix installation on every build of the compiler, and a difference is a defect. Fidelity is treated as an engineering measurement, not a reassurance — because it is the principal risk of any migration, ours included, and the only honest way to retire it is to measure it.
Will it handle our code?
That question has an exact answer, and it is produced rather than promised. An assessment translates a representative slice of your own estate and returns the evidence : what translated, compiled and ran ; the output compared against your own 4GL binaries ; and every construct that needs a human decision, marked in place and counted per program. Whole applications — screens, dialogs, reports and database access — translate and run, and the assessment turns that general fact into the specific figure for your code, which is the only figure worth planning a programme from.
What does the team work with from minute one?
An ordinary Maven project, scaffolded around the existing 4GL : the sources stay the sources, the translator runs on every build, and the result is one versioned artifact your pipeline can ship. And JUnit, from the same first build — the runtime provides the machinery to drive a 4GL program with no terminal, keystrokes queued ahead of the run and assertions on what it displayed, so screen programs go under automated test for the first time. Characterisation tests can be written before cut-over, by Java developers who have never read a line of 4GL — which means the estate becomes safe to change before anything about it changes.
Your knowledge becomes more valuable, not less.
The people who know the estate best are the ones a migration cannot succeed without — and this one is built around that fact.
Is this the end of our 4GL work?
No — and the architecture is the proof, not a reassurance. The 4GL stays the source for as long as that suits the people who know the rules : you keep authoring 4GL, the build translates it on every compile, and nothing obliges anyone to stop. The constructs the compiler deliberately escalates for a human decision are decided by the people who understand what the code means — which is you. Nobody else knows which screen is used daily, which edge case is load-bearing, or why the surcharge exemption works the way it does.
What changes for us day to day?
The toolchain around you gets dramatically better while the language stays the same : an editor that colours 4GL correctly, errors on the line that caused them, a build that produces one versioned artifact, and screens under automated test for the first time. Java developers can join the estate without displacing anyone — they write tests and new capability around rules you continue to own in the language you know best.
Fitting it to your pipeline.
What the build needs, what it produces, and what a team has to have in place before starting.
Do we have to adopt Maven?
In practice it is what you want, because it is what brings the rest with it — the runnable jar, dependency management, IDE integration, the test and coverage tooling and the pipeline. Scaffolding the project around existing 4GL is a single command and the sources stay where they are. For a developer who simply wants to see one module translated, or to run one batch program during development, j4glc does that from the command line without a project at all.
How does the application actually get deployed?
As a single runnable jar containing every program in the estate, with its dependencies inside it — so there is no classpath to assemble and no shell script wrapping a directory of binaries. Run it with no arguments and it lists the programs it contains ; name one and it runs. That is an ordinary Java build output, so it versions, promotes and deploys through whatever pipeline the organisation already uses for its other services, and rolling back is replacing one file.
Does this mean the build now depends on a database being reachable?
Yes, for any program that declares one, and this matches the behaviour of the original Informix compiler. Column precision and scale determine the generated types, so a build without the catalogue would be guessing. Programs that declare no database translate with no server at all.
What does a team need to have in place?
A JDK, a Maven build and network access to the Informix instance from wherever the build runs. Nothing else — no agent, no runtime licence and no vendor infrastructure.
The daily loop.
What you edit, what stops you editing the wrong thing, and what the debugger gives you.
Does j4glc produce different Java from the Maven build?
No. It is the same translator reading the same catalogue and producing the same output ; the build simply wraps it in a project and packages the result. That equivalence is verified rather than assumed — the same program is translated both ways and the results are compared.
What stops a developer editing the generated Java by mistake?
Its location and its header. The code sits beneath the build directory, which the ecosystem already treats as output and which source control ignores by default, and every file states that the next build overwrites it. When editing it by hand becomes the right thing to do, that is the cut-over, and it is made deliberately.
Do the terminal screens work under the debugger?
Yes. Screens defined in the original form files are translated alongside the program, so a form opens under a debugger exactly as it does in production and can be stopped on mid-entry. The application is an ordinary Java process throughout.