Skip to content

Instantly share code, notes, and snippets.

@bjodah
Forked from santa4nt/HelloJNI.java
Last active January 23, 2026 15:20
Show Gist options
  • Select an option

  • Save bjodah/46e544a5fc620c738069d6aef2aca716 to your computer and use it in GitHub Desktop.

Select an option

Save bjodah/46e544a5fc620c738069d6aef2aca716 to your computer and use it in GitHub Desktop.
Sample JNI/C++ HelloWorld
build/
*.class

Example of using JNI with C++

On e.g. debian we can:

$ sudo apt-get install openjdk-21-jdk cmake g++
$ $ ./makerun.sh 
-- The CXX compiler identification is GNU 13.3.0
-- Detecting CXX compiler ABI info
-- Detecting CXX compiler ABI info - done
-- Check for working CXX compiler: /usr/bin/c++ - skipped
-- Detecting CXX compile features
-- Detecting CXX compile features - done
-- Found Java: /usr/lib/jvm/java-21-openjdk-amd64/bin/java (found version "21.0.9") found components: Development 
-- Found JNI: /usr/lib/jvm/java-21-openjdk-amd64/include  found components: AWT JVM 
-- Configuring done (1.3s)
-- Generating done (0.0s)
-- Build files have been written to: /home/bjorningvar/vc/HelloJNI.java/build
[ 33%] Building CXX object CMakeFiles/hello.dir/HelloJNIImpl.cpp.o
[ 66%] Linking CXX shared library libhello.so
[100%] Built target hello
[DEBUG] Releasing Dave
Hello, Dave
cmake_minimum_required(VERSION 3.28)
# set language to c++
project(HelloJNI.java LANGUAGES CXX)
find_package(Java REQUIRED COMPONENTS Development)
find_package(JNI REQUIRED)
# 2. Define locations for outputs
set(JAVA_SOURCE "${CMAKE_CURRENT_SOURCE_DIR}/HelloJNI.java")
set(CLASS_OUTPUT_DIR "${CMAKE_CURRENT_BINARY_DIR}/classes")
set(HEADER_OUTPUT_DIR "${CMAKE_CURRENT_BINARY_DIR}/generated_headers")
# Create these directories before running commands
file(MAKE_DIRECTORY ${CLASS_OUTPUT_DIR} ${HEADER_OUTPUT_DIR})
# 3. Define the path of the generated header
# Note: If your Java class is inside a package (e.g., com.example),
# the file will be com_example_HelloJNI.h.
# Since we are using default package here:
set(GENERATED_HEADER "${HEADER_OUTPUT_DIR}/HelloJNI.h")
# 4. Create a custom command to run javac -h
add_custom_command(
OUTPUT ${GENERATED_HEADER} "${CLASS_OUTPUT_DIR}/HelloJNI.class"
COMMAND ${Java_JAVAC_EXECUTABLE}
-h ${HEADER_OUTPUT_DIR}
-d ${CLASS_OUTPUT_DIR}
${JAVA_SOURCE}
DEPENDS ${JAVA_SOURCE}
COMMENT "Compiling Java source and generating JNI header"
VERBATIM
)
# 5. Build the Native Library (Shared Object)
# We add GENERATED_HEADER to the sources so CMake knows to run the custom command first
add_library(hello SHARED HelloJNIImpl.cpp ${GENERATED_HEADER})
# 6. Include Directories
target_include_directories(hello PRIVATE
${JNI_INCLUDE_DIRS} # Where jni.h and jni_md.h live
${HEADER_OUTPUT_DIR} # Where our generated HelloJNI.h lives
)
# 7. Link Libraries (Optional on Linux usually, but good practice)
target_link_libraries(hello ${JNI_LIBRARIES})
public class HelloJNI {
static {
System.loadLibrary("hello"); // loads libhello.so
}
private native void sayHello(String name);
public static void main(String[] args) {
new HelloJNI().sayHello("Dave");
}
}
#include "HelloJNIImpl.h"
#include <memory>
#include <functional>
#include <iostream>
#include <jni.h>
#include "HelloJNI.h" // auto-generated by `javah HelloJNI`
using std::string;
using std::function;
using std::unique_ptr;
using std::shared_ptr;
using std::cout;
using std::endl;
class jstring_deleter
{
JNIEnv *m_env;
jstring m_jstr;
public:
jstring_deleter(JNIEnv *env, jstring jstr)
: m_env(env)
, m_jstr(jstr)
{
}
void operator()(const char *cstr)
{
cout << "[DEBUG] Releasing " << cstr << endl;
m_env->ReleaseStringUTFChars(m_jstr, cstr);
}
};
const string ToString(JNIEnv *env, jstring jstr)
{
jstring_deleter deleter(env, jstr); // using a function object
unique_ptr<const char, jstring_deleter> pcstr(
env->GetStringUTFChars(jstr, JNI_FALSE),
deleter );
return string( pcstr.get() );
}
shared_ptr<const char> ToStringPtr(JNIEnv *env, jstring jstr)
{
function<void(const char*)> deleter = // using a lambda
[env, jstr](const char *cstr) -> void
{
cout << "[DEBUG] Releasing " << cstr << endl;
env->ReleaseStringUTFChars(jstr, cstr);
};
return shared_ptr<const char>(
env->GetStringUTFChars(jstr, JNI_FALSE),
deleter );
}
/*
* Class: HelloJNI
* Method: sayHello
* Signature: (Ljava/lang/String;)V
*/
JNIEXPORT void JNICALL Java_HelloJNI_sayHello
(JNIEnv *env, jobject thisObj, jstring arg)
{
DoSayHello(ToString(env, arg));
//const string name = ToStringPtr(env, arg).get();
//DoSayHello(name);
}
void DoSayHello(const string &name)
{
cout << "Hello, " << name << endl;
}
#ifndef _HELLO_JNI_IMPL_H
#define _HELLO_JNI_IMPL_H
#include <string>
void DoSayHello(const std::string &name);
#endif//_HELLO_JNI_IMPL_H
#!/bin/bash
if [[ ! -v JAVA_HOME ]]; then
JAVA_HOME="$(java -XshowSettings:properties -version 2>&1 > /dev/null \
| grep 'java.home' \
| awk '{print $3}')"
fi
if [[ ! -d "$JAVA_HOME/include" ]]; then
>&2 echo "${BASH_SOURCE[0]}:${BASH_LINENO[0]}:No include/ directory under JAVA_HOME=$JAVA_HOME?"
exit 1
fi
env JAVA_HOME="$JAVA_HOME" cmake -S . -B build/ --fresh
cmake --build build/
env -C ./build java -cp classes -Djava.library.path=. HelloJNI
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment