Using JNIs (like OpenCV) in Flink

For a bigger project which I hope to blog about soon, I needed to get the OpenCV Java Native Library (JNI) running in a Flink stream. It was a pain in the ass, so I’m putting this here to help the next person.

First thing I tried… Just doing it.

For OpenCV, you need to statically initialize (I’m probably saying this wrong) the library, so I tried something like this

val stream = env
.map(record => {

Well, that kicks an error that looks like:

Exception in thread "Thread-143" java.lang.UnsatisfiedLinkError:
Native Library /usr/lib/jni/ already loaded in
another classloader

Ok, that’s fair. Multiple task managers, this is getting loaded all over the place. I get it. I tried moving this around a bunch. No luck.

Second thing I tried… Monkey see- monkey do: The RocksDB way.

In the Flink-RocksDB connector, and other people have given this advice, the idea is to include the JNI in the resources/fat-jar, then write out a tmp one and have that loaded.

This, for me, resulted in seemingly one tmp copy being generated for each record processed.


* This is an example of an extremely stupid way (and consequently the way Flink does RocksDB) to handle the JNI problem.
* Basically we include in src/main/resources so then it creates a tmp version.
* I deleted from it resources, so this would fail. Only try it for academic purposes. E.g. to see what stupid looks like.
object NativeUtils {
   // heavily based on
    def loadOpenCVLibFromJar() = {

        val temp = File.createTempFile("libopencv_java330", ".so")

        val inputStream= getClass().getResourceAsStream("/")

        val os = new FileOutputStream(temp)
        var readBytes: Int = 0
        var buffer = new Array[Byte](1024)

        try {
           while ({(readBytes =
              readBytes != -1}) {
              os.write(buffer, 0, readBytes)
        finally {
        // If read/write fails, close streams safely before throwing an exception



Third way: Sanity.

There were more accurately like 300 hundred ways I tried to make this S.O.B. work, I’m really just giving you the way points- major strategies I tried in my journey. This is the solution. This is the ‘Tomcat solution’ I’d seen referenced throughout my journey but didn’t understand what they meant. Hence why, I’m writing this blog post.

I created an entirely new module. I called it org.rawkintrevo.cylons.opencv. In that module there is one class.

package org.rawkintrevo.cylon.opencv;

import org.opencv.core.Core;

public class LoadNative {

   static {

   native void loadNative();

I compiled that as a fat jar and dropped it in flink/lib

Then, where I would have run System.loadLibrary(Core.NATIVE_LIBRARY_NAME), I now put Class.forName("org.rawkintrevo.cylon.opencv.LoadNative").

   val stream = env
      .map(record => {
         // System.loadLibrary(Core.NATIVE_LIBRARY_NAME)

Further, I copy the OpenCV Java wrapper (opencv/build/bin/opencv-330.jar), and library (opencv/build/lib/ in flink/lib

Then I have great success and profit.

Good hunting,


Leave a Reply

Fill in your details below or click an icon to log in: Logo

You are commenting using your account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s