Ironwood is an ahead-of-time compiled, object-oriented language for high-performance native applications. It preserves the familiar Java syntax, object model, and everyday APIs that Java developers already know, while producing optimized native executables with no JVM, JIT, or garbage collector. Ironwood is designed as a simpler, safer alternative to C++ without raw pointers, borrow syntax, or an unfamiliar ownership-driven programming model.

“Java gained its simplicity from the systematic removal of features from its predecessors.”

James Gosling and Henry McGilton, The Java Language Environment, 1995 (Source: Oracle)

“My original goal was to build a C++ compiler that didn't have these problems.”

James Gosling, Interview with Bill Venners, 1999 (Source: Artima)

-

Performance: Compile directly to native executables with LLVM. Closed-world compilation lets the compiler optimize across the whole program, with no JVM, JIT, or garbage collector in the generated application. Profile-guided optimization and a compiler-owned @Inlinedirective are on the roadmap so Ironwood keeps pushing the envelope for maximum performance.

-

Safety: Reclaim memory explicitly with compiler-proven freewhen you need to, or reuse objects through the nativeironwood.poolstandard library. The compiler rejects reclamation it cannot prove safe, preventing dangling references, double frees, and use after free. Allocations remain valid until an acceptedfreereclaims them or the process ends.

-

Familiarity: Use the Java programming model you already know: classes and interfaces, inheritance and polymorphism, Java-width primitives, generics, exceptions, packages, and ordinary nullable references. Ironwood is designed to make Java developers feel at home from the first line of code.

Both Ironwood and GraalVM Native Image produce closed-world, ahead-of-time compiled native executables, but they start from different places. GraalVM Native Image compiles existing Java bytecode and carries the JVM machinery needed to preserve Java semantics into the executable. Ironwood is a separate language designed for native compilation from the ground up, with Java-familiar syntax, objects, and APIs, but no JVM, JIT, Java bytecode, or garbage collector in the generated application.

GraalVM makes Java applications native. Ironwood makes native development feel like Java.

The step-by-step instructions to download, install and run are here.

If this looks familiar, that is intentional. Ironwood keeps Java's package and

class structure while compiling your application into a native executable.

Each top-level class lives in its own .iron file beneath the standard

src/main/ironwood source root:

hello/

└── src/

└── main/

└── ironwood/

└── org/

└── ironwood/

└── hello/

├── Chatter.iron

└── Hello.iron

package org.ironwood.hello;

public class Hello {

public static void main(String[] args) {

Chatter chatter = new Chatter();

System.out.println("Hello " + chatter.getWord() + "!");

}

}package org.ironwood.hello;

import ironwood.util.Random;

class Chatter {

private static final String[] WORDS = { "World", "Ironwood", "Developers" };

private final Random rand = new Random();

String getWord() {

int index = this.rand.nextInt(WORDS.length);

return WORDS[index];

}

}With ironwoodc on your PATH, run these commands from the hello directory:

# Compile Hello.iron; Chatter.iron is found and compiled automatically.

ironwoodc -sourcepath src/main/ironwood -d target/classes \

src/main/ironwood/org/ironwood/hello/Hello.iron

# Link the closed-world application into an optimized native executable.

ironwoodc --link -cp target/classes \

--main-class org.ironwood.hello.Hello -o target/hello -O3

# Run the native executable.

./target/helloEach run prints one of the three greetings:

Hello Ironwood!

The resulting target/hello is a native executable. It does not need the IDK (Ironwood Development Kit), Java, a JVM, a JIT, or LLVM when deployed.

Ironwood does not have a garbage collector so memory is never reclaimed automatically. For our short Hello World program that wouldn't be a problem but let's change it to show how Ironwood handles memory explicitly.

The previous Hello World example will compile with warnings because

chatterand the concatenated String are not freed. The default compiler option is--unfreed=warn. You can use--unfreed=offto silence these warnings or the stricter--unfreed=errorto fail compilation with an error. To suppress the diagnostic for a particular allocation, you can place@SuppressUnfreedbefore its local variable declaration, even when using--unfreed=error. Suppression never disables memory-safety checks. Click here for more details.

package org.ironwood.hello;

public class Hello {

public static void main(String[] args) {

Chatter chatter = new Chatter();

String text = "Hello " + chatter.getWord() + "!";

System.out.println(text);

free text; // destroy object and reclaim the memory

free chatter; // destroy object and reclaim the memory

// System.out.println(chatter); // use after free NEVER compiles

}

}package org.ironwood.hello;

import ironwood.util.Random;

class Chatter {

private static final String[] WORDS = { "World", "Ironwood", "Developers" };

private final Random rand = new Random();

String getWord() {

int index = this.rand.nextInt(WORDS.length);

return WORDS[index];

}

destructor {

free rand; // destroy object and reclaim the memory

}

}The cleanup above demonstrates how the same code can manage memory deterministically in a long-running native application. When execution reaches free chatter;, the compiler first proves that the object cannot be observed through another live reference. The Chatter destructor then runs and reclaims its privately owned Random instance before

the Chatter object itself is reclaimed. A destructor does not run merely because an object becomes unreachable. It runs only as part of a free operation that the compiler has accepted and proven safe.

If the compiler cannot prove that either reclamation is safe, compilation fails. There is no unsafe fallback, and a successfully freed reference cannot be used again.

Ironwood does not support general ownership transfer for an existing allocation. Once ownership is established, passing or storing a reference elsewhere does not give ownership to the recipient. The allocation may remain until process termination. If it is to be reclaimed earlier, the original owner remains responsible for calling free after all observable borrows and aliases have ended. If the compiler cannot prove that all observable borrows and aliases have ended, it rejects the free with a compilation error.

Yes: no C/C++ dangling pointers or unpredictable references. The compiler won't allow it.

defer runs cleanup when execution leaves its enclosing block (such as a method or loop body), including on return or exception, in reverse declaration order.

public int readFirstByte(String path) throws IOException {

FileInputStream fis = new FileInputStream(path);

defer free fis; // Runs second: reclaim the object.

defer fis.close(); // Runs first: close the file.

return fis.read(); // Cleanup runs before the method returns.

}Any method that returns void can be deferred. It works naturally with object pooling:

int i = 0;

while (i < 3) {

StringBuilder sb = pool.get(); // Borrow from an existing pool.

defer pool.release(sb); // Return it at the end of this iteration.

sb.setLength(0);

sb.append("Message ").append(i);

System.out.println(sb);

i++;

}Use defer instead of a try/finally block whose only purpose is to free an allocation or run cleanup:

// Without defer: borrow from an existing pool.

public String message(String name) {

StringBuilder sb = this.pool.get();

try {

sb.setLength(0);

sb.append("Hello ").append(name);

return sb.toString(); // caller will own this allocation

} finally {

this.pool.release(sb);

}

}// With defer: the same cleanup, without the try/finally nesting.

public String message(String name) {

StringBuilder sb = this.pool.get();

defer this.pool.release(sb);

sb.setLength(0);

sb.append("Hello ").append(name);

return sb.toString(); // caller will own this allocation

}Ironwood ships with a native object pool (ironwood.pool), which is paramount for hot paths without allocation. You can click here for more info.

Ironwood strives to provide a standard library as close as possible to the JDK, if not identical. The exception is the absence of collections in favor of the highly optimized single-threaded data-structures from ironwood.ds. For example, you can use an ArrayList with the code below.

import ironwood.ds.ArrayList;

import ironwood.util.Iterator;

public class ListExample {

public static void main(String[] args) {

ArrayList<String> list = new ArrayList<>();

list.add("Hi1");

list.add("Hi2");

Iterator<String> iter = list.iterator();

while(iter.hasNext()) {

System.out.println(iter.next());

}

}

}For the IronDocs of the latest Ironwood Standard Library, you can click here.

Ironwood's ironwood.net package provides a native subset of java.net: blocking TCP

clients and servers (Socket, ServerSocket), IPv4/IPv6 addresses, DNS, network

interfaces, and SOCKS4/5 and HTTP CONNECT proxies. It also supports TLS 1.2/1.3

clients through an optional OpenSSL dependency and includes the

wget HTTP/HTTPS downloader tool.

See the TCP client/server guide for more details and a simple example.

Equivalent single-threaded OrderBook implementations on Linux. Throughput measures 80 million operations; latency measures batches of 8,000 operations.

See benchmark details for the workload, environment, and full results.

Inspired by JUnit 5, Ironwood ships with a test framework for automated tests. You can see an example here.

Like JavaDocs, Ironwood has IronDocs, which generates documentation in the Markdown format. You can click here for more info.

You can package compiled Ironwood classes into one .ironjar file. This is useful for distributing a library or reusing it in another Ironwood project. You can click here for more info.

Compile Ironwood code to a native library, and call it from a regular Java application as if it were an ordinary Java dependency. Total transparency with no handwritten bridge code, native declarations, or manual library loading will be required. It is like Java calling Java. For more details click here.

For the full list of Java features that Ironwood preserves, the mechanisms it adapts for native development, and the complexity it deliberately leaves behind, check this document.

Questions, use cases, design feedback, and contributions are welcome. Early feedback will help guide Ironwood’s direction.

- Use GitHub Discussions for questions, ideas, and use cases.

- Open a GitHub issue for bugs and concrete proposals.

- See CONTRIBUTING.md before submitting a pull request.

- For private feedback or other inquiries, email contact@ironwood-lang.org.

Ironwood is primarily distributed under the terms of both the MIT License and the Apache License (Version 2.0), with portions covered by other licenses.

See LICENSE, LICENSE-APACHE, LICENSE-MIT, and THIRD_PARTY_NOTICES.md for details.