Updated recently
Last updated:
Quick Answer: SailPoint IIQ BeanShell rules are small pieces of Java-like logic that run at specific points in the identity lifecycle: attribute calculation, correlation, build map, provisioning, validation and workflow steps. The hard part is not the syntax. It is knowing which objects are available, what the rule is allowed to change, and how to fail without corrupting access.
SailPoint IIQ BeanShell: Where Rules Run in IdentityIQ
A rule is not a standalone program. It runs inside a context, with a set of variables injected by IIQ. The same code can behave differently depending on whether it runs during aggregation, a refresh or a provisioning plan. Always identify the rule type before writing a line.
| Rule type | Typical use | Risk |
|---|---|---|
| Identity attribute | Derive department, manager or email | Slow external calls on every refresh |
| Correlation | Match an account to an identity | Bad fallback creates wrong links |
| Build map | Map source attributes to the IIQ schema | Silent nulls and type mismatches |
| Provisioning policy | Set attributes in a provisioning plan | Missing required target fields |
| Before/after provisioning | Validate or react to a plan | Blocking every request with a broad condition |
| Workflow | Approve, route or transform data | Infinite loops and lost approvals |
| Connector | Custom target operations | Pagination, retries and rate limits |
Example 1: Identity Attribute Rule
Use an identity attribute rule when the value can be derived from data already on the identity. Keep it deterministic; an attribute rule that calls an external API for every identity will turn a refresh into a network outage.
import sailpoint.object.Identity;
Identity identity = context.getObjectByName(Identity.class, "jdoe");
if (identity == null) {
return null;
}
String department = identity.getStringAttribute("department");
if (department == null || department.trim().isEmpty()) {
return "UNASSIGNED";
}
return department.trim().toUpperCase();
In a real identity attribute rule, the current identity is usually available through the rule context rather than a hard-coded name. The pattern is the same: null check, normalize, return a predictable value.
Example 2: Correlation Rule
Correlation decides which identity owns an account. Use a deterministic identifier first, then a reviewed fallback. Never match on display name alone.
import sailpoint.object.Identity;
String employeeId = account.getStringAttribute("employeeId");
if (employeeId != null) {
Identity byEmployeeId = context.getObject(Identity.class, "employeeId", employeeId);
if (byEmployeeId != null) {
return byEmployeeId;
}
}
String email = account.getStringAttribute("email");
if (email != null) {
return context.getObject(Identity.class, "email", email);
}
return null; // Leave orphaned for review rather than guessing.
The exact query API depends on your IIQ version and rule context. The design rule does not: a fallback that returns the wrong person is worse than an orphan account.
Example 3: Build Map Rule
A build map rule transforms source attributes into the application schema. Treat every input as nullable and log only what you need; do not dump personal data into logs.
import java.util.HashMap;
import java.util.Map;
Map map = new HashMap();
map.put("accountName", source.getStringAttribute("uid"));
map.put("displayName", source.getStringAttribute("cn"));
map.put("email", source.getStringAttribute("mail"));
map.put("employeeId", source.getStringAttribute("employeeNumber"));
if (map.get("accountName") == null) {
throw new Exception("Missing uid for account");
}
return map;
Example 4: Provisioning Policy
A provisioning policy field rule supplies a value when IIQ builds an account request. Keep the logic close to the target schema; do not invent attributes the connector cannot send.
import sailpoint.object.Identity;
Identity identity = plan.getIdentity();
if (identity == null) {
return null;
}
String email = identity.getEmail();
if (email == null || email.isEmpty()) {
return identity.getName() + "@example.com";
}
return email;
Check the target system’s required fields and length limits. A policy that returns a 60-character display name to a 20-character legacy field will fail at provisioning time, not at design time.
Example 5: Before Provisioning Rule
Use a before-provisioning rule to validate a plan, not to silently rewrite half of it. If the rule blocks a request, the error message must tell the requester what to fix.
import sailpoint.object.ProvisioningPlan;
// The injected variable is usually named plan; confirm in your rule context.
ProvisioningPlan currentPlan = (ProvisioningPlan) plan;
if (currentPlan == null) {
return;
}
if (currentPlan.getAccountRequest() != null) {
String target = currentPlan.getAccountRequest().getApplicationName();
if ("SensitiveApp".equals(target)) {
throw new Exception("SensitiveApp access requires a manual approval.");
}
}
The variable name and cast vary by rule context. The principle is to fail closed and explain why. A vague “validation failed” message creates a ticket, not a fix.
Example 6: Custom Validation Rule
Validation rules are useful for request forms: check a cost centre, a manager, a date range or a combination that the standard UI cannot express. Keep validation fast; it runs while the user is waiting.
String costCenter = form.getString("costCenter");
if (costCenter == null || !costCenter.matches("[A-Z]{3}-[0-9]{4}")) {
return "Cost centre must look like ABC-1234.";
}
return null; // null means valid in many IIQ validation contexts.
Example 7: Workflow Step Rule
Workflow rules should be small and idempotent. If a step runs twice because of a retry, it should not create duplicate requests or send duplicate approvals.
import sailpoint.object.Workflow;
// The injected variable is usually named workflow; confirm in your rule context.
Workflow currentWorkflow = (Workflow) workflow;
if (currentWorkflow == null) {
return;
}
String requester = currentWorkflow.getRequester();
if (requester == null) {
throw new Exception("Workflow has no requester");
}
currentWorkflow.put("approvalOwner", "IAM-APPROVERS");
Example 8: Email Notification Rule
Email rules often fail silently because the template references a missing variable. Build the body from validated values and log the message ID, not the full personal data.
String name = identity.getDisplayName();
String app = (String) request.getAttribute("applicationName");
if (name == null) {
name = identity.getName();
}
return "Hello " + name + ", your access request for " + app + " is pending approval.";
Example 9: Connector Operation Rule
Custom connector rules handle operations the standard connector cannot. They are production code: paginate, retry, respect rate limits and surface a useful error. Do not swallow an exception and return an empty list; that looks like “no accounts” and hides a broken integration.
try {
return client.listAccounts(pageSize, cursor);
} catch (RateLimitException e) {
Thread.sleep(2000);
return client.listAccounts(pageSize, cursor);
} catch (Exception e) {
log.error("Account listing failed", e);
throw e;
}
Example 10: Logging and Debugging
Log the decision, not the entire object. A rule that logs every identity attribute creates noise and may expose personal data. Use the rule name, the identity identifier and the branch taken.
log.debug("Correlation fallback used for account=" + accountName);
log.warn("No employeeId for account=" + accountName + "; leaving orphaned");
In production, debug logging should be temporary. A forgotten debug line on a high-volume aggregation can fill a disk in hours.
How to Test a Rule Safely
Test the rule at the same lifecycle point where it will run. A correlation rule tested by calling it from a standalone script will not prove that it handles the aggregation context. Use a small set of test identities that includes a normal user, a contractor, a rehire, a duplicate name and an account with a missing employee ID.
Capture the input and output before you change the rule. Then change one branch at a time. If the rule writes or provisions, test the plan without executing it first. Check the logs for the branch taken and confirm that the fallback does not quietly assign access to the wrong person.
For high-volume rules, measure the runtime on a representative batch. A rule that takes 20 ms per account is fine for 500 accounts and painful for 200,000. If the rule calls an external service, test the timeout and the failure path, not just the successful response.
Rule Review Checklist
| Check | Question | Why it matters |
|---|---|---|
| Context | Which rule type is this, and which variables are guaranteed? | Wrong assumptions cause null-pointer failures |
| Inputs | Can every input be null, empty or malformed? | Production data is messier than test data |
| Side effects | Does the rule write, provision or call an external system? | Retries can duplicate actions |
| Failure | Does it fail closed with a useful message? | Silent defaults hide access problems |
| Performance | Does it run per identity or per account? | Small inefficiencies multiply at scale |
| Logging | Does it log identifiers without personal data? | Privacy and disk-space risk |
| Rollback | Can the change be reverted without data loss? | Production changes need an exit |
A Note on Rule Context and Versioning
Copying a rule from a blog post into production is risky because the context variables are not universal. A rule that works in an identity attribute context may not have the same `account` or `plan` object in a workflow context. Check the rule type, the injected variables and the return type before you adapt the code.
Version every rule outside the IIQ console. Keep the XML export, the change reason and the test evidence together. When an upgrade or a hotfix goes wrong, the ability to compare the current rule with the last known good version is worth more than a clever one-liner.
Rules That Fail Safely
- Null-check every external value before calling a method on it.
- Prefer deterministic identifiers over fuzzy matching.
- Do not call a remote API per account unless you have caching and a timeout.
- Throw a specific error when the data is invalid; do not return a silent default.
- Make retries idempotent.
- Log the branch taken and the identifier, not the full record.
- Test with duplicate names, missing attributes, orphans and a target-system failure.
War Story: The Rule That Passed Test and Broke Production
A developer wrote a correlation rule that fell back to email. In test, every account had a unique email. In production, contractors shared a generic mailbox, so 60 accounts correlated to the same identity. The rule was technically correct; the fallback was unsafe.
The fix was to restrict the email fallback to accounts with a valid employee type and to route the rest to a manual review queue. The lesson: test with the messy data, not the clean sample. A correlation rule is a data-quality control, not just a matching function.
Training Resources
If you want to practise these rules in a working IIQ lab, SkilBrill’s SailPoint IdentityIQ training covers BeanShell, provisioning, workflows and debugging. For the surrounding identity model, the IAM training track is a useful foundation.
Read next: IIQ tutorial, architecture, and workflow examples.
FAQ
Is BeanShell the same as Java?
BeanShell is a Java-like scripting language that runs on the JVM. IIQ rules use Java syntax and objects, but the runtime and available context variables are specific to IdentityIQ.
Where do I find the variables available in an IIQ rule?
The variables depend on the rule type. Check the rule context in the IIQ documentation and inspect a working rule of the same type. Do not assume a variable exists because it worked in a different rule.
Can a BeanShell rule call an external API?
Yes, but do it carefully. Add timeouts, handle failures and avoid a remote call for every account in a high-volume aggregation. Cache reference data where possible.
How do I debug a failing IIQ rule?
Check the task result and the server log for the exception stack trace. Add temporary debug logging around the branch you suspect, reproduce with one identity, then remove the verbose logging.
Are custom connectors better than standard connectors?
No. Use a supported connector when it meets the requirement. A custom connector adds maintenance, testing and upgrade risk, so it should be a deliberate last resort.
