IBM Support

Check for null values in rules

Question & Answer


Question

How to check for null values in ruleset to prevent NullPointerExceptions during the rule execution?

Cause

Checking for null values and initializing them is a necessary step in the implementation of a ruleset, because input objects might be null or contain null values.

For any Java application, rules developers must implement checks for null values on the objects used in the application (within rules in the case of JRules) to avoid potential NullPointerExceptions (NPEs) such as the following:

Exception in thread "main"
An exception IlrUserRuntimeException has been thrown:
Target method: public int java.lang.String.indexOf(java.lang.String)

    at condition part of rule 'test.myRule'
    at call to 'main#myTask rule task body'
    at call to 'main flow task body'
    at call to 'execute'
Target exception stack trace:
java.lang.NullPointerException: null object when invoking public int   java.lang.String.indexOf(java.lang.String)
    at ilog.rules.inset.IlrExecMethodValue.getValue(Unknown Source)
    at ilog.rules.inset.IlrExecBinaryTest.evaluate(Unknown Source)
    at ilog.rules.engine.o.evaluate(Unknown Source)
    at ilog.rules.engine.IlrDiscMem.if(Unknown Source)
    at ilog.rules.engine.IlrSingleDiscMem.k(Unknown Source)
    at ilog.rules.engine.IlrCustomDiscMem.f(Unknown Source)
    at ilog.rules.engine.IlrAlphaMem.new(Unknown Source)
    at ilog.rules.engine.IlrRuleMem.T(Unknown Source)
    at ilog.rules.engine.IlrAgenda.a(Unknown Source)
    at ilog.rules.engine.IlrEngine.fireAgendaRules(Unknown Source)
    at ilog.rules.inset.IlrExecRuleTask.a(Unknown Source)
    at ilog.rules.inset.IlrExecRuleTask.execute(Unknown Source)
    at ilog.rules.inset.IlrExecTask.run(Unknown Source)
    at ilog.rules.engine.IlrTaskEngine.execute(Unknown Source)
    at ilog.rules.inset.IlrExecTaskInstance.execute(Unknown Source)
    at ilog.rules.engine.IlrTaskEngine.executeItem(Unknown Source)
    at ilog.rules.engine.IlrTaskEngine.executeSubFlow(Unknown Source)
    at ilog.rules.inset.IlrExecFlowTask.execute(Unknown Source)
    at ilog.rules.inset.IlrExecTask.run(Unknown Source)
    at ilog.rules.engine.IlrTaskEngine.execute(Unknown Source)
    at ilog.rules.inset.IlrExecTaskInstance.execute(Unknown Source)
    at ilog.rules.engine.IlrTaskEngine.executeItem(Unknown Source)
    at ilog.rules.engine.IlrTaskEngine.if(Unknown Source)
    at ilog.rules.engine.IlrTaskEngine.a(Unknown Source)
    at ilog.rules.engine.IlrContext.a(Unknown Source)
    at ilog.rules.engine.IlrContext.execute(Unknown Source)
    at ilog.rules.studio.launching.main.IlrMain.main(IlrMain.java:184)


Objects used in the rules are either input parameter objects or objects that have been inserted in the working memory.

The rule developer must check that none of the input parameters are null, unless they are of a Java primitive type.

Indeed, if a parameter 'customer', verbalized as 'the customer' and of type test.Customer, is null and a rule author writes the following condition:
    if
      the age of the customer is more than 21

It translates in IRL (ILOG Rules Language) into the following statement, and produces an NPE on 'customer.age' because 'customer' is null:
    evaluate (customer.age > 21);


On the other hand, null objects are not inserted in the working memory, so they will never be bound in the rules. Therefore, the rule developer does not have to check if the working memory objects themselves are null.


Whether the object used in a rule is a parameter or a working memory object, the rule developer must check for nulls on its attributes and method return values of the following types:
  • Java wrapper types (i.e. java.lang.Integer, java.lang.BigDecimal...)
  • java.lang.String
  • Custom types

Only values of Java primitive types such as int and float cannot be null and therefore do not need to be checked.

Note that even if an attribute is found on the right-hand side of a comparison, it might generate an NPE if it is null. This can be due to the automatic unboxing of Java wrapper types, see the documentation for JRules 7.1 at Rule Studio > Optimizing execution > Run-time efficiency > Autoboxing.

For example, the following condition:
    if
      the age of the customer is less than the age limit of the exam

Is translated as follows in IRL:
    evaluate (person.age > exam.ageLimit.intValue());


Where 'ageLimit' is a java.lang.Integer implicitly unboxed to its Java primitive type 'int' to allow the comparison to 'age' which is of type 'int'. This is the same mechanism used in Java 5 when comparing a primitive type and its wrapper type.

Answer

The check for null values can be performed before any rule is executed or while the rules are being executed.


In any case, because the notion of null value is a technical concept, the task of checking for nulls must remain in the hands of the rule application developer.

It should not be the responsibility of rule authors when they are writing rules, as they are likely to be business analysts for whom the notion of null values is abstract.

Therefore, the check performed during the rule execution must be done implicitly: rule authors write the phrases that make sense to them from a business point of view and have checks for nulls performed in the background at execution time.

"is null"/"is not null" operators are available in BAL rules, but are not valid or available for all types. Again, business users might not know programming concepts such as null values and the order of test evaluations to use them.

In addition, rule authors would not be able to fully rely on the order in which the conditions are written in an 'and' clause, to know the order in which they will be executed. That is explained in this other technote. They could not be sure that the check for null values is executed before other tests that can potentially generate NPEs.


To check for nulls in the ruleset, before the rules are executed, you do the following:

To check for nulls while the rules are being executed, you can do the following:


The choice of a solution will depend on the application and its requirements.

Overriding operators might be less time consuming because it applies to both ruleset parameters and working memory objects. The other methods might be more tedious if the Business Object Model (BOM) contains numerous and complex classes.

On the other hand, the method of overriding operators to consider a condition 'false' if any of the data is null, only prevents potential NPE in the condition. However, it does not prevent getting NPEs in the action part of the rule ('then' or 'else').

The use of the initial actions, if you choose to continue the execution, does not cover the case when values become null during the rule execution, either through some rule actions or an external application.


Most methods offer the possibility to set the null value to a default non-null value and go on with the execution or evaluation, while other possibilities are to choose not to execute the ruleset (initial actions), not to use the object in rules (tester), or have the condition return false (overriding operators), whenever a value is null.



    To see the sample projects, extract the content of the attached zip file in one folder and import all projects in a new workspace. Refer to the readme.txt inside each project for instructions on how to test the scenario that illustrates the approach to test for null values.

--------------------


1. Using the initial actions

The initial actions of the ruleflow are edited in the start node. The test is written in IRL code, either directly in the Initial Actions or in one or more ruleset functions called there.

The test goes through the ruleset input parameters (using getParameterValue(...)) and/or the objects present in working memory (using getObjects(...)). See the IlrContext API documentation for information on those two methods.

See the JRules 7.1 documentation at Rule Studio > Orchestrating ruleset execution > Working with ruleflows > Adding initial actions and Rule Studio > Creating rule projects > Creating a function on how to set initial actions and use a function to write the IRL code.


This method allows to check if any attribute or method return value is null, or if the parameters themselves are null, in order to initialize the attributes and parameters, or choose not to execute rules by exiting the ruleflow. The last option is the most reliable option to prevent any NPE, because the fact that a method returns null can not be altered at that point. In any case, this method cannot prevent the fact that values may become null later during the rule execution, either through some rule actions or an external application.


The project "initial-action-approach" in the attached zip archive illustrates this approach.



2. Using testers on BOM classes

In this approach, the rule developer adds a tester to the BOM class to perform the check for null values in order to ensure that only objects without null attributes nor methods returning a null value are bound in the rules.

Whenever an object is bound in a rule and that the corresponding BOM class has a tester, the latter is analyzed to determine if the object instance is eligible.

In this case, it depends on whether or not it contains null values. The tester function returns 'true' if the instance is eligible to be bound to a rule instance, and 'false' otherwise. If the tester returns 'false', the object is not bound in a rule instance. This prevents a potential NPE during the execution.

For example if the BOM contains a test.Customer class with one attribute, 'name', of type java.lang.String, the following tester should be added to the test.Customer BOM class:
    return this.name != null;


In the case of attributes, you could alternatively choose to initialize nulls to default values based on your own criteria and have the tester return 'true'.

For more information on testers, see section Rule Studio > Creating rule projects > Defining how business elements map to the XOM > Mapping classes > Adding tests to filter out class instances of the JRules 7.1 documentation.


This method handles objects inserted in the working memory. If your ruleset also has parameters of a BOM type, you could choose to insert those into the working memory in the initial actions of the ruleflow. You must then not verbalize these parameters so that your rules only bind them from the working memory using automatic variables (see section Rule Studio > Writing rules > Automatic variables of the JRules 7.1 documentation). This allows to use the tester of the corresponding BOM classes to check for null values on those parameters as well.


The project "tester-approach" in the attached zip archive illustrates this approach.



3. Overriding operators

Here, the rule developer overrides basic operators on numbers and strings by creating static virtual BOM methods (for example on an utility class ) with the corresponding parameters and give them the verbalization of those operators.

For example, to override the "is less than" operator for numbers, the BOM method would look as follows:
    sample.NumberUtility.isLessThan (java.lang.Number, java.lang.Number)

This method woud be verbalized as follows:
    {0} is less than {1}


The verbalization is key to allow the new BOM method to be called in place of the default operator in the IRL code, without rule authors having to change the way that they write the rule.

For example, the following condition:
    the age of the customer is less than the age limit of the exam

would be translated as follows in IRL:
    evaluate (sample.NumberUtility.isLessThan(person.age , exam.ageLimit));


instead of
    evaluate (person.age > exam.ageLimit.intValue());

    This gives the possibility to test if the values evaluated are null, with the following BOM to XOM code for the method sample.NumberUtility.isLessThan(...):

    if(number1 != null && number2 != null){
      return number1.doubleValue() < number2.doubleValue();
    }
    return false;


To know more about the BOM to XOM mapping, see the documentation at Rule Studio > Creating rule projects > BOM and XOM for JRules 7.1.

See the section Rule Studio > Creating rule projects > Defining a vocabulary > Editing and creating phrases > Editing a phrase of the JRules 7.1. documentation for how to modify the method verbalization.


This method requires to override all operators of Numbers to check for null values on both sides of the operation, because of the possibility of auto-unboxing, and all operators of String (except "is null"/"is not null" or "is one of"/"is not one of") to check for null values on the left hand side of the comparison.


The project "operator-overriding-approach" in the attached zip archive illustrates this approach.



4. Overriding BOM getters and methods

The rule developer overrides getters and methods of the BOM by writing BOM to XOM (B2X) code to test the value before returning it to the rule execution.

For example if the BOM contains a test.Customer class with one attribute, 'name', of type java.lang.String, the following B2X code for the getter could be:
    if (this.name!=null)
      return this.name;
    else
      return "";

Every time the attribute is read, whether it is on a parameter or on a working memory object, and whether it is in the condition part, action part or another part of the rule, a null value is replaced with "" to avoid any NPE.

To know more about the BOM to XOM mapping, see the documentation at Rule Studio > Creating rule projects > BOM and XOM for JRules 7.1.


This method requires to write B2X code for all verbalized BOM getters (plus corresponding getters) and methods returning a value. The only possibility here is to return a default non-null value if a null value is found.


The project "getter-overriding-approach" in the attached zip archive illustrates this approach.

[{"Product":{"code":"SS6MTS","label":"WebSphere ILOG JRules"},"Business Unit":{"code":"BU053","label":"Cloud & Data Platform"},"Component":"General","Platform":[{"code":"PF025","label":"Platform Independent"}],"Version":"7.1;7.0;6.7","Edition":"","Line of Business":{"code":"LOB45","label":"Automation"}},{"Product":{"code":"SSQP76","label":"IBM Operational Decision Manager"},"Business Unit":{"code":"BU053","label":"Cloud & Data Platform"},"Component":null,"Platform":[{"code":"PF025","label":"Platform Independent"}],"Version":"7.5","Edition":"Enterprise","Line of Business":{"code":"LOB45","label":"Automation"}}]

Document Information

Modified date:
15 June 2018

UID

swg21585612