Repository navigation
J2C Modern Edition 0.6.0 (preview)
Pre-releaseFirst release of J2C Modern Edition: a standalone Java-to-C++ CLI and a VS
Code extension that bundles it, parsing Java 17 source and generating
C++20-targeted output.
Command line and extension
- Standalone CLI with
--sourcepath,--classpath,--name,
--sealed-types advisory|reject,--encoding,--check, and--version. --checklists every compile error and unsupported construct as
file:line: message, with a summary per kind, without writing output.- Translation problems are reported as
file:linediagnostics without a stack
trace. Exit status is 0 on success, 1 for translation problems, and 2 for
invalid arguments. - Source directories skip
module-info.javaandpackage-info.java, which
declare no types; a module declaration no longer fails the check. - Sources are read as UTF-8 by default and generated C++ is always UTF-8,
independent of the platform charset. - VS Code: J2C: Translate to C++ and J2C: Check Translatability in the
editor and Explorer menus. Problems appear in the Problems panel; runs can
be cancelled, and only one runs at a time. - Problems have stable IDs with fix hints (
j2c explain), and--format json
prints machine-readable results with line and column.j2c capabilities
describes supported features and limitations. - Translation refuses to replace a non-empty output directory that J2C did
not create (its contents would be moved to a backup) unless--forceis
given; the extension asks first. - Translation checks every selected file before writing anything, and
reports the problems of all of them at once. A translation that fails
this way leaves the output directory as it was: earlier output is not
moved to the backup, and an empty directory gets no.j2c-outputmarker. - A Java file that is only a dependency (on the source path, not selected)
and does not compile no longer fails the translation: like a library
class, it gets a header and stubs from what the compiler resolved, and no
implementation. The warningJ2C-DEPENDENCY-ERRORS, at its first error,
is printed to stderr and listed inwarningsin JSON. Selected files with
errors still fail.
Runtime (preview)
-
--runtime <directory>translates against a J2C runtime instead of the
running JDK: its Java sources replacejava.*, its C++ natives replace
the generated stubs, and the Makefile builds its hooks. -
The repository's
runtime/is written from the Java SE specification and
covers:String,StringBuilder, the boxed types,MathandObjects;- the exception classes, with Java's uncaught-exception printout and exit
status; - the collections:
ArrayList,LinkedList,ArrayDeque,
PriorityQueue,HashMap,LinkedHashMap,TreeMap, their sets,
Collections,Arrays,Comparatorandjava.util.function; - streams:
Stream,IntStream,LongStream,DoubleStreamand
Collectors, sequential and lazy in the JVM's order, withOptional
and its primitive forms and the summary statistics; String.format,formatted,printfandFormatter, with Java's
rounding and exception messages (no%tdate conversions);java.util.regex(Pattern,Matcher,MatchResult,
PatternSyntaxExceptionwith the JDK's messages), a backtracking
matcher whose stack lives on the heap, used byString.matches,
split,replaceAllandreplaceFirst: classes with intersection,
groups (named, atomic), backreferences, look-around, greedy, reluctant
and possessive quantifiers, and the inline flags.- text input and files:
System.in, thejava.iostreams, readers and
writers (BufferedReader,PrintWriter,FileWriterand the rest),
File,Scanner(whitespace or literal delimiters, no regular
expressions),java.nio.file.FilesandPath, and the UTF-8,
ISO-8859-1 and US-ASCII charsets, with the JVM's buffering, malformed
input handling and exception messages. - threads:
Thread(start, join, sleep, interrupt, names, daemons, the
uncaught-exception handlers and printout),ThreadLocal,
Object.wait,notifyandnotifyAll, andjava.util.concurrent's
atomics,Executors.newFixedThreadPoolwithFuture,
CountDownLatch,ReentrantLockwithCondition,TimeUnitand
ConcurrentHashMap, plusCollections.synchronizedListand
synchronizedMap. Program exit waits for the non-daemon threads.
System.outand the other shared pieces (string interning, class
initialization,Math.random) are thread-safe.
HashMapandHashSetiterate in the JVM's order, so printed maps match. -
--runtime bundleduses the runtime packaged in the CLI JAR, so no
repository checkout is needed (the JAR grows by about 250 KB). It is
extracted on first use into~/.j2c/cache(orJ2C_CACHE_DIR) as
runtime-<version>-<hash>, reused while its files are intact and
replaced when one is missing, changed or added; concurrent processes are
safe. The output equals--runtimewith the checkout'sruntime/.
--runtime <directory>is unchanged, and the CLI still translates
without a runtime by default. Seedocs/RUNTIME.md. -
The MCP tools
j2c_check,j2c_translate,j2c_fix,j2c_report,
j2c_buildandj2c_testtakeruntime("bundled"or a directory),
andj2c buildandj2c testtake--runtime bundled. -
VS Code: the
j2c.runtimesetting,bundledby default, so Check and
Translate use the J2C runtime and translated programs run. This changes
the extension's output:java.*comes from the runtime, so code using JDK
classes it does not provide (such asjava.time) now gets
J2C-COMPILE-ERRORproblems instead of stubs. Setj2c.runtimeto
nonefor the previous behavior, or to a runtime directory. -
The order of includes in generated files no longer depends on the
directory the sources are in (JDT puts it into the keys of generic source
types), so a runtime gives the same output wherever it is. -
j2c reportnames the runtime it compares with as--runtime bundled
when no runtime directory is given. -
make J2C_GC=boehmlinks a program with the Boehm garbage collector,
which then serves every allocation (Java objects and the runtime's C++
containers), so memory is reclaimed. Exceptions in flight stay reachable
whilefinallyblocks run. Threads are registered with the collector;
make testbinaries are not covered. -
CI translates each program in
test/runtime, builds it under
UndefinedBehaviorSanitizer, and compares its output and exit status with
java, then does the same withJ2C_GC=boehm, collecting every 1000
allocations.test/runtime/allocationallocates about 1 GB and must stay
under 100 MB of resident memory with the collector. A program's
input.txtis its standard input, and each run starts in an empty
working directory, so programs can write files. -
synchronizedmethods now hold the monitor ofthis, or of the class
when static; before, the modifier was dropped silently. Asynchronized
block onnullthrowsNullPointerException, and static initializers run
once when several threads reach a class at the same time. Generated
Makefiles build with-pthread. -
Every translation now includes
java.lang.Classand
java.lang.ArrayStoreException, which generated code always uses, and
programs link the program library again after the-extlibrary. -
A lambda or method reference whose types include an inner class of a
parameterized class (Outer<A>.Inner) translates; it was a compile error
because the inner class was written raw. -
A member class of a local or anonymous class that reads a variable the
local class captured translates; translation crashed with a
NullPointerExceptionalthough--checkfound no problems. -
A lambda that J2C reports instead of translating (such as one using
this
of an anonymous class) is no longer also reported, with a runtime, as a
compile error aboutjava.lang.invoke.LambdaMetafactoryat line 1.
Unit tests
--teststranslates JUnit 4 and 5 test classes and generates a GoogleTest
wrapper for each runnable one, with JUnit's lifecycle order (including
inherited methods), skipped disabled tests, and expected exceptions; the
Makefile gainstestsandtesttargets.- With
--tests,--checkreports test features the wrappers cannot run
(mocking libraries, runners and extensions, parameterized or repeated tests,
@Nested, timeouts,@TestInstance, private or parameterized methods).
JUnit 4's@Test(timeout = ...)is reported too (it was missed when the
timeout was written as anint). - JUnit assertions and assumptions (
assertEqualswith and without delta,
assertTrue,assertNull,assertSame,assertArrayEquals,fail,
assertThrows,assertDoesNotThrow,assumeTrue, and others) are
translated into helpers that follow JUnit's rules and messages without a
Java runtime. Other assertion forms are reported. Comparing JDK objects
and showing custom messages need the runtime. - With
--runtime, translated tests compare JDK objects (strings, boxed
numbers, lists, maps) with Java'sequals, and failure messages match
JUnit's: values shown withtoString, JUnit 5'smessage ==>prefix,
Supplier<String>messages called only on failure, and JUnit 4's own
forms (expected:<a> but was:<b>, strings compacted as
ComparisonFailuredoes, itsassertNull,assertSame,assertNotEquals
and array messages). TheAssertionErrorcarries the message, and
GoogleTest reports every exception's message. JUnit 4's assertions now
translate intoj2c::junit::junit4helpers. - JUnit 4's
@Test(expected = ...)failures use JUnit's messages
(Expected exception: X,Unexpected exception, expected<X> but was<Y>). - The JUnit fixtures run with real JUnit 5.10 and 4.13 on the JVM
(npm run junit:jvm) and, translated, with GoogleTest against stubs and
against the runtime; CI checks that all three give the outcomes in
expected-results.json, and that the JVM and the runtime give its
failure messages (73 tests, 44 messages). --mocksgenerates a gMock class for each top-level and member interface
of the translated files (mock/<package>/Mock<Interface>.hpp, class
<package>::Mock<Interface>), for C++ tests written by hand. Abstract
methods, inherited ones too, are mocked withMOCK_METHODand the erased
C++ types; default methods keep their Java bodies; a method overriding an
inherited one with another erasure gets the bridge a Java class would
have. The Makefile buildsUSER_TEST_SRCSinto the tests binary and links
gMock (GMOCK_CFLAGS,GMOCK_LIBS). Methods with more than 15
parameters, and inherited erased return types no C++ override accepts,
are reported asJ2C-MOCK-UNSUPPORTED. Also themocksargument of
j2c_check,j2c_translate,j2c_buildandj2c_test(whose results
listmockFiles;--format jsontranslations too), and thej2c.mocks
setting. CI builds a hand-written gMock test against the generated mocks
and compiles every mock oftest/src.
AI agents
- An agent benchmark (
test/agent-bench, for development): 13 tasks across
quick fixes, manual rewrites, negative controls, running against the
runtime, JUnit tests and assessment. They are graded deterministically
(protected files,javac, hidden drivers,j2c --check,j2c run,
j2c testwith mutants, the report) and run as headless Claude Code
sessions under the developer's login (npm run bench).npm run bench:selftestchecks the graders without an agent, in CI. j2c mcpserves J2C as a Model Context Protocol server withj2c_check,
j2c_translate,j2c_explain, andj2c_capabilitiestools, confined to a
workspace root, for Claude Code, Claude Desktop, Copilot, and other MCP
clients.- The VS Code extension registers the server for each workspace folder
(j2c.mcp.enabled), so Copilot agent mode gets the tools without setup. docs/AGENTS.md, a Claude Code skill, and Copilot instructions describe the
check-fix-translate workflow;AGENTS.mdandCLAUDE.mdguide agents
working on J2C itself.- Project auto-setup: a Maven or Gradle project directory as input (
.on
the command line,paths: ["."]in MCP, the workspace folder in VS Code)
uses the build's source roots (Maven modules,<sourceDirectory>,
build-helper-maven-pluginsources; Gradle includes and source sets),
test roots with--tests, and the dependency class path from the
project's wrapper ormvn/gradle, cached by a hash of the build files.
--project auto|none|<directory>and--no-build-toolcontrol it (MCP:
project,buildTool, andj2c mcp --project none --no-build-tool;
VS Code:j2c.project). A missing or failing build tool is the warning
J2C-PROJECT-CLASSPATH-UNRESOLVED; JSON results gainprojectsand
warnings, andj2c capabilitieslistsbuildTools. - Structured quick fixes: problems with a mechanical fix carry the exact
edits to the Java source in--format jsonoutput (fixes, JSON schema
version 2):J2C-LAMBDA-ANONYMOUS-THIS(var self = this;),
J2C-LAMBDA-INTERFACE-SUPER(a private helper method),
J2C-PATTERN-OUTSIDE-METHOD(the initializer moved into a method),
J2C-TEST-TIMEOUTandJ2C-TEST-PRIVATE-METHOD. j2c fix [--apply] <paths>previews the fixes as a unified diff, or with
--applywrites them and checks again until no fixable problem is left. It
exits with 0 when no problem needs changes by hand, else 1. The MCP server
adds aj2c_fixtool (preview by default,apply: truewrites), and the
VS Code extension offers the fixes as quick fixes on J2C diagnostics.- Migration readiness report:
j2c report [--format markdown|json] <paths>
(the same inputs and options as--check, plus--runtime) and the
read-onlyj2c_reportMCP tool show the files, types and methods that
translate (those with no problem from--checkwithin them) with
percentages, the blockers by problem ID and by package with their top
locations and quick-fix counts, test-feature blockers with--tests, the
JDK types and members the code uses (by use count) compared with what the
J2C runtime provides, and next steps. The Markdown reads on GitHub; the
command exits with 0 whenever the report is produced. The bundled
runtime's API is generated intoruntime/api.txtand shipped in the JAR.
JSON schema version 3. - MCP prompts and resources: the
j2c-migrate(check, fix, translate,
build) andj2c-assess(report and summarize) prompts, and the resources
j2c://problems,j2c://problems/{id},j2c://runtime(the runtime's
contract and types) andj2c://output/{path}(translated files inside an
output directory with J2C's marker, under the workspace root). - Distribution, ready to publish:
server.jsonfor the MCP registry
(io.github.juangp3/j2c), the npm package@juangp3/j2c-mcpit names
(npx @juangp3/j2c-mcp, bundling the CLI JAR; attached to the release),
a Claude Code plugin (agents/claude/plugin, with the skill and the
server) listed in.claude-plugin/marketplace.json, and a configuration
for Copilot's cloud coding agent (agents/copilot/mcp-config.json).
vscode/scripts/check-distribution.jschecks that they agree with the
release version. - Faster re-checks in the MCP server: check results are kept between calls,
and a re-check compiles only the changed files and the files that may use
them (an edit inside method bodies compiles just that file), with the same
result as a fresh check.j2c mcp --check-cache <files>bounds what is
kept (default 20000 files,0turns it off). On joda-time, a re-check
after a body edit takes 0.13 s instead of 1.2 s (2.6 s from the command
line). - MCP progress and cancellation: tool calls run on a worker thread, so
pingis answered while one runs (other requests run in order); a call with
_meta.progressTokengetsnotifications/progress(files checked, build
tool running, files translated), files at most every 100 ms and every
phase (such as running the program) always; and
notifications/cancelledstops a call at its next file or phase, stopping
the build tool, with no response. A cancelled translation leaves the files
written so far and the.j2c-outputmarker. - Problems are listed by file path in every check (they were in JDT's
order). The.j2c-outputmarker is written before translation starts, so
a failed or cancelled translation's partial output is replaced next time
without--force. - C++ errors mapped back to Java:
--line-directivesadds#line
directives to the generated.cppand.hppfiles (method bodies, field
initializers and declarations, including lambdas, which map to the
lambda's own lines), so GCC, Clang and gdb name Java files and lines
(break Foo.java:21works). Each C++ line maps to the Java statement it
came from; generated boilerplate maps back to the C++ file's own lines.
Paths are relative to the output directory, wheremakeruns. Without the
option the output is unchanged, byte for byte. j2c build --output <directory> [inputs]translates the inputs with
directives (or uses earlier J2C output), runsmake -k(in WSL on Windows
whenmakeis missing;--make,--wsl auto|always|never,
--wsl-distro,--target,--jobs, orJ2C_MAKE,J2C_WSL,
J2C_WSL_DISTRO) and reports GCC and Clang errors, warnings and notes
(with their include chains and linker errors) at their Java file and line,
with the C++ location of each and the end of the log. It exits with 0 when
makesucceeds, else 1. Thej2c_buildMCP tool does the same (not
destructive, closed-world: it never runs Maven or Gradle; progress per
compiled file, and cancellation stopsmakeand its compilers),
j2c_translate
takeslineDirectives, and VS Code adds J2C: Build Translated C++
(diagnostics in the Problems panel) and thej2c.lineDirectivesand
j2c.build.*settings. JSON schema version 4.j2c test --output <directory> <inputs>translates the inputs with
--testsand--line-directives(and--runtimewhen given), builds the
GoogleTest binary (make tests, run asj2c buildrunsmake; a failed
build reports its errors at their Java lines), runs it with a JSON report,
and reports each test at its Java class, method, file and line (an
inherited test at its declaration in the superclass): status (passed,
failed,skipped,disabled, ornotRunwhen the test process
crashed or timed out first), JUnit's failure message and the exception
class, time, and the C++ test name, with counts and the binary's exit
code.--filtertakes test classes,Class.method,Class#methodor
method names and becomes--gtest_filter;--timeout <seconds>(default
300, 0 for none) stops the test process and everything it started. It
exits with 0 when no test failed, else 1. Thej2c_testMCP tool does the
same (filter,timeout,runtime,jobs; never runs Maven or Gradle;
progress per compiled file and per finished test, and cancellation stops
makeor the tests), and thej2c-migrateprompt calls it withtests.
JSON schema version 5.j2c run --output <directory> <inputs> [-- <arguments>]translates a
program against the J2C runtime (--runtime, defaultbundled) with
--line-directives, builds its main class (make bin/<Class>; a failed
build reports its errors at their Java lines), runs it with the arguments
and--stdin <file>in a fresh working directory, compiles the same Java
files with the running JDK'sjavacand runs the class on the JVM (in
WSL when the build ran there:--java,J2C_WSL_JAVAor WSL'sjava),
and compares standard output, standard error without stack frames, and
the exit code. The result has each run's exit code, signal, duration and
output (up to--output-limitcharacters per stream, default 20000), the
first differing line of each stream, andstatus(ok,differs,
failed,crashed,timeoutorbuild-failed).--mainchooses among
several main classes,--no-compareskips the JVM, and--timeout
(default 60 s, 0 for none) stops each run and everything it started. It
exits with 0 when the status isok, else 1. Thej2c_runMCP tool does
the same (main,args,stdin,compare,timeout,outputLimit,
runtime,jobs; never runs Maven or Gradle; progress per compiled file
and run, and cancellation stopsmakeor the program), and the
j2c-migrateprompt calls it.
Java language support
- Lambda expressions and method references in every form, including generic
functional methods and intersection-cast targets. Each is rewritten into the
equivalent anonymous class. - Pattern-matching
instanceofin any expression position, with Java's flow
scoping. - Switch expressions and statements in arrow and colon form on primitive,
boxed, enum, and string selectors. - Records, including records with object, array, and nested-type components,
and sealed hierarchies of records. - Text blocks, try-with-resources (including
try (existingResource)), and
default, static, and private interface methods. - Sealed and non-sealed declarations become ordinary C++ inheritance with
advisory permits comments;--sealed-types rejectrejects them instead.
Java semantics in generated C++
- Integer division and remainder by zero throw
ArithmeticException,
MIN_VALUE / -1wraps, shift counts are masked, floating-point to integer
casts saturate, and generated Makefiles build with-fwrapv. - Array accesses throw
ArrayIndexOutOfBoundsExceptionwith the JVM's
message, and negative array sizesNegativeArraySizeException. Define
J2C_NO_BOUNDS_CHECKS(CFLAGS=-DJ2C_NO_BOUNDS_CHECKS make) to index
without checks. - Generated constructors no longer bind a reference through a null pointer.
- Effectively final variables are captured by anonymous classes and lambdas,
not only explicitlyfinalones.
Fixes to the original translator
- Moving to the Java 17 AST no longer crashes on array class literals, and
enumswitchlabels are no longer emitted asMISSING. - Arrow-form switch statements no longer return from the enclosing method or
fall through. x %= yon floating-point values computedfmod(x, x).- Casts to intersection types produced an invalid include and are now checked
per component. - JDK library stubs now match their headers: default and static interface
methods get definitions, and inherited default methods forward to their
interface, so programs usingjava.utilcollections compile and link. CloneableandSerializableheaders needed byObjectArray.hppare
always generated.- Rethrowing a caught exception (
throw t) throws its dynamic type, so outer
handlers for its class match, andthrow nullraises
NullPointerException. - A
close()that throws in try-with-resources no longer terminates the
program: its exception propagates, or is added to the suppressed list of
the body's exception. Afinallyblock may throw on a normal exit. - Dependency headers are no longer dropped when two unrelated generic types
compare equal in JDT (TypeDescriptor.OfFieldandAbstractList). getClass().getName()returns binary names, as on the JVM:Outer$Inner,
Outer$1for anonymous andOuter$1Localfor local classes, and[Ior
[LOuter$Inner;for arrays, which were allTODO[]. The
uncaught-exception printout names nested exceptions the same way.Iface.super.m()calls the named interface's default method instead of
the superclass'sm. Inside a lambda or method reference it is reported
asJ2C-LAMBDA-INTERFACE-SUPER.- A local class created from an anonymous class, a lambda or another local
class, or extended by one, gets the variables it captures; generic local
classes get their outer instance. - Numbers before the first string in a concatenation are added, as in Java:
1 + 2 + " ints"is3 ints, not12 ints. - A default method overriding another default replaces it: it was left out
of the header as "already declared", so callers ran the base's body. - A class whose generic superclass implements an interface method with
another erasure is no longer abstract in C++, as inSpliterator.OfInt
implementations that extend a genericOfPrimitiveclass. - Java names that are C library macros,
errnoand the errno codes such as
ENOENT, are renamed with a trailing_like C++ keywords, so they no
longer expand inside generated code. - Sources are parsed in batches with one shared compiler environment: faster,
and dependency headers that per-file parsing omitted are generated.
Quality checks and licensing
- CLI and extension integration suites run during packaging; CI compiles the
generated C++ for every Java 17 fixture and runs native smoke tests under
UndefinedBehaviorSanitizer. test/probesandnpm run probereport which Java constructs translate and
compile, to find and track translation gaps.- The CLI JAR and VSIX ship a runtime dependency inventory, the full EPL-2.0
and Apache-2.0 texts, and each dependency's original notice files.
Known limitations
This preview is not a complete Java 17 translator. Generated programs need a
C++ implementation of the Java classes they use: JDK classes are generated as
stubs, so output compiles and links but does not run until a runtime is
provided. Objects are never freed unless the program is built with
make J2C_GC=boehm. Lambdas that
use this inside an anonymous class, and instanceof patterns in field
initializers, are reported instead of translated. Sealed restrictions are not
enforced by C++. The smoke tests cover the listed fixtures and do not
establish broad compatibility or semantic parity.
Requirements
- Java 11 or newer to run the CLI and the VS Code extension.
- A C++20 compiler for generated Makefiles and C++ output.
- VS Code 1.85 or newer for the extension.
Assets
j2c-cli-0.6.0.jar: standalone CLI (java -jar j2c-cli-0.6.0.jar --help).j2c-0.6.0.vsix: VS Code extension with the same CLI bundled
(code --install-extension j2c-0.6.0.vsix).juangp3-j2c-mcp-0.6.0.tgz: the npm package of the MCP server
(npx @juangp3/j2c-mcp), with the same CLI.SHA256SUMS: verify downloads withsha256sum -c SHA256SUMS.
Third-party license notices are in THIRD-PARTY-NOTICES.txt and
licenses/ in the source tree, inside the CLI JAR under
META-INF/j2c-licenses/, and at the root of the VSIX.