The GLFW callback functions have been designed to work like this:
GLFWKeyCallback cb = glfwSetKeyCallback(window, newCallbackOrNull);
if ( cb != null )
cb.release();
If you release the previous callback before doing that, what happens is that the JNI global reference stored in the FFI closure is deleted. That's why you're getting a NPE there. You also risk a segfault, since the FFI closure itself has also been released (probably getting lucky because libFFI uses a custom memory allocator).
In general, it is good practice to release callbacks after they have been "unregistered", since some callbacks may be called asynchronously. In GLFW's case that's not a problem, because callbacks (except Error and Monitor) can only be invoked in glfwPollEvents(), so I can't say that what you're doing is not legitimate.
There are two ways to fix this. a) Document the fact that .release() should only be called after replacing the callback or b) Change the GLFW callback methods to return a raw pointer (long instead of the callback object). This will change the above code to:
long cb = glfwSetKeyCallback(window, null);
if ( cb != NULL )
GLFWKeyCallback.create(cb).release();