Maximo Automation Scripts: From Launch Points to Production Patterns

Maximo Automation Scripts: From Launch Points to Production Patterns

Share

Automation scripts are the most powerful customization mechanism in the Maximo ecosystem. They let you extend and modify system behavior using Jython (Python on the JVM) or JavaScript without touching the core Java codebase. This means your customizations survive upgrades, stay within the supported configuration layer, and can be managed entirely through the Maximo UI.

But power without discipline creates chaos. Every Maximo consultant has encountered a production system littered with undocumented scripts, silent exception handlers, and launch points that fire on every save with no clear purpose. This article is about doing it right: understanding every launch point type, writing production-grade error handling, leveraging the new Bob AI tool for script modernization, and knowing when a script is the wrong answer.

The Launch Point Taxonomy

A launch point is the trigger that fires your script. Maximo provides five distinct launch point types, each with its own execution context, available variables, and appropriate use cases. Understanding the differences is the foundation of effective scripting.

Object Launch Points

Object launch points fire when a record is created, saved, or deleted. They are the most commonly used launch point type and the most frequently misused.

Events available:
- Initialize: Fires when a new record is first created, before any data is populated. Use this to set default values that depend on complex logic.
- Validate: Fires during save, before the record is committed. Use this for cross-field validation that cannot be expressed through standard conditional UI.
- Save: Fires after validation but before the database commit. Use this for derived field calculations, audit logging, or triggering related record updates.
- After Save: Fires after the database commit. Use this for integrations, notifications, or any action that should only occur if the save succeeded.

Available implicit variables:
- mbo: The current MBO (Maximo Business Object) record
- mboName: The object name as a string
- user: The current user name
- app: The current application name (may be null in non-UI contexts)

Example: Auto-generating a follow-up work order from a failed inspection:

# Object Launch Point: INSPFIELDRESULT - Save - After Save
# Generates a follow-up work order when an inspection result fails

from psdi.mbo import MboConstants
from psdi.server import MXServer

def main():
    # Only proceed if the inspection result indicates failure
    result_value = mbo.getString("RESULTVALUE")
    if result_value != "FAIL":
        return

    # Check if a follow-up work order already exists
    existing_wo = mbo.getString("FOLLOWUPWO")
    if existing_wo:
        return

    # Get the parent inspection record
    inspection = mbo.getOwner()
    if inspection is None:
        return

    # Create the follow-up work order
    wo_set = MXServer.getMXServer().getMboSet(
        "WORKORDER", 
        mbo.getUserInfo()
    )

    new_wo = wo_set.add()
    new_wo.setValue("DESCRIPTION", 
        "Follow-up: Failed inspection on " + inspection.getString("ASSETNUM"))
    new_wo.setValue("ASSETNUM", inspection.getString("ASSETNUM"))
    new_wo.setValue("LOCATION", inspection.getString("LOCATION"))
    new_wo.setValue("WOPRIORITY", 1)
    new_wo.setValue("WORKTYPE", "CM")
    new_wo.setValue("REPORTEDBY", user)

    # Set the GL account from the asset or location
    asset_set = mbo.getMboSet("ASSET")
    if not asset_set.isEmpty():
        asset = asset_set.getMbo(0)
        new_wo.setValue("GLACCOUNT", asset.getString("GLACCOUNT"))

    wo_set.save()

    # Link back to the inspection
    mbo.setValue("FOLLOWUPWO", new_wo.getString("WONUM"))

    wo_set.close()

main()

Attribute Launch Points

Attribute launch points fire when a specific field value changes. They are more surgical than object launch points and should be preferred when the logic is scoped to a single field.

Events available:
- Initialize: Fires when the attribute is first displayed. Use for dynamic defaulting.
- Validate: Fires when the attribute value changes. Use for field-level validation.
- Action: Fires on a specific user action tied to the attribute.

Available implicit variables:
- mbo: The current MBO
- app: The current application name
- value: The new value being set (available in Validate event)

Example: Dynamic default for asset priority based on criticality:

# Attribute Launch Point: ASSET.PRIORITY - Initialize
# Sets default priority based on asset criticality field

def main():
    criticality = mbo.getString("CRITICALITY")

    priority_map = {
        "CRITICAL": 1,
        "HIGH": 2,
        "MEDIUM": 3,
        "LOW": 4
    }

    if criticality in priority_map:
        mbo.setValue("PRIORITY", priority_map[criticality])

Action Launch Points

Action launch points are triggered by user-initiated actions: button clicks, select actions, or custom dialog invocations. They are the mechanism for building interactive customizations.

Key characteristics:
- Explicitly invoked by the user, not automatic
- Can be tied to a signature option in Application Designer
- Ideal for custom dialogs, bulk operations, and complex multi-step processes

Example: Custom dialog invocation pattern:

# Action Launch Point: CUSTOMDIALOG.INVOKE
# Handles the OK button on a custom dialog

from psdi.mbo import MboConstants
from psdi.server import MXServer

def main():
    # The dialog passes selected values through the MBO
    assetnum = mbo.getString("ASSETNUM")
    location = mbo.getString("LOCATION")

    if not assetnum and not location:
        errorgroup = "custom"
        errorkey = "noAssetOrLocation"
        params = []
        raise MXException(errorgroup, errorkey, params)

    # Perform the business logic
    wo_set = MXServer.getMXServer().getMboSet(
        "WORKORDER", 
        mbo.getUserInfo()
    )

    new_wo = wo_set.add()
    new_wo.setValue("DESCRIPTION", "Custom dialog generated work order")
    new_wo.setValue("ASSETNUM", assetnum)
    new_wo.setValue("LOCATION", location)
    wo_set.save()
    wo_set.close()

Custom Condition Launch Points

Custom condition launch points are used within workflows to evaluate complex routing logic that cannot be expressed through SQL-based conditions. They return a boolean value that determines which workflow path to follow.

Available implicit variables:
- mbo: The current MBO being processed by the workflow
- evalresult: A boolean variable you set to True or False to control routing

Example: Complex workflow routing based on multiple conditions:

# Custom Condition Launch Point: WORKORDER.ROUTE_DECISION
# Routes work orders based on priority, work type, and asset criticality

def main():
    priority = mbo.getInt("WOPRIORITY")
    worktype = mbo.getString("WORKTYPE")

    # Get asset criticality
    asset_set = mbo.getMboSet("ASSET")
    criticality = "LOW"
    if not asset_set.isEmpty():
        asset = asset_set.getMbo(0)
        criticality = asset.getString("CRITICALITY")

    # Route to emergency path if priority 1 and critical asset
    if priority <= 1 and criticality == "CRITICAL":
        evalresult = True
        return

    # Route to expedited path if priority 2 and CM work type
    if priority <= 2 and worktype == "CM":
        evalresult = True
        return

    # Default: standard path
    evalresult = False

Integration Launch Points

Integration launch points fire during inbound or outbound integration processing. They allow you to transform data, validate incoming records, or enrich outgoing messages.

Events available:
- Inbound: Fires before an inbound integration message is processed
- Outbound: Fires before an outbound integration message is sent

Available implicit variables:
- mbo: The current MBO (inbound) or the MBO being published (outbound)
- user: The integration user
- app: The integration application name

Production-Grade Error Handling

The single most common failure pattern in Maximo automation scripts is silent exception handling. A script catches an exception, logs nothing, and continues as if nothing happened. The result is data corruption that may not be discovered for weeks or months.

The Anti-Pattern

# DO NOT DO THIS
try:
    critical_operation()
except:
    pass  # Silent failure - data corruption waiting to happen

The Production Pattern

from psdi.util.logging import MXLoggerFactory
import traceback

logger = MXLoggerFactory.getLogger("maximo.script")

def main():
    try:
        critical_operation()
    except Exception as e:
        # Log the full stack trace
        logger.error("Script CUSTOM_SCRIPT failed: " + str(e))
        logger.error(traceback.format_exc())

        # Set a user-visible error message
        errorgroup = "custom"
        errorkey = "scriptExecutionFailed"
        params = [str(e)]

        # Re-raise as MXException so the transaction rolls back
        from psdi.util import MXException
        raise MXException(errorgroup, errorkey, params)

Key principles:

  1. Always log the full stack trace. A one-line error message is not enough to diagnose production issues.
  2. Use a consistent logger name. The pattern maximo.script or maximo.automation.YOUR_SCRIPT_NAME makes log filtering straightforward.
  3. Re-raise as MXException. This ensures the transaction rolls back. A script that catches an exception and continues has silently corrupted data.
  4. Include context in the error message. The MBO name, key values, and user context help with troubleshooting.
  5. Never catch bare except:. Always catch Exception (or a specific exception type) so system-level exceptions like KeyboardInterrupt are not swallowed.

Structured Error Handling for Integration Scripts

Integration scripts have a special consideration: you may want to reject a single record without aborting the entire integration batch. The pattern for this:

from psdi.iface.mic import MicConstants

def main():
    try:
        # Validate the incoming record
        if not is_valid(mbo):
            mbo.setValue("SKIP", True, MicConstants.NO_ACCESSCHECK)
            return
    except Exception as e:
        logger.error("Integration validation failed: " + str(e))
        mbo.setValue("SKIP", True, MicConstants.NO_ACCESSCHECK)
        # Do not re-raise - allow the batch to continue

Bob: AI-Assisted Script Modernization

In June 2026, IBM introduced Bob, an AI-assisted development tool specifically trained on Maximo coding patterns and best practices. Bob is not a generic code generator. It is a Maximo-specific skill that analyzes existing automation scripts, identifies issues, and suggests improvements.

Bob's workflow follows a five-stage pipeline:

  1. Discover: Reads existing automation scripts from the Maximo environment, including configuration scripts, object launch points, attribute launch points, and integration scripts.
  2. Analyze: Reviews each script for outdated patterns, risky constructs, and inefficiencies. Bob looks for repeated code blocks, missing exception handling, hardcoded values, deprecated API calls, and performance bottlenecks.
  3. Optimize: Suggests cleaner, more maintainable logic. This includes simplifying complex conditionals, reducing duplicate scripts, improving readability, and replacing deprecated patterns with current best practices.
  4. Validate: Ensures the optimized script produces the same behavior as the original. This step is critical in enterprise environments where a script change can affect thousands of transactions.
  5. Update: Once reviewed and approved, the changes are written back through a governed process. Bob does not modify production scripts without human review.

Bob is particularly valuable for organizations with large script inventories accumulated over years of Maximo usage. Scripts written for Maximo 7.5 or 7.6 often use patterns that are suboptimal or deprecated in MAS 9.x. Bob can identify these and suggest modern equivalents.

Scripts vs. Workflows vs. Escalations: A Decision Framework

Not every automation need requires a script. Maximo provides three primary automation mechanisms, and choosing the wrong one creates technical debt.

Criterion Automation Script Workflow Escalation
Trigger Real-time (save, field change, action) Real-time (route a record) Scheduled (cron-based)
Complexity High (arbitrary logic) Medium (routing, approvals) Low to medium (batch operations)
Performance impact Per-transaction Per-route step Batch, off-peak
Maintainability Requires coding skills Visual designer, lower skill bar SQL or simple scripts
Transaction context Full MBO access Limited to workflow variables Limited to escalation context
Use case Complex validation, integrations, custom UI Approvals, status transitions, notifications Batch updates, periodic sync, cleanup

When to use an automation script:
- The logic involves complex conditionals or calculations
- You need to interact with multiple related MBOs
- You are building a custom UI interaction (dialog, button)
- You are integrating with an external system via HTTP
- The logic cannot be expressed in the workflow designer

When to use a workflow:
- The process involves human approval steps
- The logic is primarily routing a record through status transitions
- You need an audit trail of who did what and when
- Non-technical users need to modify the process

When to use an escalation:
- The operation runs on a schedule, not in real time
- You are processing records in bulk
- The logic is simple (set a field, send a notification, close stale records)
- You want to offload work to off-peak hours

The most common mistake is using an object launch point on save for logic that should be an escalation. If the operation scans and updates many records, it does not belong in a per-transaction script. Move it to an escalation that runs nightly.

Script Performance Considerations

Automation scripts execute within the Maximo application server's JVM. Poorly written scripts can degrade performance for all users. Here are the patterns to watch:

Avoid N+1 Queries

# BAD: N+1 query pattern
wo_set = mbo.getMboSet("WOCHILDREN")
for i in range(wo_set.count()):
    child = wo_set.getMbo(i)
    # Each getMboSet call inside the loop is a separate database query
    child_assets = child.getMboSet("ASSET")
    # ...

# GOOD: Use relationship traversal or batch fetching
wo_set = mbo.getMboSet("WOCHILDREN")
wo_set.setQbe("status", "=WAPPR")
wo_set.reset()
# Process the filtered set without additional queries per record

Cache MXServer References

# BAD: Getting MXServer reference inside a loop
for assetnum in asset_list:
    mxserver = MXServer.getMXServer()  # Unnecessary repeated call
    # ...

# GOOD: Get it once
mxserver = MXServer.getMXServer()
for assetnum in asset_list:
    # Use the cached reference

Close MBO Sets

Every getMboSet() call that is not a relationship traversal should be explicitly closed:

wo_set = MXServer.getMXServer().getMboSet("WORKORDER", userInfo)
try:
    # Process the set
    pass
finally:
    wo_set.close()  # Always close to release database resources

Practical Implications

Script inventory and governance. Every organization running Maximo should maintain an inventory of all automation scripts, including their launch point type, associated object, last modified date, and business purpose. Without this, script modernization is guesswork.

Bob as a force multiplier. For organizations with dozens or hundreds of scripts, Bob can reduce the modernization effort from months to weeks. The key is to run Bob in a non-production environment first, review its suggestions carefully, and validate before applying changes.

Exception handling is non-negotiable. Every script in production should have explicit exception handling with logging and transaction rollback. This is not a nice-to-have. It is the difference between a script that fails safely and one that silently corrupts data.

Launch point selection matters. Using an object launch point when an attribute launch point would suffice means your script fires on every save, not just when the relevant field changes. This is wasted CPU cycles and increased risk of unintended side effects.

The upgrade argument. One of the strongest selling points for automation scripts over Java customizations is upgrade survivability. Scripts live in the database and are not affected by application binary updates. But this only holds if the scripts use supported APIs. Scripts that reach into internal classes or use reflection may break across releases just like Java customizations.

Bottom Line

Automation scripts are the right answer for most Maximo customizations in 2026. They are upgrade-safe, manageable through the UI, and powerful enough to handle complex business logic. But they require discipline: proper launch point selection, production-grade error handling, performance awareness, and a governance framework that prevents script sprawl.

Bob represents a step change in script maintainability. Organizations that adopt it early will reduce their technical debt and free up developers for higher-value work. Those that ignore it will continue accumulating scripts that become harder to modernize with each passing release.

The decision framework is simple: use scripts for complex real-time logic, workflows for human-driven processes, and escalations for scheduled batch operations. Get the launch point right. Handle every exception. Close every MBO set. And when in doubt, ask Bob.

Read more