DZone
Thanks for visiting DZone today,
Edit Profile
  • Manage Email Subscriptions
  • How to Post to DZone
  • Article Submission Guidelines
Sign Out View Profile
  • Post an Article
  • Manage My Drafts
Over 2 million developers have joined DZone.
Log In / Join
Please enter at least three characters to search
Refcards Trend Reports
Events Video Library
Refcards
Trend Reports

Events

View Events Video Library

Zones

Culture and Methodologies Agile Career Development Methodologies Team Management
Data Engineering AI/ML Big Data Data Databases IoT
Software Design and Architecture Cloud Architecture Containers Integration Microservices Performance Security
Coding Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks
Culture and Methodologies
Agile Career Development Methodologies Team Management
Data Engineering
AI/ML Big Data Data Databases IoT
Software Design and Architecture
Cloud Architecture Containers Integration Microservices Performance Security
Coding
Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance
Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks

Because the DevOps movement has redefined engineering responsibilities, SREs now have to become stewards of observability strategy.

Apache Cassandra combines the benefits of major NoSQL databases to support data management needs not covered by traditional RDBMS vendors.

The software you build is only as secure as the code that powers it. Learn how malicious code creeps into your software supply chain.

Generative AI has transformed nearly every industry. How can you leverage GenAI to improve your productivity and efficiency?

Related

  • Java’s Next Act: Native Speed for a Cloud-Native World
  • Enhancing API Integration Efficiency With a Mock Client
  • Secrets Management With Datadog Secret Backend Utility
  • The Energy Efficiency of JVMs and the Role of GraalVM

Trending

  • Creating a Web Project: Caching for Performance Optimization
  • Analyzing Techniques to Provision Access via IDAM Models During Emergency and Disaster Response
  • Navigating Change Management: A Guide for Engineers
  • Memory-Optimized Tables: Implementation Strategies for SQL Server
  1. DZone
  2. Coding
  3. Java
  4. Ballerina Code to GraalVM Executable

Ballerina Code to GraalVM Executable

This article is written using Ballerina Swan Lake Update 7(2201.7.0). We will explore how to build a GraalVM native executable for a Ballerina application.

By 
Tharmigan Krishnananthalingam user avatar
Tharmigan Krishnananthalingam
·
Oct. 12, 23 · Tutorial
Likes (2)
Comment
Save
Tweet
Share
4.7K Views

Join the DZone community and get the full member experience.

Join For Free

In the rapidly evolving landscape of software development, creating efficient and high-performing applications is essential. In this article, we will explore the seamless integration of Ballerina and GraalVM, focusing on the process of transforming Ballerina code into a native executable using GraalVM.

Ballerina is a modern, open-source programming language designed explicitly for cloud-native development. With its focus on integration and ease of use, Ballerina empowers developers to rapidly build scalable, distributed applications. The language comes equipped with a rich set of connectors for interacting with various protocols, services, and APIs, making it ideal for microservices architectures.

GraalVM is a cutting-edge, high-performance runtime that supports multiple languages, including Java, JavaScript, Python, and Ruby. Its unique capability lies in offering both Just-In-Time (JIT) compilation and Ahead-of-Time (AOT) compilation, enabling developers to choose the best execution mode for their applications. GraalVM can take Java bytecode and compile it into a standalone native executable, resulting in faster startup times and reduced memory overhead.

When Ballerina meets GraalVM, it becomes the winning formula for cloud-native applications. In this article, we will explore how to build a GraalVM native executable for a real-world Ballerina conference application and how it performs in terms of startup time and memory footprint.

The following shows the outline of the application we are going to build.

conference service outline


The application has a Conference Service that exposes the following resources:
  1. GET /conferences : returns an array of conferences with the name and the id
  2. POST /conferences : creates a conference resource with a name
  3. GET /conferenceswithcountry : returns an array of conferences with the name and the country

The conference resources are stored and retrieved from an H2 in-memory database. The country of the conference is retrieved by requesting an external Country Service endpoint. 

Let’s start by setting up Ballerina and GraalVM.

  1. Download and install Ballerina SwanLake 2201.7.0 or greater. Make sure you download the installer compatible with your local machine’s architecture
  2. Install GraalVM and configure it appropriately
  3. Install Visual Studio Code with Ballerina extension
  4. Create a ballerina package using the following command:
$ bal new conference_service


Create the required records to represent the data involved in this application. 

Java
 
# Represents a conference
#
# + id - The id of the conference
# + name - The name of the conference
public type Conference record {|
    readonly int id;
    string name;
|};

# Represents a conference request
#
# + name - The name of the conference
public type ConferenceRequest record {|
    string name;
|};

# Represents a country response
#
# + name - The name of the country
public type Country record {|
    string name;
|};

# Represents a extended conference
#
# + name - The name of the conference
# + country - The country of the conference
public type ExtendedConference record {|
    string name;
    string country;
|};


Create the database client to connect to an in-memory H2 database and the Country Service.

Java
 
import ballerinax/java.jdbc;
import ballerina/sql;
import ballerina/http;
import ballerinax/h2.driver as _;

# Represents the configuration of the h2 database
#
# + user - The user of the database  
# + password - The password of the database  
# + dbName - The file path of the database
public type H2dbConfigs record {|
    string user;
    string password;
    string dbName;
|};

# Represents the conference database client
public isolated client class ConferenceDBClient {

    private final jdbc:Client conferenceJDBCClient;
    private final http:Client countryClient;

    public isolated function init(H2dbConfigs dbConfigs, string countryServiceUrl)
            returns error? {

        self.conferenceJDBCClient = check new (
            "jdbc:h2:mem:" + dbConfigs.dbName,
            dbConfigs.user,
            dbConfigs.password
        );
        self.countryClient = check new (countryServiceUrl,
            retryConfig = {
                count: 3,
                interval: 2
            }
        );
        // Reinitialize table
        check self.dropTable();
        check self.createTable();
    }

    # Create the conference table in the database
    #
    # + return - returns an error if the table creation fails
    isolated function createTable() returns error? {
        _ = check self.conferenceJDBCClient->execute(
            `CREATE TABLE conferences (
                id INT AUTO_INCREMENT PRIMARY KEY, 
                name VARCHAR(255))`
        );
    }

    # Drop the conference table in the database
    #
    # + return - returns an error if the table drop fails
    isolated function dropTable() returns error? {
        _ = check self.conferenceJDBCClient->execute(
            `DROP TABLE IF EXISTS conferences`);
    }

    # Retrieve all the conferences from the database
    #
    # + return - retruns an array of conferences or
    # an error if the retrieval fails
    isolated resource function get conferences()
            returns Conference[]|error {

        stream<Conference, sql:Error?> conferences = self.conferenceJDBCClient->query(
            `SELECT * FROM conferences`);
        return from Conference conference in conferences
            select conference;
    }

    # Create a conference in the database
    #
    # + conference - The conference to be created
    # + return - returns an error if the conference creation fails
    isolated resource function post conferences(ConferenceRequest conference)
            returns error? {

        _ = check self.conferenceJDBCClient->execute(
            `INSERT INTO conferences (name) VALUES (${conference.name})`);
    }

    # Retrieve all the conferences with the country from the database
    #
    # + return - retruns an array of extended conferences 
    # or an error if the retrieval fails
    isolated resource function get conferenceswithcountry()
            returns ExtendedConference[]|error {

        Conference[] conferences = check self->/conferences;
        return from Conference conference in conferences
            select {
                name: conference.name,
                country: check self.getCountry(conference.name)
            };
    }

    # Retrieve the country of a conference by calling the country service
    #
    # + conference - The conference name
    # + return - retruns the country of the conference 
    # or an error if the retrieval fails
    isolated function getCountry(string conference)
            returns string|error {

        Country country = check self.countryClient->/conferences/[conference]/country;
        return country.name;
    }
}


Create a custom StatusCodeResponse to represent the internal errors.

Java
 
import ballerina/http;

# Represents a Error Detail
# 
# + message - The message of the error
# + cause - The cause of the error
public type ErrorDetail record {|
    string message;
    string cause;
|};

# Represents a Internal Server Error Response
# 
# + body - The body of the response
public type ConferenceServerError record {|
    *http:InternalServerError;
    ErrorDetail body;
|};


Now, we can define the Conference Service.

Java
 
import ballerina/http;
import ballerina/mime;

configurable string countryServiceUrl = ?;
configurable H2dbConfigs dbConfigs = ?;

service class ConferenceService {
    *http:Service;
    final ConferenceDBClient conferenceDBClient;

    isolated function init() returns error? {
        self.conferenceDBClient = check new (dbConfigs, countryServiceUrl);
    }

    @http:ResourceConfig {produces: [mime:APPLICATION_JSON]}
    isolated resource function get conferences() 
            returns Conference[]|ConferenceServerError {
            
        do {
            return check self.conferenceDBClient->/conferences;
        } on fail error err {
            return {
                body: {
                    message: "Error occurred while retrieving conferences",
                    cause: err.message()
                }
            };
        }
    }

    @http:ResourceConfig {consumes: [mime:APPLICATION_JSON]}
    isolated resource function post conferences(ConferenceRequest conference)
            returns ConferenceServerError? {
            
        do {
            return check self.conferenceDBClient->/conferences.post(conference);
        } on fail error err {
            return {
                body: {
                    message: "Error occurred while creating conference",
                    cause: err.message()
                }
            };
        }
    }

    @http:ResourceConfig {produces: [mime:APPLICATION_JSON]}
    isolated resource function get conferenceswithcountry() 
            returns ExtendedConference[]|ConferenceServerError {
            
        do {
            return check self.conferenceDBClient->/conferenceswithcountry;
        } on fail error err {
            return {
                body: {
                    message: "Error occurred while retrieving conferences with country",
                    cause: err.message()
                }
            };
        }
    }
}


Let’s start the service on a listener dynamically by modifying the main.bal .

Java
 
import ballerina/log;
import ballerina/http;
import ballerina/lang.runtime;

configurable int conferenceServicePort = ?;

public function main() returns error? {
    http:Listener conferenceListener = check new (conferenceServicePort);
    log:printInfo("Starting the listener...");
    // Attach the service to the listener.
    check conferenceListener.attach(check new ConferenceService());
    // Start the listener.
    check conferenceListener.'start();
    // Register the listener dynamically.
    runtime:registerListener(conferenceListener);
    log:printInfo("Startup completed. " +  
        string`Listening on: http://localhost:${conferenceServicePort}`);
}


Before running the application, we need to pass the required configurations via Config.toml .

TOML
 
conferenceServicePort = 8102

countryServiceUrl = "http://localhost:9000"

# The database related configs
[dbConfigs]
dbName = "conferencedb"
user = "admin"
password = "admin"


Let’s start the Country Service application using the following docker command.

$ docker run -p 9000:9000 ktharmi176/country-service:latest


We are all set to build the Conference Service application now. Run the following command to build the GraalVM native executable for the application.

$ bal build --graalvm

Compiling source
        tharmigan/conference_service:0.1.0

===========================================================================
GraalVM Native Image: Generating 'conference_service' (executable)...
===========================================================================
[1/7] Initializing...                                       (7.8s @ 0.53GB)
 Version info: 'GraalVM 22.3.1 Java 11 CE'
 Java version info: '11.0.18+10-jvmci-22.3-b13'
 C compiler: cc (apple, arm64, 14.0.3)
 Garbage collector: Serial GC
 2 user-specific feature(s)
 - com.oracle.svm.thirdparty.gson.GsonFeature
 - io.ballerina.stdlib.crypto.svm.BouncyCastleFeature
[2/7] Performing analysis...  [***********]                (90.6s @ 4.74GB)
  28,210 (93.89%) of 30,045 classes reachable
  92,277 (81.07%) of 113,830 fields reachable
 156,792 (72.71%) of 215,640 methods reachable
   1,526 classes,    65 fields, and 3,017 methods registered for reflection
      92 classes,    98 fields, and    66 methods registered for JNI access
       6 native libraries: -framework CoreServices, ...
[3/7] Building universe...                                 (16.6s @ 3.69GB)
[4/7] Parsing methods...      [****]                       (15.5s @ 5.11GB)
[5/7] Inlining methods...     [***]                         (8.0s @ 3.41GB)
[6/7] Compiling methods...    [********]                   (76.3s @ 3.01GB)
[7/7] Creating image...                                    (15.8s @ 4.05GB)
 100.86MB (58.88%) for code area:   113,643 compilation units
  68.52MB (40.00%) for image heap:  529,078 objects and 104 resources
   1.92MB ( 1.12%) for other data
 171.29MB in total
---------------------------------------------------------------------------
Top 10 packages in code area:     Top 10 object types in image heap:
  15.91MB ballerina.http/2           17.47MB byte[] for code metadata
   4.15MB ballerina.http/2.types     15.03MB byte[] for embedded resources
   2.83MB ballerina.io/1              7.44MB java.lang.Class
   2.30MB ballerina.sql/1             5.63MB byte[] for java.lang.String
   1.60MB sun.security.ssl            5.26MB java.lang.String
   1.37MB ballerina.file/1            4.05MB byte[] for general heap data
   1.21MB ballerina.sql/1.types       2.58MB com.oracle.svm.core.hub
   1.20MB com.sun.media.sound         1.38MB byte[] for reflection metadata
   1.19MB ballerina.jwt/2             1.08MB java.lang.String[]
   1.14MB ballerina.http/2.creators   1.02MB c.o.svm.core.hub.DynamicHub
  67.31MB for 1000 more packages      6.44MB for 3894 more object types
---------------------------------------------------------------------------
 41.4s (16.9% of total time) in 68 GCs | Peak RSS: 4.15GB | CPU load: 3.88
---------------------------------------------------------------------------
Produced artifacts:
 /conference-service/target/bin/conference_service(executable)
 /conference-service/target/bin/conference_service.build_artifacts.txt(txt)
===========================================================================
Finished generating 'conference_service' in 4m 3s.


This will build the GraalVM executable and the uber JAR in the target/bin directory. Now let’s run the Conference Service native executable.

$ ./target/bin/conference_service

time = 2023-07-28T12:36:15.991+05:30 level = INFO module = tharmigan/conference_service/0 message = "Starting the listener..."
time = 2023-07-28T12:36:16.003+05:30 level = INFO module = tharmigan/conference_service/0 message = "Startup completed. Listening on: http://localhost:8102"


You can test the service using the cURL commands or use the REST Client VS code extension. You need to create a .http file to invoke HTTP requests with the REST Client extension. Then, you can click on the Send Request action to make a request.

HTTP
 
### Add a new conference
POST http://localhost:8102/conferences
Content-Type: application/json

{
    "name": "WSO2Con"
}

### Retrive all conferences
GET http://localhost:8102/conferences

### Add another conference
POST http://localhost:8102/conferences
Content-Type: application/json

{
    "name": "KubeCon"
}

### Retrive all conferences with country
GET http://localhost:8102/conferenceswithcountry


Note: Ballerina language libraries, standard libraries, and Ballerina extended libraries are GraalVM compatible. If we use any GraalVM incompatible libraries, then the compiler will report warnings. For more information, see Evaluate GraalVM compatibility.

To check the startup time, we will be executing the following script, which will log the time of the execution and listener startup.

#!/bin/bash

echo "time = $(date +"%Y-%m-%dT%H:%M:%S.%3N%:z") level = INFO module = tharmigan/conference_service message = Executing the Ballerina application"

if [ "$1" = "graalvm" ];
then
    ./target/bin/conference_service
else
    java -jar ./target/bin/conference_service.jar
fi


Let’s first run the JAR file.

$ sh run.sh

time = 2023-08-01T12:24:43.038+05:30 level = INFO module = tharmigan/conference_service message = Executing the Ballerina application
time = 2023-08-01T12:24:44.002+05:30 level = INFO module = tharmigan/conference_service message = "Starting the listener..."
time = 2023-08-01T12:24:44.241+05:30 level = INFO module = tharmigan/conference_service message = "Startup completed. Listening on: http://localhost:8102"


The JAR application started in 1203ms. Now, make the requests to the endpoint and execute the following to check the RSS of the process.

$ ps -o pid,rss,command | grep conference_service

21068 183616 java -jar ./target/bin/conference_service.jar
21086   1184 grep conference_service


The RSS is around 180MB. Now, let’s check the native executable.

$ sh run.sh graalvm

time = 2023-08-01T12:29:06.470+05:30 level = INFO module = tharmigan/conference_service message = Executing the Ballerina application
time = 2023-08-01T12:29:06.512+05:30 level = INFO module = tharmigan/conference_service/0 message = "Starting the listener..."
time = 2023-08-01T12:29:06.519+05:30 level = INFO module = tharmigan/conference_service/0 message = "Startup completed. Listening on: http://localhost:8102"


The GraalVM native executable just started in 49ms. The startup time is approximately 25 times less than the JAR execution! 

Now, execute the requests and check the RSS of the process.

$ ps -o pid,rss,command | grep conference_service

21154  87808 ./target/bin/conference_service_ballerina
21233   1184 grep conference_service_ballerina


The RSS of the native executable is around 90MB. The memory footprint is approximately half of the JAR execution!

Performance comparison between JAR execution VS GraalVM native executable execution

Note: Ballerina supports providing additional native-image build options via --graalvm-build-options= argument. This can also be configured in the Ballerina.toml file. For more information, see Configure GraalVM native image build options.

So, the GraalVM native executable can achieve instant startup and a low memory footprint. But there is no such thing as a free lunch. There are trade-offs.

  • Longer build times due to the image generation process.
  • The target platform must be known at build time since the executable is platform-dependent.
  • The dynamic features of Java, such as reflection, are not directly supported. This is still possible by adding, but it has to be configured and compiled into the native executable.
  • No runtime optimizations. So, long-running applications in the traditional JVMs perform better in throughput and latency. However, the GraalVM enterprise edition supports Profile-Guided Optimizations(PGO) to achieve better throughput.

In conclusion, GraalVM and Ballerina offer a unique and compelling solution for building cloud-native applications. This solution provides small packaging, instant startup, low memory footprint, and simple and powerful integration capabilities.

The Ballerina code can be found in this GitHub repository.

Executable GraalVM Ballerina (programming language)

Published at DZone with permission of Tharmigan Krishnananthalingam. See the original article here.

Opinions expressed by DZone contributors are their own.

Related

  • Java’s Next Act: Native Speed for a Cloud-Native World
  • Enhancing API Integration Efficiency With a Mock Client
  • Secrets Management With Datadog Secret Backend Utility
  • The Energy Efficiency of JVMs and the Role of GraalVM

Partner Resources

×

Comments
Oops! Something Went Wrong

The likes didn't load as expected. Please refresh the page and try again.

ABOUT US

  • About DZone
  • Support and feedback
  • Community research
  • Sitemap

ADVERTISE

  • Advertise with DZone

CONTRIBUTE ON DZONE

  • Article Submission Guidelines
  • Become a Contributor
  • Core Program
  • Visit the Writers' Zone

LEGAL

  • Terms of Service
  • Privacy Policy

CONTACT US

  • 3343 Perimeter Hill Drive
  • Suite 100
  • Nashville, TN 37211
  • support@dzone.com

Let's be friends:

Likes
There are no likes...yet! 👀
Be the first to like this post!
It looks like you're not logged in.
Sign in to see who liked this post!