Failed to instantiate the JavaCallout Class com.tmobile.DecryptionDocumentId

Not applicable

Java Class:

decryptioncallout.zip

Following Java class in zip is used for decryption. But getting error message.

I am using Java Callout policy where i am decrypting data but getting error message Failed to instantiate the JavaCallout Class com.tmobile.DecryptionDocumentId. not sure why, can you please have a look at it

<JavaCallout name="JavaCallout.decryptingDocumentId"> 
  <DisplayName>JavaCallout.decryptingDocumentId</DisplayName> 
  <ResourceURL>java://DecryptionDocumentId.jar</ResourceURL> 
  <ClassName>com.tmobile.DecryptionDocumentId</ClassName> 
</JavaCallout>
The Java source code is here:
package com.tmobile;

import com.apigee.flow.execution.ExecutionContext;
import com.apigee.flow.execution.ExecutionResult;
import com.apigee.flow.execution.spi.Execution;
import com.apigee.flow.message.MessageContext;
import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.SecretKey;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import javax.xml.bind.DatatypeConverter;

public class DecryptionDocumentId implements Execution {
    int iterations = 65536 ;
    int keySize = 256;
    byte[] ivBytes;
    MessageContext messageContext;
    SecretKey secretKey = messageContext.getVariable("decryptionKey") ;
    @Override public ExecutionResult execute(MessageContext messageContext, ExecutionContext executionContext){
        try {
            String documentId = messageContext.getVariable("documentId");
            char[] message = documentId.toCharArray();
            String decryptedValue = decrypt(message);
            messageContext.setVariable("documentId", decryptedValue);
            return ExecutionResult.SUCCESS;
        }
        catch (Exception e) {
            return ExecutionResult.ABORT;
        }
    }
    
    private String decrypt(char[] encryptedText) throws Exception {
        byte[] encryptedTextBytes = DatatypeConverter.parseBase64Binary(new String(encryptedText));
        SecretKeySpec secretSpec = new SecretKeySpec(secretKey.getEncoded(), "AES");
        Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
        cipher.init(Cipher.DECRYPT_MODE, secretSpec, new IvParameterSpec(ivBytes));
        byte[] decryptedTextBytes = null;
        try {
            decryptedTextBytes = cipher.doFinal(encryptedTextBytes);
        }
        catch (IllegalBlockSizeException e) {
            messageContext.setVariable("Decrypting Exception", e.toString());
            throw e;
        }
        catch (BadPaddingException e) {
            messageContext.setVariable("Decrypting1 Exception", e.toString());
            throw e;
        }
        return new String(decryptedTextBytes);
    }
}

Solved Solved
1 1 320
1 ACCEPTED SOLUTION

Yes, the problem is that you have a private field called messageContext, and when the callout class gets instantiated, you are calling a method on that field. But the value of the field has never been set. Therefore a null pointer exception occurs, during instantiation of that class.

public class DecryptionDocumentId implements Execution {
    int iterations = 65536 ;
    int keySize = 256;
    byte[] ivBytes;
    MessageContext messageContext;
    SecretKey secretKey = messageContext.getVariable("decryptionKey"); // << NPE


This is happening at the time of deployment.

This is why you see "Failed to instantiate the JavaCallout Class com.tmobile.DecryptionDocumentId."

The MessageContext will never be valid during class instantiation. The messageContext is passed to the execute method, once per invocation. Therefore messageContext is not a class member, it is a local variable passed into execute().

What you need to do: resolve the secretKey within the execute() method, once for each invocation of the callout. I also suggest these other changes:

  • catch basic mis-configurations (eg, documentId is not supplied) and set a context variable indicating same
  • Include more diagnostics in the catch clause of your execute method
  • eliminate ivBytes as a class-level variable also. It should be scoped to the invocation.

like this:

    private static final String _varPrefix = "decrypt_";
    int iterations = 65536 ;
    int keySize = 256;

    private static final String varName(String s) { return _varPrefix + s; }


    @Override public ExecutionResult execute(MessageContext messageContext, ExecutionContext executionContext){
        try {
            String documentId = messageContext.getVariable("documentId");
            if (documentId == null) {
                messageContext.setVariable(varName("reason"), "missing documentId");
                return ExecutionResult.ABORT;
            }
            char[] message = documentId.toCharArray();
            SecretKey secretKey = messageContext.getVariable("decryptionKey");
            if (secretKey == null) {
                messageContext.setVariable(varName("reason"), "missing secretKey");
                return ExecutionResult.ABORT;
            }
            String decryptedValue = decrypt(message, secretKey);
            messageContext.setVariable("documentId", decryptedValue);
            return ExecutionResult.SUCCESS;
        }
        catch (Exception e) {
            String error = e.toString();
            messageContext.setVariable(varName("error"), error);
            int ch = error.lastIndexOf(':');
            if (ch >= 0) {
                messageContext.setVariable(varName("reason"), error.substring(ch+2));
            }
            else {
                messageContext.setVariable(varName("reason"), error);
            }
            messageContext.setVariable(varName("stacktrace"), ExceptionUtils.getStackTrace(e));
            return ExecutionResult.ABORT;
        }
    }

With these changes you should be able to deploy the callout. But it will not work properly.

The reason is,... you are referring to ivBytes when you initialize the cipher. That ivBytes is not set anywhere. Moving it from a class-level field into the decrypt() method solves one problem. But it will have a null value in that method. So while the deploy will succeed, you will see another NullPointerException at runtime.

    private String decrypt(char[] encryptedText, SecretKey secretKey) throws Exception {
        byte[] encryptedTextBytes = DatatypeConverter.parseBase64Binary(new String(encryptedText));
        SecretKeySpec secretSpec = new SecretKeySpec(secretKey.getEncoded(), "AES");
        byte[] ivBytes; // ???? no value set here
        Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
        // the following will throw a NullPointerException
        cipher.init(Cipher.DECRYPT_MODE, secretSpec, new IvParameterSpec(ivBytes));
        byte[] decryptedTextBytes = null;
        decryptedTextBytes = cipher.doFinal(encryptedTextBytes);
        return new String(decryptedTextBytes);
    }

To avoid this sort of frustration in the future, I suggest that you write and run some basic unit tests of the callout class before trying to deploy and use it within Apigee Edge.

If you had run some unit tests , you would have clearly seen the console output indicating the NPE and the offending code line when instantiating the class. You would also have seen the problem with the ivBytes. Writing test code is just good practice regardless of what kind of coding you're doing.

Also with unit tests you'd be able to test the behavior of the callout with various combinations of all the inputs...like SecretKey and ivBytes and documentId.

After you get that all sorted, then you can consider using a cache to preserve the SecretKeySpec or the Cipher. SecretKeySpec is thread safe, and can be re-used across threads. Instances of the Cipher class are not thread safe so you must not re-use them across threads, but you could use a keyed pool, something like what is provided by MapMaker in Guava, to optimize the creation of new instances and make the callout faster under high concurrency.

View solution in original post

1 REPLY 1

Yes, the problem is that you have a private field called messageContext, and when the callout class gets instantiated, you are calling a method on that field. But the value of the field has never been set. Therefore a null pointer exception occurs, during instantiation of that class.

public class DecryptionDocumentId implements Execution {
    int iterations = 65536 ;
    int keySize = 256;
    byte[] ivBytes;
    MessageContext messageContext;
    SecretKey secretKey = messageContext.getVariable("decryptionKey"); // << NPE


This is happening at the time of deployment.

This is why you see "Failed to instantiate the JavaCallout Class com.tmobile.DecryptionDocumentId."

The MessageContext will never be valid during class instantiation. The messageContext is passed to the execute method, once per invocation. Therefore messageContext is not a class member, it is a local variable passed into execute().

What you need to do: resolve the secretKey within the execute() method, once for each invocation of the callout. I also suggest these other changes:

  • catch basic mis-configurations (eg, documentId is not supplied) and set a context variable indicating same
  • Include more diagnostics in the catch clause of your execute method
  • eliminate ivBytes as a class-level variable also. It should be scoped to the invocation.

like this:

    private static final String _varPrefix = "decrypt_";
    int iterations = 65536 ;
    int keySize = 256;

    private static final String varName(String s) { return _varPrefix + s; }


    @Override public ExecutionResult execute(MessageContext messageContext, ExecutionContext executionContext){
        try {
            String documentId = messageContext.getVariable("documentId");
            if (documentId == null) {
                messageContext.setVariable(varName("reason"), "missing documentId");
                return ExecutionResult.ABORT;
            }
            char[] message = documentId.toCharArray();
            SecretKey secretKey = messageContext.getVariable("decryptionKey");
            if (secretKey == null) {
                messageContext.setVariable(varName("reason"), "missing secretKey");
                return ExecutionResult.ABORT;
            }
            String decryptedValue = decrypt(message, secretKey);
            messageContext.setVariable("documentId", decryptedValue);
            return ExecutionResult.SUCCESS;
        }
        catch (Exception e) {
            String error = e.toString();
            messageContext.setVariable(varName("error"), error);
            int ch = error.lastIndexOf(':');
            if (ch >= 0) {
                messageContext.setVariable(varName("reason"), error.substring(ch+2));
            }
            else {
                messageContext.setVariable(varName("reason"), error);
            }
            messageContext.setVariable(varName("stacktrace"), ExceptionUtils.getStackTrace(e));
            return ExecutionResult.ABORT;
        }
    }

With these changes you should be able to deploy the callout. But it will not work properly.

The reason is,... you are referring to ivBytes when you initialize the cipher. That ivBytes is not set anywhere. Moving it from a class-level field into the decrypt() method solves one problem. But it will have a null value in that method. So while the deploy will succeed, you will see another NullPointerException at runtime.

    private String decrypt(char[] encryptedText, SecretKey secretKey) throws Exception {
        byte[] encryptedTextBytes = DatatypeConverter.parseBase64Binary(new String(encryptedText));
        SecretKeySpec secretSpec = new SecretKeySpec(secretKey.getEncoded(), "AES");
        byte[] ivBytes; // ???? no value set here
        Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
        // the following will throw a NullPointerException
        cipher.init(Cipher.DECRYPT_MODE, secretSpec, new IvParameterSpec(ivBytes));
        byte[] decryptedTextBytes = null;
        decryptedTextBytes = cipher.doFinal(encryptedTextBytes);
        return new String(decryptedTextBytes);
    }

To avoid this sort of frustration in the future, I suggest that you write and run some basic unit tests of the callout class before trying to deploy and use it within Apigee Edge.

If you had run some unit tests , you would have clearly seen the console output indicating the NPE and the offending code line when instantiating the class. You would also have seen the problem with the ivBytes. Writing test code is just good practice regardless of what kind of coding you're doing.

Also with unit tests you'd be able to test the behavior of the callout with various combinations of all the inputs...like SecretKey and ivBytes and documentId.

After you get that all sorted, then you can consider using a cache to preserve the SecretKeySpec or the Cipher. SecretKeySpec is thread safe, and can be re-used across threads. Instances of the Cipher class are not thread safe so you must not re-use them across threads, but you could use a keyed pool, something like what is provided by MapMaker in Guava, to optimize the creation of new instances and make the callout faster under high concurrency.