JavaCPP is a powerful bridge between Java and native C++ code, providing efficient and straightforward access to C++ libraries from Java. It generates JNI (Java Native Interface) code automatically, making native integration much simpler than manual JNI programming.
Overview and Setup
Maven Dependency
<dependency> <groupId>org.bytedeco</groupId> <artifactId>javacpp</artifactId> <version>1.5.10</version> </dependency> <!-- Platform-specific dependencies --> <dependency> <groupId>org.bytedeco</groupId> <artifactId>javacpp-platform</artifactId> <version>1.5.10</version> </dependency>
Gradle Dependency
implementation 'org.bytedeco:javacpp:1.5.10'
Basic JavaCPP Usage
Example 1: Simple C++ Function Calls
import org.bytedeco.javacpp.*; import org.bytedeco.javacpp.annotation.*; public class BasicJavaCPP { // Load native library static { Loader.load(); } // Define a C++ class mapping @Platform(include = {"<iostream>", "<string>"}) @Namespace("example") public static class NativeMath extends Pointer { static { Loader.load(); } public NativeMath() { allocate(); } private native void allocate(); // Native method declarations public native int add(int a, int b); public native double multiply(double a, double b); public native void printMessage(String message); // Static methods public static native long factorial(int n); } // C-style function mappings @Platform(include = "<cmath>") public static class MathFunctions extends Pointer { static { Loader.load(); } public static native double sqrt(double x); public static native double sin(double x); public static native double cos(double x); public static native double pow(double base, double exponent); } public static void main(String[] args) { // Using the native math class NativeMath math = new NativeMath(); System.out.println("5 + 3 = " + math.add(5, 3)); System.out.println("4.5 * 2.5 = " + math.multiply(4.5, 2.5)); math.printMessage("Hello from JavaCPP!"); System.out.println("Factorial of 5: " + NativeMath.factorial(5)); // Using C-style functions System.out.println("Square root of 16: " + MathFunctions.sqrt(16)); System.out.println("2^8 = " + MathFunctions.pow(2, 8)); } } Corresponding C++ Implementation
// native_math.cpp #include <iostream> #include <string> #include <cmath> namespace example { class NativeMath { public: NativeMath() { std::cout << "NativeMath constructor called" << std::endl; } int add(int a, int b) { return a + b; } double multiply(double a, double b) { return a * b; } void printMessage(const std::string& message) { std::cout << "Message: " << message << std::endl; } static long factorial(int n) { if (n <= 1) return 1; return n * factorial(n - 1); } }; } // C-style functions extern "C" { double sqrt(double x) { return std::sqrt(x); } double sin(double x) { return std::sin(x); } double cos(double x) { return std::cos(x); } double pow(double base, double exponent) { return std::pow(base, exponent); } } Advanced JavaCPP Features
Example 2: Memory Management and Arrays
import org.bytedeco.javacpp.*; import org.bytedeco.javacpp.annotation.*; public class MemoryManagementDemo { @Platform(include = {"<vector>", "<algorithm>"}) @Namespace("std") public static class VectorInt extends Pointer { static { Loader.load(); } public VectorInt() { allocate(); } public VectorInt(long n) { allocate(n); } private native void allocate(); private native void allocate(long n); public native long size(); public native long capacity(); public native @Cast("bool") boolean empty(); public native void push_back(int value); public native void pop_back(); public native int at(long position); public native int get(long position); public native void put(long position, int value); public native void clear(); public native void resize(long n); // Sort method public native void sort(); } @Platform(include = {"<iostream>", "<algorithm>"}) public static class ArrayProcessor extends Pointer { static { Loader.load(); } public ArrayProcessor() { allocate(); } private native void allocate(); // Process Java arrays public native void processIntArray(int[] array, @Cast("size_t") long length); public native void processDoubleArray(DoublePointer array, @Cast("size_t") long length); // Create and return native arrays public native IntPointer createIntArray(@Cast("size_t") long size); public native DoublePointer createDoubleArray(@Cast("size_t") long size); // Memory management public native void freeArray(IntPointer ptr); public native void freeArray(DoublePointer ptr); } public static void main(String[] args) { // Using std::vector through JavaCPP VectorInt vec = new VectorInt(); vec.push_back(10); vec.push_back(5); vec.push_back(20); vec.push_back(15); System.out.println("Vector size: " + vec.size()); System.out.println("Vector contents:"); for (long i = 0; i < vec.size(); i++) { System.out.println(" " + i + ": " + vec.at(i)); } vec.sort(); System.out.println("After sorting:"); for (long i = 0; i < vec.size(); i++) { System.out.println(" " + i + ": " + vec.at(i)); } // Using native arrays ArrayProcessor processor = new ArrayProcessor(); // Process Java array int[] javaArray = {1, 2, 3, 4, 5}; processor.processIntArray(javaArray, javaArray.length); // Create and use native array try (IntPointer nativeArray = processor.createIntArray(5)) { for (int i = 0; i < 5; i++) { nativeArray.put(i, i * 10); } System.out.println("Native array contents:"); for (int i = 0; i < 5; i++) { System.out.println(" " + i + ": " + nativeArray.get(i)); } } // Auto-closed with try-with-resources } } Corresponding C++ Implementation
// memory_management.cpp #include <vector> #include <algorithm> #include <iostream> #include <memory> extern "C" { // ArrayProcessor implementation class ArrayProcessor { private: std::vector<int> buffer; public: ArrayProcessor() { std::cout << "ArrayProcessor created" << std::endl; } void processIntArray(int* array, size_t length) { std::cout << "Processing int array of size " << length << ":" << std::endl; for (size_t i = 0; i < length; i++) { std::cout << " " << array[i] << " -> " << (array[i] * 2) << std::endl; array[i] *= 2; // Modify in place } } void processDoubleArray(double* array, size_t length) { std::cout << "Processing double array of size " << length << std::endl; for (size_t i = 0; i < length; i++) { array[i] = array[i] * array[i]; // Square each element } } int* createIntArray(size_t size) { int* arr = new int[size]; std::cout << "Created int array of size " << size << std::endl; return arr; } double* createDoubleArray(size_t size) { double* arr = new double[size]; std::cout << "Created double array of size " << size << std::endl; return arr; } void freeArray(int* ptr) { delete[] ptr; std::cout << "Freed int array" << std::endl; } void freeArray(double* ptr) { delete[] ptr; std::cout << "Freed double array" << std::endl; } }; } Real-World Example: Image Processing
Example 3: OpenCV Integration with JavaCPP
import org.bytedeco.javacpp.*; import org.bytedeco.opencv.opencv_core.*; import org.bytedeco.opencv.opencv_imgproc.*; import org.bytedeco.opencv.global.opencv_core; import org.bytedeco.opencv.global.opencv_imgproc; import org.bytedeco.opencv.global.opencv_imgcodecs; import javax.swing.*; import java.awt.*; import java.awt.image.BufferedImage; import static org.bytedeco.opencv.global.opencv_core.*; import static org.bytedeco.opencv.global.opencv_imgproc.*; import static org.bytedeco.opencv.global.opencv_imgcodecs.*; public class OpenCVDemo { // Convert Java BufferedImage to OpenCV Mat public static Mat bufferedImageToMat(BufferedImage image) { int type = image.getType(); int cvType = CV_8UC3; // Default to 3-channel if (type == BufferedImage.TYPE_BYTE_GRAY) { cvType = CV_8UC1; } Mat mat = new Mat(image.getHeight(), image.getWidth(), cvType); byte[] data = ((DataBufferByte) image.getRaster().getDataBuffer()).getData(); mat.data().put(data); return mat; } // Convert OpenCV Mat to Java BufferedImage public static BufferedImage matToBufferedImage(Mat mat) { int type = BufferedImage.TYPE_BYTE_GRAY; if (mat.channels() > 1) { type = BufferedImage.TYPE_3BYTE_BGR; } BufferedImage image = new BufferedImage(mat.cols(), mat.rows(), type); byte[] data = ((DataBufferByte) image.getRaster().getDataBuffer()).getData(); mat.data().get(data); return image; } // Image processing methods public static Mat applyGaussianBlur(Mat input, int kernelSize) { Mat output = new Mat(); GaussianBlur(input, output, new Size(kernelSize, kernelSize), 0); return output; } public static Mat applyCannyEdgeDetection(Mat input, double threshold1, double threshold2) { Mat gray = new Mat(); Mat edges = new Mat(); if (input.channels() > 1) { cvtColor(input, gray, COLOR_BGR2GRAY); } else { gray = input.clone(); } Canny(gray, edges, threshold1, threshold2); return edges; } public static Mat detectFaces(Mat input) { Mat output = input.clone(); // Load face detection classifier CascadeClassifier faceDetector = new CascadeClassifier(); faceDetector.load("haarcascade_frontalface_default.xml"); RectVector faces = new RectVector(); faceDetector.detectMultiScale(input, faces); // Draw rectangles around faces for (long i = 0; i < faces.size(); i++) { Rect rect = faces.get(i); rectangle(output, rect, new Scalar(0, 255, 0, 0), 3, LINE_8, 0); } return output; } public static void displayImage(Mat mat, String title) { BufferedImage image = matToBufferedImage(mat); JFrame frame = new JFrame(title); frame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE); JLabel label = new JLabel(new ImageIcon(image)); frame.getContentPane().add(label, BorderLayout.CENTER); frame.pack(); frame.setVisible(true); } public static void main(String[] args) { // Load an image Mat image = imread("input.jpg"); if (image.empty()) { System.out.println("Could not load image"); return; } System.out.println("Image loaded: " + image.cols() + "x" + image.rows()); // Display original displayImage(image, "Original Image"); // Apply Gaussian blur Mat blurred = applyGaussianBlur(image, 15); displayImage(blurred, "Blurred Image"); // Apply edge detection Mat edges = applyCannyEdgeDetection(image, 100, 200); displayImage(edges, "Edge Detection"); // Note: Face detection requires the classifier file // Mat faces = detectFaces(image); // displayImage(faces, "Face Detection"); // Release native memory image.close(); blurred.close(); edges.close(); } } Performance-Critical Applications
Example 4: High-Performance Numerical Computing
import org.bytedeco.javacpp.*; import org.bytedeco.javacpp.annotation.*; @Platform(include = {"<vector>", "<algorithm>", "<chrono>", "<iostream>"}) @Namespace("performance") public class HighPerformanceComputing extends Pointer { static { Loader.load(); } public HighPerformanceComputing() { allocate(); } private native void allocate(); // Matrix operations public native void matrixMultiply(@Const DoublePointer A, @Const DoublePointer B, DoublePointer C, @Cast("int") int m, @Cast("int") int n, @Cast("int") int p); // Vector operations public native double vectorDotProduct(@Const DoublePointer v1, @Const DoublePointer v2, @Cast("size_t") long size); public native void vectorAdd(@Const DoublePointer v1, @Const DoublePointer v2, DoublePointer result, @Cast("size_t") long size); // Sorting algorithms public native void quickSort(DoublePointer array, @Cast("size_t") long size); public native void parallelSort(DoublePointer array, @Cast("size_t") long size); // Benchmarking public native double benchmarkMatrixMultiplication(@Cast("int") int size); public native double benchmarkVectorOperations(@Cast("size_t") long size); } public class PerformanceDemo { public static void main(String[] args) { HighPerformanceComputing hpc = new HighPerformanceComputing(); // Matrix multiplication benchmark int matrixSize = 500; double duration = hpc.benchmarkMatrixMultiplication(matrixSize); System.out.printf("Matrix multiplication (%dx%d): %.3f seconds%n", matrixSize, matrixSize, duration); // Vector operations long vectorSize = 10000000; double vectorTime = hpc.benchmarkVectorOperations(vectorSize); System.out.printf("Vector operations (size %d): %.3f seconds%n", vectorSize, vectorTime); // Manual matrix multiplication example int m = 2, n = 3, p = 2; try (DoublePointer A = new DoublePointer(m * n); DoublePointer B = new DoublePointer(n * p); DoublePointer C = new DoublePointer(m * p)) { // Initialize matrices double[] aData = {1, 2, 3, 4, 5, 6}; double[] bData = {7, 8, 9, 10, 11, 12}; A.put(aData); B.put(bData); // Perform multiplication hpc.matrixMultiply(A, B, C, m, n, p); // Print result System.out.println("Matrix multiplication result:"); for (int i = 0; i < m; i++) { for (int j = 0; j < p; j++) { System.out.printf("%.1f ", C.get(i * p + j)); } System.out.println(); } } } } Corresponding C++ Performance Implementation
// high_performance.cpp #include <vector> #include <algorithm> #include <chrono> #include <iostream> #include <thread> #include <future> namespace performance { class HighPerformanceComputing { public: HighPerformanceComputing() { std::cout << "HighPerformanceComputing initialized" << std::endl; } void matrixMultiply(const double* A, const double* B, double* C, int m, int n, int p) { for (int i = 0; i < m; i++) { for (int j = 0; j < p; j++) { double sum = 0.0; for (int k = 0; k < n; k++) { sum += A[i * n + k] * B[k * p + j]; } C[i * p + j] = sum; } } } double vectorDotProduct(const double* v1, const double* v2, size_t size) { double result = 0.0; for (size_t i = 0; i < size; i++) { result += v1[i] * v2[i]; } return result; } void vectorAdd(const double* v1, const double* v2, double* result, size_t size) { for (size_t i = 0; i < size; i++) { result[i] = v1[i] + v2[i]; } } void quickSort(double* array, size_t size) { std::sort(array, array + size); } void parallelSort(double* array, size_t size) { std::sort(std::execution::par_unseq, array, array + size); } double benchmarkMatrixMultiplication(int size) { auto start = std::chrono::high_resolution_clock::now(); // Create test matrices std::vector<double> A(size * size, 1.0); std::vector<double> B(size * size, 2.0); std::vector<double> C(size * size, 0.0); // Perform multiplication matrixMultiply(A.data(), B.data(), C.data(), size, size, size); auto end = std::chrono::high_resolution_clock::now(); std::chrono::duration<double> duration = end - start; return duration.count(); } double benchmarkVectorOperations(size_t size) { auto start = std::chrono::high_resolution_clock::now(); std::vector<double> v1(size, 1.0); std::vector<double> v2(size, 2.0); std::vector<double> result(size); // Perform multiple operations for (int i = 0; i < 10; i++) { vectorDotProduct(v1.data(), v2.data(), size); vectorAdd(v1.data(), v2.data(), result.data(), size); } auto end = std::chrono::high_resolution_clock::now(); std::chrono::duration<double> duration = end - start; return duration.count(); } }; } Best Practices and Patterns
Example 5: Error Handling and Resource Management
import org.bytedeco.javacpp.*; import org.bytedeco.javacpp.annotation.*; @Platform(include = {"<stdexcept>", "<memory>"}) @Namespace("safe") public class SafeNativeOperations extends Pointer { static { Loader.load(); } public SafeNativeOperations() { allocate(); } private native void allocate(); // Methods that may throw exceptions public native @ByVal IntPointer createArray(@Cast("size_t") long size) throws NativeException; public native double safeDivide(double a, double b) throws NativeException; public native @StdString String readFile(@Const @StdString String filename) throws NativeException; // Resource management with callbacks public interface ResourceCallback extends Pointer { void onSuccess(@Const @StdString String message); void onError(@Const @StdString String error); } public native void asyncOperation(@Const @StdString String data, ResourceCallback callback); } // Custom exception for native errors class NativeException extends Exception { public NativeException(String message) { super(message); } public NativeException(String message, Throwable cause) { super(message, cause); } } public class SafeNativeDemo { public static void main(String[] args) { SafeNativeOperations nativeOps = new SafeNativeOperations(); // Error handling with exceptions try { IntPointer array = nativeOps.createArray(100); System.out.println("Array created successfully"); double result = nativeOps.safeDivide(10.0, 2.0); System.out.println("Division result: " + result); // This will throw an exception double invalid = nativeOps.safeDivide(10.0, 0.0); } catch (NativeException e) { System.err.println("Native operation failed: " + e.getMessage()); } // Async operations with callbacks SafeNativeOperations.ResourceCallback callback = new SafeNativeOperations.ResourceCallback() { @Override public void onSuccess(String message) { System.out.println("Async operation succeeded: " + message); } @Override public void onError(String error) { System.err.println("Async operation failed: " + error); } }; nativeOps.asyncOperation("test data", callback); // Wait for async operation to complete try { Thread.sleep(1000); } catch (InterruptedException e) { Thread.currentThread().interrupt(); } } } Key Benefits of JavaCPP
- Automatic Code Generation: No manual JNI coding required
- Type Safety: Strong typing between Java and C++
- Memory Management: Automatic resource cleanup with Pointer objects
- Performance: Near-native performance for critical operations
- Broad Library Support: Pre-built configurations for popular C++ libraries
- Cross-Platform: Works on Windows, Linux, macOS
Common Use Cases
- Computer Vision: OpenCV, Dlib
- Machine Learning: TensorFlow, PyTorch C++ API
- Scientific Computing: BLAS, LAPACK, FFTW
- Game Development: Integrating game engines
- High-Performance Computing: Custom numerical libraries
- Legacy System Integration: Wrapping existing C++ codebases
JavaCPP dramatically simplifies the process of integrating C++ code with Java applications, providing a robust foundation for building high-performance, native-enabled Java applications.