← All blog posts

A Small Guide to J%

August 27, 2026 · Programming

J% is an extension of the Java language where a program written in another language — an SQL query, a regular expression, a JSON document — is a type, not a string. You declare it with one new keyword, external, the compiler validates it while your project builds, and what comes out the other side is an ordinary Java class you instantiate with new.

The idea it argues against is the string literal. When you embed one language inside another by quoting it, the guest language disappears from every tool that reads the host: the compiler cannot check it, the type system cannot connect the values you splice into it with the code around it, and every mistake waits for runtime to introduce itself. J% keeps the guest language visible, and moves those failures to compile time.

It began as my PhD work in 2015 and was rebuilt in 2026 as jmodv3 — a source-to-source compiler: no javac plugin, no bytecode tricks. Four DSL modules ship with it: regular expressions, SQL, JSON, and GetSet (typed data carriers).

Getting it running

You need a JDK 17 or newer — nothing else. The repository carries a bootstrap script that downloads Maven by itself:

git clone https://github.com/bkarak/jmodv3
cd jmodv3
./mvnw package

That produces target/jmod-0.2.0.jar, which is both the compiler and the runtime library.

Your first external

An external type lives in a .jmod file — package, imports, and a declaration whose body is not Java. This is a regular expression matching an IP address:

package examples.simpleregex;

import org.jmod.dsl.regex.Regex;
import org.jmod.dsl.regex.RegexConfiguration;

public external IpAddress extends Regex<RegexConfiguration> {
([0-9]{1,3}\\.){3}[0-9]{1,3}
}

The base type it extends (Regex) selects the module; the type argument names a configuration (more on that below). Everything between the braces is the pattern. One convention to know: the generator embeds the body inside a Java string literal, so backslashes are written exactly as you would write them in Java quotes — \\. for a literal dot.

Use it from plain Java as if the class already existed:

IpAddress ip = new IpAddress();
System.out.println(ip.matches("127.0.0.1"));

Compile the directory, then run with the jar on the classpath — the generated classes extend runtime types that ship inside it:

java -jar target/jmod-0.2.0.jar -i examples/simpleregex -o out
java -cp out:target/jmod-0.2.0.jar examples.simpleregex.Main

The compiler validates the pattern, generates IpAddress.java, copies your ordinary .java files through unchanged, and runs javac over the result. -n skips the javac pass, -l lists the installed modules, --help has the rest.

Passing values across

When Java values must flow into the DSL, you declare a splice: #[name]<JavaType>. This is the only extension J% makes to any guest language.

package examples.simplesql;

import org.jmod.dsl.sql.SQLQuery;

public external SelectExample extends SQLQuery<SimpleConf> {
select * from sqlexample where sqle_primary = #[prim]<int>
}

Each distinct name becomes a constructor parameter of that Java type — here, SelectExample(int prim). Omit the type and it defaults to String. An array type such as int[] becomes an SQL IN-list that expands at runtime. A splice is not string interpolation: the SQL module turns it into a bound JDBC parameter, so the query text and your values never meet as strings.

Switching on the deeper checks

By default a module checks syntax and nothing more. The configuration type — a plain Java class of constants — turns on the rest:

package examples.simplesql;

import org.jmod.dsl.sql.SQLConfiguration;

public class SimpleConf extends SQLConfiguration {
    public boolean SQLMOD_NS_AWARE = true;
    public String SQLMOD_NS_URI = "file://./schema.sql";
}

This one points the SQL module at a DDL file, and every query is now checked against the schema: table names, column names, and whether each splice's Java type fits the column it binds to. Keep the values plain literals — the compiler reads these classes, it does not execute them — and relative file://./… paths resolve against the configuration's own directory. The SQL module can also probe a live database over JDBC (SQLMOD_LIVE_TEST); point that only at a disposable development database, because the statement genuinely executes. The JSON module works the same way with a schema document (JSONMOD_SCHEMA_URI, drafts 4 through 2020-12).

When it says no

The point of all this is the errors, so here are two real ones, exactly as the compiler prints them. A malformed pattern:

BadRegex.jmod: invalid regular expression: Unclosed character class near index 3

And a splice whose Java type does not fit the column — the check that most justifies the exercise, since nothing about the Java alone is wrong:

SelectExample.jmod: type incompatibility: #[prim]<String> is not compatible
with column sqle_primary (INT)

In both cases the build stops. These are compilation failures, not warnings.

Where it stops

J% is a research prototype, honestly labelled. One external type per file; the body ends at the matching brace, so brace-unbalanced DSL text cannot be expressed; #[ is reserved for splices; and there is no IDE story — the generated API is documented per module, not completed in your editor.

Reading further

The J% page has a page per module — what each checks, generates, and accepts as configuration. The design behind all of it is in the revised chapter of my thesis, updated in 2026 with the module contract and the safety argument spelled out. The code is at bkarak/jmodv3 — and a v4, with a grammar tuned for 2026 and beyond, is on the drawing board.