GPULlama3.java Brings GPU-Accelerated LLM Inference to Pure Java

MMS Founder
MMS A N M Bazlur Rahman

The University of Manchester’s Beehive Lab has released GPULlama3.java, marking the first Java-native implementation of Llama3 with automatic GPU acceleration. This project leverages TornadoVM to enable GPU-accelerated large language model inference without requiring developers to write CUDA or native code, potentially transforming how Java developers approach AI applications in enterprise environments.

At the heart of GPULlama3.java lies TornadoVM, an innovative heterogeneous programming framework that extends OpenJDK and GraalVM to automatically accelerate Java programs on GPUs, FPGAs, and multi-core CPUs. Unlike traditional GPU programming approaches that require developers to rewrite code in low-level languages like CUDA or OpenCL manually, TornadoVM enables GPU acceleration while keeping all code in pure Java.

According to the TornadoVM documentation, the system works by extending the Graal JIT compiler with specialized backends that translate Java bytecode to GPU-compatible code at runtime. When a method is marked for acceleration using annotations like @Parallel, TornadoVM’s compilation pipeline converts standard Java bytecode through Graal’s Intermediate Representation, applies GPU-specific optimizations, and generates target-specific code—whether that’s OpenCL C for cross-platform compatibility, PTX assembly for NVIDIA GPUs, or SPIR-V binary for Intel graphics.

// TornadoVM Task-Graph API example from documentation
TaskGraph taskGraph = new TaskGraph("computation")
.transferToDevice(DataTransferMode.FIRST_EXECUTION, data)
.task("process", MyClass::compute, input, output)
.transferToHost(DataTransferMode.EVERY_EXECUTION, output);

TornadoExecutionPlan executor = new TornadoExecutionPlan(taskGraph.snapshot());
executor.execute();

The TornadoVM programming guide demonstrates how developers can utilize hardware-agnostic APIs, enabling the same Java source code to run identically on various hardware accelerators. The TornadoVM runtime handles all device-specific optimizations, memory management, and data transfers automatically.

According to the GPULlama3.java repository, the project supports three primary backends, enabling execution across diverse hardware:

  • NVIDIA GPUs: Full support through both OpenCL and PTX backends
  • Intel GPUs: Including Arc discrete graphics and integrated HD Graphics through the OpenCL backend
  • Apple Silicon: M1/M2/M3 support through OpenCL (though Apple has deprecated OpenCL in favour of Metal)

The repository indicates that configuration is handled through command-line flags:

# Run with GPU acceleration (from project README)
./llama-tornado --gpu --verbose-init --opencl --model beehive-llama-3.2-1b-instruct-fp16.gguf --prompt "Explain the benefits of GPU acceleration."

The GPULlama3.java implementation leverages modern Java features as documented in the repository:

  • Java 21+ requirement for Vector API and Foreign Memory API support
  • GGUF format support for single-file model deployment
  • Quantization support for Q4_0 and Q8_0 formats to reduce memory requirements

The project builds upon Mukel’s original Llama3.java, adding GPU acceleration capabilities through TornadoVM integration.

GPULlama3.java joins other Java LLM projects, including:

  • JLama: A modern LLM inference engine for Java with distributed capabilities
  • Llama3.java: The original pure Java implementation focusing on CPU optimization

As noted in Quarkus’s blog on Java LLMs, the Java ecosystem is expanding its AI/ML capabilities, enabling developers to build LLM-powered applications without leaving the Java platform.

TornadoVM originated from research at the University of Manchester, aiming to make heterogeneous computing accessible to Java developers. The framework has been in development since 2013 and continues to progress with new backend support and optimizations.

GPULlama3.java is currently in beta, with ongoing performance optimization and benchmark collection. The performance on Apple Silicon remains suboptimal due to the deprecation of OpenCL. The TornadoVM team is developing a Metal backend to enhance support for Apple Silicon, optimizing transformer operations and broadening model architecture compatibility.

GPULlama3.java represents a significant advancement in bringing GPU-accelerated large language model (LLM) inference to the Java ecosystem. By leveraging TornadoVM, the project demonstrates that Java developers can utilize GPU acceleration without leaving their familiar programming environment. While performance optimization continues and the project remains in active development, it opens up new possibilities for Java-based AI applications in enterprise settings, where Java’s strengths in security, scalability, and maintainability are highly valued.

For developers interested in exploring GPU-accelerated LLM inference in Java, the project is open source and accessible on GitHub, complete with documentation and examples to help get started.

About the Author

Subscribe for MMS Newsletter

By signing up, you will receive updates about our latest information.

  • This field is for validation purposes and should be left unchanged.

Spring AI 1.0 Released, Streamlines AI Application Development with Broad Model Support

MMS Founder
MMS A N M Bazlur Rahman

The Spring team has announced the general availability of Spring AI 1.0, a framework designed to simplify the development of AI-driven applications within the Java and Spring ecosystem. This release, the result of over two years of development and eight milestone iterations, delivers a stable API. It integrates with a wide range of AI models for chat, image generation, and transcription. Key features include portable service abstractions, support for Retrieval Augmented Generation (RAG) via vector databases, and tools for function calling. Spring AI 1.0 enables developers to build scalable, production-ready AI applications by aligning with established Spring patterns and the broader Spring ecosystem.

Spring AI provides out-of-the-box support for numerous AI models and providers. The framework integrates with major generative AI providers, including OpenAI, Anthropic, Microsoft Azure OpenAI, Amazon Bedrock, and Google Vertex AI, through a unified API layer. It supports various model types across modalities, including chat completion, embedding, image generation, audio transcription, text-to-speech synthesis, and content moderation. This enables developers to integrate capabilities such as GPT-based chatbots, image creation, or speech recognition into Spring applications.

The framework offers portable service abstractions, decoupling application code from specific AI providers. Its API facilitates switching between model providers (e.g., from OpenAI to Anthropic) with minimal code changes, while retaining access to model-specific features. Spring AI supports structured outputs by mapping AI model responses to Plain Old Java Objects (POJOs) for type-safe processing. For Retrieval Augmented Generation (RAG), Spring AI integrates with various vector databases, including Cassandra, PostgreSQL/PGVector, MongoDB Atlas, Milvus, Pinecone, and Redis, through a consistent Vector Store API, enabling applications to ground LLM responses in enterprise data. The framework also includes support for tools and function calling APIs, allowing AI models to invoke functions or external tools in a standardized manner to address use cases like “Q&A over your documentation” or “chat with your data.”

Spring AI 1.0 includes support for the Model Context Protocol (MCP), an emerging open standard for structured, language-agnostic interaction between AI models (particularly LLMs) and external tools or resources. The Spring team has contributed its MCP implementation to ModelContextProtocol.io, where it serves as an official Java SDK for MCP services. This reflects Spring AI’s focus on open standards and interoperability.

To facilitate MCP integration, Spring AI provides dedicated client and server Spring Boot starters, enabling models to interact with tools like this example weather service:

import org.springframework.ai.tool.annotation.Tool;
import org.springframework.stereotype.Component;

@Component
public class WeatherTool {

   @Tool(name = "getWeather", description = "Returns weather for a given city")
   public String getWeather(String city) {
       return "The weather in " + city + " is 21°C and sunny.";
   }
}

These starters are categorized as follows:

  • Client Starters: spring-ai-starter-mcp-client (providing core STDIO and HTTP-based SSE support) and spring-ai-starter-mcp-client-webflux (offering WebFlux-based SSE transport for reactive applications).
  • Server Starters: spring-ai-starter-mcp-server (for core STDIO transport support), spring-ai-starter-mcp-server-webmvc (for Spring MVC-based SSE transport in servlet applications), and spring-ai-starter-mcp-server-webflux (for WebFlux-based SSE transport in reactive applications).

Developers can begin new Spring AI 1.0 projects using Spring Initializr, which preconfigures necessary dependencies. Including the desired Spring AI starter on the classpath allows Spring Boot to auto-configure the required clients or services.

An example of a simple chat controller is as follows:

import org.springframework.ai.chat.client.ChatClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class ChatController {

   private final ChatClient chatClient;

   public ChatController(ChatClient.Builder chatClientBuilder) {
       this.chatClient = chatClientBuilder.build();
   }

   @GetMapping("/ask")
   public String ask(@RequestParam String question) {
       return chatClient.prompt()
               .user(question)
               .call()
               .content();
   }
}

At a minimum, the following key-value in application.properties is necessary to run the above example.

spring.ai.openai.api-key=YOUR_API_KEY
spring.ai.openai.chat.model=gpt-4

Spring AI introduces higher-level APIs for common AI application patterns. A fluent ChatClient API offers a type-safe builder for chat model interactions. Additionally, an Advisors API encapsulates recurring generative AI patterns such as retrieval augmentation, conversational memory, and question-answering workflows. For instance, a RAG flow can be implemented by combining ChatClient with QuestionAnswerAdvisor:

ChatResponse response = ChatClient.builder(chatModel)
        .build()
        .prompt()
        .advisors(new QuestionAnswerAdvisor(vectorStore))
        .user(userText)
        .call()
        .chatResponse();

In this example, QuestionAnswerAdvisor performs a similarity search in the VectorStore, appends relevant context to the user prompt, and forwards the enriched input to the model. An optional SearchRequest with an SQL-like filter can constrain document searches.

The release incorporates Micrometer for observability, allowing developers to monitor AI-driven applications. These integrations facilitate embedding AI capabilities into Spring-based projects for various applications, including real-time chat, image processing, and transcription services.

For more information, developers can explore the Spring AI project page or begin building with Spring AI at start.spring.io. This release provides Java developers with a solution for integrating AI capabilities, offering features for scalability and alignment with idiomatic Spring development.

About the Author

Subscribe for MMS Newsletter

By signing up, you will receive updates about our latest information.

  • This field is for validation purposes and should be left unchanged.

Netflix Adopts Virtual Threads: A Case Study on Performance and Pitfalls

MMS Founder
MMS A N M Bazlur Rahman

Netflix, a long-time Java adopter, recently upgraded to Java 21. They are now harnessing new features such as generational ZGC, introduced in JEP 439, and virtual threads, introduced in JEP 444, to improve performance across its extensive microservices fleet. While virtual threads, designed for high-throughput concurrent applications, showed early promise, they also brought unique challenges in real-world scenarios.

In a recent post on the Netflix Tech Blog, the JVM Ecosystem team shared insights from their experience with virtual threads, particularly an issue where services experienced timeouts and hung instances. The issue was related to the interaction of virtual threads with blocking operations and OS thread availability, resulting in a deadlock-like situation in their SpringBoot-based applications.

Netflix engineers observed intermittent timeouts and non-responsive instances in services running on Java 21 with SpringBoot 3 and embedded Tomcat. Despite the JVM instances remaining active, they stopped serving traffic, which was characterized by a significant increase in sockets stuck in a closeWait state. This state occurs when the remote side closes a TCP connection, but the local side has not yet closed its end, leaving the socket in a waiting state. More about this can be found in RFC 793 in the terminology section.

Initial diagnostics suggested that virtual threads were implicated in the issue, although they didn’t appear in traditional thread dumps. Using jcmd Thread.dump_to_file, the team found thousands of “blank” virtual threads, indicating threads created but not yet running. The issue was traced to Tomcat’s request handling, where new virtual threads were created but couldn’t be scheduled due to the unavailability of OS threads.

#119821 "" virtual
#119820 "" virtual
#119823 "" virtual
#120847 "" virtual
#119822 "" virtual
...

The analysis revealed that Tomcat’s virtual thread executor was creating threads for each request, but these threads were stuck waiting for a lock. Specifically, the threads were pinned to OS threads due to blocking operations within synchronized blocks, exacerbated by the limited number of available OS threads in the ForkJoinPool.

The problem resulted from a classic deadlock scenario in which virtual threads could not proceed because the required lock was held by other virtual threads pinned to all available OS threads. This prevented new virtual threads from being scheduled, effectively stalling the application.

To resolve the issue, Netflix’s JVM Ecosystem team used a heap dump to inspect the lock’s state and confirmed that no thread owned it, yet the threads waiting for it were unable to proceed. This was a transient state that should have resolved but was instead causing a deadlock-like situation.

The team identified the root cause and developed a reproducible test case to prevent similar issues in the future. While virtual threads in Java 21 have shown potential for improving performance by reducing overhead, this case highlights the importance of understanding their interaction with existing threading models and locking mechanisms.

Adding to Netflix’s findings, a recent case study on InfoQ also delves into the practical challenges and benefits of virtual threads, particularly in scenarios involving heavy concurrent workloads. This study underscores the need for careful consideration and testing when integrating virtual threads into production systems, as even small architectural details can lead to significant performance impacts.

In addition to virtual threads, Netflix’s adoption of generational ZGC has also played a crucial role in optimizing its systems, as mentioned in one of the recent articles. ZGC, with its ability to maintain low pause times even as heap sizes grow, has significantly improved Netflix’s application performance by reducing garbage collection overhead and enhancing responsiveness. More on generational ZGC can be found in this InfoQ news item.

Netflix also has a robust alert system, leveraging its Atlas Streaming Eval platform, which was vital in identifying and diagnosing these issues. The system, designed for improved real-time monitoring and alerting, enabled the team to catch instances in a problematic state and provided critical data for retroactive analysis.

Despite the challenges, Netflix is optimistic about the future of virtual threads and anticipates further improvements in upcoming Java releases, particularly in addressing the integration challenges with locking primitives. This case study is a valuable example for performance engineers and developers as they explore virtual threads in their applications.

About the Author

Subscribe for MMS Newsletter

By signing up, you will receive updates about our latest information.

  • This field is for validation purposes and should be left unchanged.

JEP 472: Prepare to Restrict the Use of JNI in JDK 24

MMS Founder
MMS A N M Bazlur Rahman

JEP 472, Prepare to Restrict the Use of JNI, has been promoted to Proposed to Target. This JEP proposes issuing warnings for the use of the Java Native Interface (JNI) and adjusting the Foreign Function & Memory (FFM) API to issue consistent warnings. This prepares developers for a future release that ensures integrity by default by uniformly restricting JNI and the FFM API. This proposal builds on the long-term efforts to enhance the security and performance of the Java Platform, following the examples of JEP 454, Foreign Function & Memory API; JEP 471, Deprecate the Memory-Access Methods of sun.misc.Unsafe; and JEP 451, Prepare to Restrict the Dynamic Loading of Agents. The main goal is to ensure that any use of JNI and the FFM API in future releases will require explicit approval from the application’s developer at startup.

The JNI, introduced in JDK 1.1, has been a standard way for Java code to interoperate with native code. However, any interaction between Java code and native code can compromise the integrity of applications and the Java Platform itself. For example, calling native code can lead to unpredictable issues, including JVM crashes, that cannot be handled by the Java runtime or caught with exceptions. These issues can disrupt the normal operation of the Java Platform and the applications running on it.

For example, consider the following C function that takes a long value passed from Java code and treats it as an address in memory, storing a value at that address:

void Java_pkg_C_setPointerToThree__J(jlong ptr) {

    *(int*)ptr = 3;  // Potential memory corruption
}

Additionally, exchanging data through direct byte buffers, which are not managed by the JVM’s garbage collector, can expose Java code to invalid memory regions, leading to undefined behavior. Furthermore, native code can bypass JVM access checks and modify fields or call methods, potentially violating the integrity of Java code, such as by mutating String objects.

JEP 472 proposes a staged approach to restrict JNI usage to mitigate these risks. Initially, warnings will be issued for operations that load and link native libraries uniformly in both JNI and the FFM API.

Developers can avoid warnings by enabling native access for specific Java code at startup. The command-line option --enable-native-access=ALL-UNNAMED enables native access to all codes on the classpath. For specific modules, developers can pass a comma-separated list of module names, such as java --enable-native-access=M1,M2,....

Code that calls native methods declared in another module does not need to have native access enabled. However, code that calls System::loadLibrary, System::load, Runtime::loadLibrary, or Runtime::load, or declares a native method is affected by native access restrictions. When a restricted method is called from a module for which native access is not enabled, the JVM runs the method but, by default, issues a warning that identifies the caller:

WARNING: A restricted method in java.lang.System has been called
WARNING: System::load has been called by com.foo.Server in module com.foo (file:/path/to/com.foo.jar)
WARNING: Use --enable-native-access=com.foo to avoid a warning for callers in this module
WARNING: Restricted methods will be blocked in a future release unless native access is enabled

Developers can avoid these warnings and future restrictions by explicitly enabling native access for specific code at startup using the --enable-native-access command-line option. This option can be applied globally or selectively to specific modules on the module path.

The impact of native access restrictions can be controlled using the --illegal-native-access option. Its modes include:

  • allow: Allows the restricted operation without warnings.
  • warn: Issues a warning for the first occurrence of illegal native access in a module (default in the upcoming release).
  • deny: Throws an IllegalCallerException for every illegal native access operation (planned default for a future release).

The long-term goal is to transition the default behavior to deny, enforcing stricter security measures by default. This change aligns with broader efforts to achieve integrity across the Java Platform by default, enhancing security and performance.

Developers are encouraged to use the deny mode to proactively identify code that requires native access and make necessary adjustments. Additionally, a new tool, jnativescan, will be introduced to help identify libraries using JNI.

In conclusion, JEP 472 marks a significant step towards a more secure Java Platform. While the transition to stricter JNI restrictions may require some adjustments, the resulting benefits in terms of security and integrity are expected to be substantial. Developers can ensure a smooth transition and contribute to a more robust Java ecosystem by preparing for these changes.

About the Author

Subscribe for MMS Newsletter

By signing up, you will receive updates about our latest information.

  • This field is for validation purposes and should be left unchanged.

JEP 456: Preparing for the Removal of Unsafe Memory-Access Methods

MMS Founder
MMS A N M Bazlur Rahman

JEP 471, Deprecate the Memory-Access Methods in sun.misc.Unsafe for Removal, has been delivered for JDK 23. This JEP proposes to deprecate the memory access methods in the Unsafe class for removal in a future release. These unsupported methods have been superseded by standard APIs, namely, JEP 193, Variable Handles, delivered in JDK 9; and JEP 454, Foreign Function & Memory API, delivered in JDK 22.

The primary goal of this deprecation is to prepare the ecosystem for the eventual removal of sun.misc.Unsafe‘s memory-access methods. By highlighting their usage through compile-time and runtime warnings, developers can identify and migrate to supported replacements. This transition aims to ensure that applications can smoothly adapt to modern JDK releases, enhancing security and performance.

Two standard APIs now provide safe and efficient alternatives to sun.misc.Unsafe. The VarHandle API, delivered in JDK 9 through JEP 193, offers methods to safely manipulate on-heap memory, ensuring operations are performed efficiently and without undefined behaviour. The Foreign Function & Memory API, delivered in JDK 22 through JEP 454, provides safe off-heap memory access methods, often used in conjunction with VarHandle to manage memory inside and outside the JVM heap. These APIs promise no undefined behaviour, long-term stability, and better integration with Java tooling and documentation.

The deprecated sun.misc.Unsafe methods fall into three categories: on-heap, off-heap, and bimodal (methods that can access both on-heap and off-heap memory). The on-heap methods are:

long objectFieldOffset(Field f)
long staticFieldOffset(Field f)
Object staticFieldBase(Field f)
int arrayBaseOffset(Class arrayClass)
int arrayIndexScale(Class arrayClass)

These methods can be replaced by VarHandle and MemorySegment::ofArray with its overloaded methods. For example, consider the following example:

class Foo {

    private static final Unsafe UNSAFE = ...;    // A sun.misc.Unsafe object

    private static final long X_OFFSET;

    static {
        try {
            X_OFFSET = UNSAFE.objectFieldOffset(Foo.class.getDeclaredField("x"));
        } catch (Exception ex) { throw new AssertionError(ex); }
    }

    private int x;

    public boolean tryToDoubleAtomically() {
        int oldValue = x;
        return UNSAFE.compareAndSwapInt(this, X_OFFSET, oldValue, oldValue * 2);
    }
}

This above code can be implemented using VarHandle as follows:

class Foo {

    private static final VarHandle X_VH;

    static {
        try {
            X_VH = MethodHandles.lookup().findVarHandle(Foo.class, "x", int.class);
        } catch (Exception ex) { throw new AssertionError(ex); }
    }

    private int x;

    public boolean tryAtomicallyDoubleX() {
        int oldValue = x;
        return X_VH.compareAndSet(this, oldValue, oldValue * 2);
    }
}

The off-heap methods are primarily as follows:

long allocateMemory(long bytes)
long reallocateMemory(long address, long bytes)
void freeMemory(long address)
void invokeCleaner(java.nio.ByteBuffer directBuffer)
void setMemory(long address, long bytes, byte value)
void copyMemory(long srcAddress, long destAddress, long bytes)
[type] get[Type](long address)
void put[Type](long address, [type] x)

These methods can be replaced by MemorySegment operations. Consider the following example:

class OffHeapIntBuffer {

    private static final Unsafe UNSAFE = ...;

    private static final int ARRAY_BASE = UNSAFE.arrayBaseOffset(int[].class);
    private static final int ARRAY_SCALE = UNSAFE.arrayIndexScale(int[].class);

    private final long size;
    private long bufferPtr;

    public OffHeapIntBuffer(long size) {
        this.size = size;
        this.bufferPtr = UNSAFE.allocateMemory(size * ARRAY_SCALE);
    }

    public void deallocate() {
        if (bufferPtr == 0) return;
        UNSAFE.freeMemory(bufferPtr);
        bufferPtr = 0;
    }

    private boolean checkBounds(long index) {
        if (index = size)
            throw new IndexOutOfBoundsException(index);
        return true;
    }

    public void setVolatile(long index, int value) {
        checkBounds(index);
        UNSAFE.putIntVolatile(null, bufferPtr + ARRAY_SCALE * index, value);
    }

    public void initialize(long start, long n) {
        checkBounds(start);
        checkBounds(start + n-1);
        UNSAFE.setMemory(bufferPtr + start * ARRAY_SCALE, n * ARRAY_SCALE, 0);
    }

    public int[] copyToNewArray(long start, int n) {
        checkBounds(start);
        checkBounds(start + n-1);
        int[] a = new int[n];
        UNSAFE.copyMemory(null, bufferPtr + start * ARRAY_SCALE, a, ARRAY_BASE, n * ARRAY_SCALE);
        return a;
    }

}

This above can be replaced by using the standard Arena and MemorySegment APIs:

class OffHeapIntBuffer {

    private static final VarHandle ELEM_VH = ValueLayout.JAVA_INT.arrayElementVarHandle();

    private final Arena arena;
    private final MemorySegment buffer;

    public OffHeapIntBuffer(long size) {
        this.arena  = Arena.ofShared();
        this.buffer = arena.allocate(ValueLayout.JAVA_INT, size);
    }

    public void deallocate() {
        arena.close();
    }

    public void setVolatile(long index, int value) {
        ELEM_VH.setVolatile(buffer, 0L, index, value);
    }

    public void initialize(long start, long n) {
        buffer.asSlice(ValueLayout.JAVA_INT.byteSize() * start,
                       ValueLayout.JAVA_INT.byteSize() * n)
              .fill((byte) 0);
    }

    public int[] copyToNewArray(long start, int n) {
        return buffer.asSlice(ValueLayout.JAVA_INT.byteSize() * start,
                              ValueLayout.JAVA_INT.byteSize() * n)
                     .toArray(ValueLayout.JAVA_INT);
    }

}

The migration will occur in several phases, each aligned with a separate JDK release. In Phase 1, starting with JDK 23, all memory-access methods will be deprecated, and compile-time warnings will be issued. Phase 2, planned for JDK 25 or earlier, will introduce runtime warnings whenever the deprecated methods are used. Phase 3, scheduled for JDK 26 or later, will escalate the response by throwing exceptions by default when these methods are invoked. Finally, Phases 4 and 5 will remove the deprecated methods, potentially occurring in the same release.

Developers can use the new command-line option --sun-misc-unsafe-memory-access={allow|warn|debug|deny} to manage the deprecation warnings and assess the impact on their applications.

The deprecation of sun.misc.Unsafe memory-access methods are a significant step towards enhancing the integrity and security of the Java Platform. By adopting the VarHandle and Foreign Function & Memory APIs, developers can ensure their applications remain robust and compatible with future JDK releases. The phased approach provides ample time for migration, minimizing disruption while promoting best practices in Java development.

About the Author

Subscribe for MMS Newsletter

By signing up, you will receive updates about our latest information.

  • This field is for validation purposes and should be left unchanged.

JEP 477 Enhances Beginner Experience with Implicitly Declared Classes and Instance Main Methods

MMS Founder
MMS A N M Bazlur Rahman

JEP 477, Implicitly Declared Classes and Instance Main Methods (Third Preview), has been promoted from its Proposed to Target to Targeted status. This JEP proposes to “evolve the Java language so that students can write their first programs without needing to understand language features designed for large programs.” This JEP moves forward the September 2022 blog post, Paving the on-ramp, by Brian Goetz, the Java language architect at Oracle. The latest draft of the specification document by Gavin Bierman, a consulting member of the technical staff at Oracle, is open for review by the Java community.

Java has long been recognized for its capabilities in building large, complex applications. However, its extensive features can be daunting for beginners who are just starting to learn programming. To address this, this JEP has introduced new preview features to simplify the language for new programmers. These features allow beginners to write their first programs without needing to understand complex language constructs designed for larger applications and empower experienced developers to write small programs more succinctly, enhancing their productivity and code readability.

Consider the classic Hello, World! example that is often a beginner’s first program:

public class HelloWorld {

    public static void main(String[] args) {
        System.out.println("Hello, World!");
    }
}

With this JEP, the above program can be simplified to:

void main() {
    println("Hello, World!");
}

The proposal introduces several key features designed to simplify Java for beginners while maintaining its robust capabilities. One of the main highlights is the introduction of implicitly declared classes, allowing new programs to be written without explicit class declarations. In this new approach, all methods and fields in a source file are considered part of an implicitly declared class, which extends Object, does not implement interfaces, and cannot be referenced by name in source code. Additionally, the proposal introduces instance main methods, which no longer need to be static or public, and methods without parameters are also recognized as valid program entry points.

With these changes, developers can now write Hello, World! As:

void main() {
    System.out.println("Hello, World!");
}

Top-level members are interpreted as members of the implicit class, so we can also write the program as:

String greeting() { 
    return "Hello, World!"; 
}

void main() {
    System.out.println(greeting());
}

Or, using a field as: 

String greeting = "Hello, World!";

void main() {
    System.out.println(greeting);
}

Following the initial preview in JDK 21 (JEP 445) and subsequent updates in JDK 22 (JEP 463), the proposal has been refined further based on community feedback. For instance, in this JEP, implicitly declared classes now automatically import the following three static methods from the new java.io.IO class for simple textual I/O:

public static void println(Object obj);
public static void print(Object obj);
public static String readln(String prompt);

This change eliminates the need for the System.out.println incantation, thereby simplifying console output. Consider the following example:

void main() {
    String name = readln("Please enter your name: ");
    print("Pleased to meet you, ");
    println(name);
}

Many other classes declared in the Java API are useful in small programs. They can be imported explicitly at the start of the source file:

import java.util.List;

void main() {
    var authors = List.of("James", "Bill", "Bazlur", "Mike", "Dan", "Gavin");
    for (var name : authors) {
        println(name + ": " + name.length());
    }
}

However, with the JEP 476, Module Import Declarations, implicitly declared classes automatically import all public top-level classes and interfaces from the java.base module, removing the need for explicit import statements for commonly used APIs such as java.util.List. This makes the development process more seamless and reduces the learning curve for new programmers. More details on JEP 476 may be found in this InfoQ news story.

This is a preview language feature, available through the --enable-preview flag with the JDK 23 compiler and runtime. To try the examples above in JDK 23, you must enable the preview features:

  • Compile the program with javac --release 23 --enable-preview Main.java and run it with java --enable-preview Main; or,
  • When using the source code launcher, run the program with java --enable-preview Main.java; or,
  • When using jshell, start it with jshell --enable-preview.

Rather than introducing a separate dialect of Java, this JEP streamlines the declaration process for single-class programs. This approach facilitates a gradual learning curve, allowing beginners to start with simple, concise code and progressively adopt more advanced features as they gain experience. By simplifying syntax and minimizing boilerplate code, Java continues to uphold its reputation as a versatile and powerful programming language suitable for a wide range of applications. This enhancement not only makes Java more accessible to new programmers but also boosts productivity and readability for experienced developers working on smaller projects.

About the Author

Subscribe for MMS Newsletter

By signing up, you will receive updates about our latest information.

  • This field is for validation purposes and should be left unchanged.

JEP 477 Enhances Beginner Experience with Implicitly Declared Classes and Instance Main Methods

MMS Founder
MMS A N M Bazlur Rahman

JEP 477, Implicitly Declared Classes and Instance Main Methods (Third Preview), has been promoted from its Proposed to Target to Targeted status. This JEP proposes to “evolve the Java language so that students can write their first programs without needing to understand language features designed for large programs.” This JEP moves forward the September 2022 blog post, Paving the on-ramp, by Brian Goetz, the Java language architect at Oracle. The latest draft of the specification document by Gavin Bierman, a consulting member of the technical staff at Oracle, is open for review by the Java community.

Java has long been recognized for its capabilities in building large, complex applications. However, its extensive features can be daunting for beginners who are just starting to learn programming. To address this, this JEP has introduced new preview features to simplify the language for new programmers. These features allow beginners to write their first programs without needing to understand complex language constructs designed for larger applications and empower experienced developers to write small programs more succinctly, enhancing their productivity and code readability.

Consider the classic Hello, World! example that is often a beginner’s first program:

public class HelloWorld {

    public static void main(String[] args) {
        System.out.println("Hello, World!");
    }
}

With this JEP, the above program can be simplified to:

void main() {
    println("Hello, World!");
}

The proposal introduces several key features designed to simplify Java for beginners while maintaining its robust capabilities. One of the main highlights is the introduction of implicitly declared classes, allowing new programs to be written without explicit class declarations. In this new approach, all methods and fields in a source file are considered part of an implicitly declared class, which extends Object, does not implement interfaces, and cannot be referenced by name in source code. Additionally, the proposal introduces instance main methods, which no longer need to be static or public, and methods without parameters are also recognized as valid program entry points.

With these changes, developers can now write Hello, World! As:

void main() {
    System.out.println("Hello, World!");
}

Top-level members are interpreted as members of the implicit class, so we can also write the program as:

String greeting() { 
    return "Hello, World!"; 
}

void main() {
    System.out.println(greeting());
}

Or, using a field as: 

String greeting = "Hello, World!";

void main() {
    System.out.println(greeting);
}

Following the initial preview in JDK 21 (JEP 445) and subsequent updates in JDK 22 (JEP 463), the proposal has been refined further based on community feedback. For instance, in this JEP, implicitly declared classes now automatically import the following three static methods from the new java.io.IO class for simple textual I/O:

public static void println(Object obj);
public static void print(Object obj);
public static String readln(String prompt);

This change eliminates the need for the System.out.println incantation, thereby simplifying console output. Consider the following example:

void main() {
    String name = readln("Please enter your name: ");
    print("Pleased to meet you, ");
    println(name);
}

Many other classes declared in the Java API are useful in small programs. They can be imported explicitly at the start of the source file:

import java.util.List;

void main() {
    var authors = List.of("James", "Bill", "Bazlur", "Mike", "Dan", "Gavin");
    for (var name : authors) {
        println(name + ": " + name.length());
    }
}

However, with the JEP 476, Module Import Declarations, implicitly declared classes automatically import all public top-level classes and interfaces from the java.base module, removing the need for explicit import statements for commonly used APIs such as java.util.List. This makes the development process more seamless and reduces the learning curve for new programmers. More details on JEP 476 may be found in this InfoQ news story.

This is a preview language feature, available through the --enable-preview flag with the JDK 23 compiler and runtime. To try the examples above in JDK 23, you must enable the preview features:

  • Compile the program with javac --release 23 --enable-preview Main.java and run it with java --enable-preview Main; or,
  • When using the source code launcher, run the program with java --enable-preview Main.java; or,
  • When using jshell, start it with jshell --enable-preview.

Rather than introducing a separate dialect of Java, this JEP streamlines the declaration process for single-class programs. This approach facilitates a gradual learning curve, allowing beginners to start with simple, concise code and progressively adopt more advanced features as they gain experience. By simplifying syntax and minimizing boilerplate code, Java continues to uphold its reputation as a versatile and powerful programming language suitable for a wide range of applications. This enhancement not only makes Java more accessible to new programmers but also boosts productivity and readability for experienced developers working on smaller projects.

About the Author

Subscribe for MMS Newsletter

By signing up, you will receive updates about our latest information.

  • This field is for validation purposes and should be left unchanged.

JEP 467: Java Enhances Documentation with Markdown Support

MMS Founder
MMS A N M Bazlur Rahman

JEP 467, Markdown Documentation Comments, has been promoted from Proposed to Target to Targeted for JDK 23. This feature proposes to enable JavaDoc documentation comments to be written in Markdown rather than a mix of HTML and JavaDoc @ tags. This will allow for documentation comments that are easier to write and read in source form.

This update’s primary goal is to simplify the process of writing and reading documentation comments in Java source code. By allowing Markdown, which is known for its simplicity and readability, developers can avoid the complexities associated with HTML and JavaDoc tags. Existing documentation comments will remain unaffected, ensuring backward compatibility.

Furthermore, the update extends the Compiler Tree API, allowing other tools that analyze documentation comments to handle Markdown content effectively.

It’s important to note that this update does not include automated conversion of existing documentation comments to Markdown syntax. Developers must manually update their documentation to take advantage of the new feature.

Java documentation comments traditionally use HTML and JavaDoc tags, a practical choice in 1995 but has since become less convenient. HTML is verbose and challenging to write by hand, especially for developers who may not be familiar with it. Inline JavaDoc tags, such as {@link} and {@code}, are cumbersome and often require referencing documentation for proper usage.

Markdown, in contrast, is a lightweight markup language that is easy to read and write. It supports simple document structures like paragraphs, lists, styled text, and links, making it a suitable replacement for HTML in documentation comments. Additionally, Markdown allows the inclusion of HTML for constructs that it does not directly support, providing flexibility while reducing complexity.

Consider the following JavaDoc comment for java.lang.Object.hashCode written in the traditional format:

/**
 * Returns a hash code value for the object. This method is
 * supported for the benefit of hash tables such as those provided by
 * {@link java.util.HashMap}.
 * 

* The general contract of {@code hashCode} is: *

    *
  • Whenever it is invoked on the same object more than once during * an execution of a Java application, the {@code hashCode} method * must consistently return the same integer, provided no information * used in {@code equals} comparisons on the object is modified. * This integer need not remain consistent from one execution of an * application to another execution of the same application. *
  • If two objects are equal according to the {@link * #equals(Object) equals} method, then calling the {@code * hashCode} method on each of the two objects must produce the * same integer result. *
  • It is not required that if two objects are unequal * according to the {@link #equals(Object) equals} method, then * calling the {@code hashCode} method on each of the two objects * must produce distinct integer results. However, the programmer * should be aware that producing distinct integer results for * unequal objects may improve the performance of hash tables. *
* * @implSpec * As far as is reasonably practical, the {@code hashCode} method defined * by class {@code Object} returns distinct integers for distinct objects. * * @return a hash code value for this object. * @see java.lang.Object#equals(java.lang.Object) * @see java.lang.System#identityHashCode */

This comment can be written in Markdown as follows:

/// Returns a hash code value for the object. This method is
/// supported for the benefit of hash tables such as those provided by
/// [java.util.HashMap].
///
/// The general contract of `hashCode` is:
///
///   - Whenever it is invoked on the same object more than once during
///     an execution of a Java application, the `hashCode` method
///     must consistently return the same integer, provided no information
///     used in `equals` comparisons on the object is modified.
///     This integer need not remain consistent from one execution of an
///     application to another execution of the same application.
///   - If two objects are equal according to the
///     [equals][#equals(Object)] method, then calling the
///     `hashCode` method on each of the two objects must produce the
///     same integer result.
///   - It is _not_ required that if two objects are unequal
///     according to the [equals][#equals(Object)] method, then
///     calling the `hashCode` method on each of the two objects
///     must produce distinct integer results.  However, the programmer
///     should be aware that producing distinct integer results for
///     unequal objects may improve the performance of hash tables.
///
/// @implSpec
/// As far as is reasonably practical, the `hashCode` method defined
/// by class `Object` returns distinct integers for distinct objects.
///
/// @return  a hash code value for this object.
/// @see     java.lang.Object#equals(java.lang.Object)
/// @see     java.lang.System#identityHashCode

Markdown documentation comments are indicated using /// at the beginning of each line instead of the traditional /** ... */ syntax. This helps to avoid conflicts with embedded /* ... */ comments in the examples, which is increasingly common in documentation comments.

The Markdown parser used in this implementation is the CommonMark variant, with enhancements to support linking to program elements and simple GFM (GitHub Flavored Markdown) pipe tables. JavaDoc tags can still be used within Markdown documentation comments, ensuring that existing JavaDoc features are retained.

The parsed documentation comments are represented by com.sun.source.doctree package in the Compiler Tree API. To handle uninterpreted text, a new type of tree node, RawTextTree, is introduced, with a new tree-node kind, DocTree.Kind.MARKDOWN, indicating Markdown content. The implementation leverages the commonmark-java library to transform Markdown to HTML.

The introduction of Markdown support for JavaDoc comments marks a significant improvement in Java’s documentation capabilities, making it more accessible and easier to maintain. This change is expected to enhance developer productivity and improve the overall readability of Java documentation.

About the Author

Subscribe for MMS Newsletter

By signing up, you will receive updates about our latest information.

  • This field is for validation purposes and should be left unchanged.

JEP 476: Simplifying Java Development with Module Import

MMS Founder
MMS A N M Bazlur Rahman

After its review concluded, JEP 476, Module Import Declarations (Preview), was integrated into JDK 23. This preview feature proposes to enhance the Java programming language with the ability to succinctly import all of the packages exported by a module, with the goal of simplifying the reuse of modular libraries without requiring code to be in a module itself.

This JEP streamlines the importing of entire modules in Java, thus simplifying code and making it easier for developers, especially beginners, to utilize libraries and standard classes. This feature reduces the need for multiple import statements and eliminates the necessity of knowing the package hierarchy.

Importantly, this change does not disrupt existing code, as developers are not required to modularize their work. This feature is developed in conjunction with JEP 477, which automatically imports all public classes and interfaces from the java.base module for implicitly declared classes.

The Java programming language includes the automatic import of essential classes from the java.lang package.  However, as the platform has evolved, many classes, like List, Map, and Stream are not automatically included, forcing developers to import them explicitly.

For instance, the following code demonstrates how manually importing several packages consumes unnecessary lines:

import java.util.Map;                   
import java.util.function.Function;     
import java.util.stream.Collectors;     
import java.util.stream.Stream;

String[] fruits = new String[] { "apple", "berry", "citrus" };
Map m =
    Stream.of(fruits)
          .collect(Collectors.toMap(s -> s.toUpperCase().substring(0,1),
                                    Function.identity()));

With module imports, the syntax simplifies significantly:

import module java.base;

String[] fruits = new String[] { "apple", "berry", "citrus" };
Map m =
    Stream.of(fruits)
          .collect(Collectors.toMap(s -> s.toUpperCase().substring(0,1),
                                    Function.identity()));

A module import declaration follows this pattern:

import module M;

where M is the name of the module whose packages should be imported.

The effect of the import module is twofold:

  • Direct Packages: It imports all public top-level classes and interfaces in packages exported by the module M to the current module.
  • Transitive Dependencies: Packages exported by modules read via transitive dependencies are also imported.

As an example, the declaration import module java.base imports all 54 exported packages, effectively bringing a wide range of classes into scope from java.util to java.io.

However, importing entire modules increases the risk of ambiguous names when multiple packages contain classes with identical simple names. For instance, this example will trigger an error due to ambiguous List references:

import module java.base; // exports java.util.List

import module java.desktop; // exports java.awt.List

List l = ...                // Error - Ambiguous name!

The solution is to import the desired type explicitly:

import java.sql.Date; // resolve the ambiguity of the simple name Date!

Date d = ...                 // Ok! Date is resolved to java.sql.Date

The import module feature is currently a preview available through the --enable-preview flag with the JDK 23 compiler and runtime. To compile and run use the following command:

  • Compile the program with javac --release 23 --enable-preview Main.java and run it with java --enable-preview Main; or,

  • When using the source code launcher, run the program with java --enable-preview Main.java; or,

  • When using jshell, start it with jshell --enable-preview.

This JEP aims to provide a cleaner and more modular way to import Java libraries, reducing boilerplate code and enhancing accessibility, especially for new learners and developers working with modular libraries.

Overall, Java’s module import feature promises to improve productivity and ease of development. By simplifying imports, developers can focus more on crafting meaningful code and less on keeping their imports organized.

About the Author

Subscribe for MMS Newsletter

By signing up, you will receive updates about our latest information.

  • This field is for validation purposes and should be left unchanged.

JEP 474: Generational Mode Now Standard for ZGC in Java

MMS Founder
MMS A N M Bazlur Rahman

JEP 474, ZGC: Generational Mode by Default, has been Targeted for JDK 23. This JEP proposes to use the Z Garbage Collector (ZGC) from non-generational to generational mode by default. The non-generational mode will be deprecated and removed in a future JDK release. This will ultimately reduce the cost of maintaining the two modes, so future development can primarily focus on JEP 439, Generational ZGC.

Perhaps the most significant change in JEP 474 is the switch of the ZGenerational option’s default value from false to true. This clearly signals a strategic change in Java’s approach to memory management, specifically optimizing garbage collection based on delivering more ‘free’ memory to Java applications and a lower overhead of GC.

This transition to the generational mode by default is motivated by the desire to reduce the maintenance work of supporting generational and non-generational modes alike. With this focus on performance, the OpenJDK team will make the generational ZGC more efficient and effective as they evolve to handle the expectations of modern Java applications.

Developers should be aware that the non-generational mode is still available but deprecated, which means that warnings will be issued if the non-generational mode is explicitly enabled via command-line options. The fact that non-generational mode is being deprecated is a sign that it is going to be removed in the future.

Consider the typical command-line arguments:

  • -XX:+UseZGC: Now defaults to using Generational ZGC.
  • -XX:+UseZGC -XX:+ZGenerational: It also uses Generational ZGC but with a deprecation warning for the ZGenerational flag.
  • -XX:+UseZGC -XX:-ZGenerational: Uses non-generational ZGC with a deprecation warning for the ZGenerational flag and an advisory that non-generational mode is deprecated for removal.

Developers should test their applications to ensure compatibility and performance under the new default setting. If applications are tightly integrated with the JVM and are particularly sensitive to garbage collection behaviour, they might see changes to their performance.

OpenJDK’s testing commitment includes ensuring that existing configurations and benchmarks perform as expected with the new default. The risk is relatively low for most applications, but specific high-performance or low-latency applications might require adjustments to their configuration.

JEP 474 marks a pivotal update in Java’s ongoing development. It aims to streamline and improve the garbage collection process, focusing on generational techniques. This update will require developers to reassess their applications’ compatibility and performance, but it promises a more efficient management of Java applications in the long run.

About the Author

Subscribe for MMS Newsletter

By signing up, you will receive updates about our latest information.

  • This field is for validation purposes and should be left unchanged.