Technical Blog Post
Check for null values in rules
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...)
- 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.
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 the document entitled Conditions of BAL rules unexpectedly evaluated. 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:
- use the initial actions of the ruleflow.
To check for nulls while the rules are being executed, you can do the following:
- use testers on BOM objects
- override operators
- override BOM getters & methods
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.
Each solution listed above is explained, as well as illustrated in a sample attached to the document entitled Check for null values in rules.