Using PKCS11 with JCE means that you can use HSMs to house your private keys, which protects them from theft or misappropriation. And that's a very good thing.
With JCE, you use instances of subclasses of Key to perform cryptographic operations. Keys can be PublicKey, PrivateKey or SecretKey objects. SecretKey objects represent keys to use with a symmetric algorithm, like AES. PublicKey and PrivateKey objects comprise a key pair used for asymmetric algorithms, like RSA.
What PKCS11 does is replace SecretKey and PrivateKey objects with sham objects that represent index numbers into the HSM. When you ask JCE to perform a cryptographic operation with those keys, it instead actually delegates the task to the HSM. The HSM will look up the keys, use them internally, and return the result to you. When done that way, the secret material never leaves the HSM.
But what happens when you have to manage potentially thousands of keys? That's too many to store in the limited storage space of an HSM, and having the HSM root through its storage to find the key you want isn't what it was designed best to do.
No, instead you should use a database to store all of the keys. But the problem is that the database isn't cryptographically protected the same way an HSM is.
The solution is in the Cipher class wrap() and unwrap() methods.
What you do is you establish a SecretKey in your HSM. Give it an alias of "WrappingKey" or something of that sort. If you ask your HSM to generate such a key internally, then it will never be allowed to leave the HSM. That's a good thing. You then generate whatever keys you need and use the Cipher wrap() method to turn the private key material into an encrypted byte array. You can write that byte array to the database with confidence, since there's no use you can make of that byte array without the secret key that was used to encrypt it, which is safely encased in the HSM.
Now what if you want to use it?
You fetch the bytes from the database again and use the same Cipher object to unwrap the key. What you'll get is a PrivateKey. But as we've seen, PrivateKeys that you get from an HSM are just sham objects. You don't actually get to see that PrivateKey - it is merely a reference to the key in the HSM. You then can use the HSM to perform whatever crypto operation you need to do on that key. The HSM doesn't permanently store the unwrapped key - as soon as the sham object gets dereferenced, the PKCS11 module will tell the HSM to throw it away. But done in this way, the unencrypted form of the private key never leaves the HSM, nor does the encryption key that wraps and unwraps it. Your database can be used to keep track of the potentially millions of private keys you need to keep track of, but no human eyes will ever get to see even one of them.