Stop Customizing Maximo the Old Way: Replace Third-Party Tools, Java, and Database Tricks

Series: Modern Maximo - Transforming from Legacy 7.x to MAS 9, Cloud-Native, and AI-Driven EAM | Part 4 of 12

Read Time: 12-15 minutes

Who this is for: Maximo developers, customization architects, and technical leads who currently rely on Java MBOs, database triggers, direct SQL, or third-party customization tools -- and need to understand what replaces them in MAS 9.
The bottom line: If your customization strategy depends heavily on direct database access, custom Java MBOs, or database triggers, your MAS migration will become harder to support and harder to upgrade. This blog shows you the safer path forward.

The Customization Reset

Dev: "I'll extend the WORKORDER MBO like always."
Architect: "That pattern is much harder to support in MAS. Start with automation scripts."
Dev: "But I need database access!"
Architect: "Plan around supported APIs instead of direct database access."
Dev: "This will take 3x longer!"
Architect: "It may take more redesign upfront, but it usually ages better."

The tension: Legacy patterns were fast and familiar, but MAS rewards more supportable patterns.

Quick Reference: Legacy to MAS-Safe

Legacy Pattern — MAS Replacement

Custom Java MBOs — Automation Scripts (Python/JS)

Database Triggers — Automation Scripts + Events

Direct SQL Queries — MboSet API + REST/GraphQL

JDBC Integrations — REST API + OAuth

File System Access — DOCLINKS + Object Storage

Hardcoded Config — System Properties + K8s Secrets

Why Database-Level Tools Are Dead

MAS locks down the database. SaaS model, container immutability, and operator-managed upgrades mean no more direct schema modifications.

The Five Deadly Anti-Patterns

Anti-Pattern 1: Custom MBO Extensions with Direct SQL

The Legacy 7.6.x Way:

public class MyWorkOrderSet extends WorkOrderSet {
    public void customValidation() throws MXException {
        // Direct SQL query - DANGEROUS!
        SqlFormat sql = new SqlFormat(
            "SELECT COUNT(*) FROM MAXIMO.ASSET WHERE ASSETNUM = :1"
        );
        sql.setObject(1, "ASSETNUM", "STRING", getString("ASSETNUM"));
        MXResultSet rs = getDatabase().executeQuery(sql);
        // validation logic...
    }
}

Why This Fails in MAS:

  • Direct database access is blocked in containerized environments
  • MBO extensions don't survive upgrades cleanly
  • Doesn't scale in microservices architecture
  • Completely broken in SaaS (zero database access)
  • Bypasses caching, security, and optimization layers

The MAS Way: Automation Script

# Object Launch Point: WORKORDER, Save Event, Before Save

if mbo.toBeAdded() or mbo.isModified("ASSETNUM"):
    assetnum = mbo.getString("ASSETNUM")

    # Use MboSet instead of SQL - respects security, caching, everything
    assetSet = mbo.getMboSet("ASSET")
    if assetSet.isEmpty():
        errorgroup = 'asset'
        errorkey = 'assetnotfound'

Anti-Pattern 2: Database Triggers for Business Logic

The Legacy 7.6.x Way:

CREATE TRIGGER update_asset_repair_date
AFTER UPDATE ON MAXIMO.WORKORDER
FOR EACH ROW
BEGIN
    IF :NEW.STATUS = 'COMP' THEN
        UPDATE MAXIMO.ASSET
        SET LASTREPAIREDDATE = SYSDATE
        WHERE ASSETNUM = :NEW.ASSETNUM;
    END IF;
END;

Why This Fails in MAS:

  • Database is sealed--you literally cannot add triggers
  • Triggers bypass all application logic and security
  • Invisible to API consumers and audit trails
  • Cannot be version controlled properly
  • 100% broken in SaaS

The MAS Way: Automation Script with Launch Point

# Object Launch Point: WORKORDER, Status Change, After Save

if mbo.getString("STATUS") == "COMP":
    assetnum = mbo.getString("ASSETNUM")

    if assetnum:
        assetSet = mbo.getMboSet("ASSET")
        if not assetSet.isEmpty():
            asset = assetSet.getMbo(0)
            asset.setValue("LASTREPAIREDDATE", mbo.getDate("ACTFINISH"))

Anti-Pattern 3: Direct Database Integration

The Legacy 7.6.x Way:

# External reporting tool connecting directly to Maximo DB
import cx_Oracle

connection = cx_Oracle.connect('maximo/password@maxdb')
cursor = connection.cursor()

cursor.execute("""
    SELECT WONUM, DESCRIPTION, STATUS
    FROM MAXIMO.WORKORDER
    WHERE SITEID = 'MAIN' AND STATUS IN ('WAPPR', 'APPR')
""")

Why This Fails in MAS:

  • No external database connections allowed
  • Bypasses authentication, authorization, and encryption
  • No audit trail for data access
  • Schema changes break everything
  • Completely impossible in SaaS

The MAS Way: REST API Integration

import requests

# Proper API-based integration
base_url = "https://mas-instance.example.com/maximo/oslc/os/mxwo"
headers = {
    "apikey": os.getenv("MAXIMO_API_KEY"),
    "Content-Type": "application/json"
}
params = {
    "oslc.where": 'siteid="MAIN" and status in ["WAPPR","APPR"]',
    "oslc.select": "wonum,description,status"
}

response = requests.get(base_url, headers=headers, params=params)
work_orders = response.json()["member"]
Key insight: The three most common anti-patterns -- custom MBOs with direct SQL, database triggers, and direct database integrations -- share a common flaw: they bypass the application layer entirely. In MAS, the application layer IS the only access path. Every pattern that skips it is fundamentally incompatible, not just inconvenient.

Anti-Pattern 4: File System Dependencies

The Legacy 7.6.x Way:

public void exportReport() {
    String filePath = "/opt/IBM/SMP/maximo/reports/output/report.pdf";
    File reportFile = new File(filePath);
    // Write report to filesystem
}

Why This Fails in MAS:

  • Containers have no persistent file system
  • Pods can be destroyed and recreated at any moment
  • Hardcoded paths don't exist in cloud environments
  • Doesn't scale horizontally
  • Broken in SaaS

The MAS Way: Object Storage

# Use Maximo's DOCLINKS for attachments
doclinks = mbo.getMboSet("DOCLINKS")
doc = doclinks.add()

doc.setValue("DOCUMENT", "ReportOutput")
doc.setValue("DOCTYPE", "Attachments")
doc.setValue("URLNAME", "report_" + mbo.getString("WONUM") + ".pdf")

# Or use external object storage (S3, Azure Blob, etc.)
import boto3
s3 = boto3.client('s3')
s3.upload_file('local_report.pdf', 'maximo-reports', f'reports/{wonum}.pdf')

Anti-Pattern 5: Hardcoded Configuration

The Legacy 7.6.x Way:

public class IntegrationService {
    private static final String ENDPOINT = "http://erp.company.com/api";
    private static final String API_KEY = "sk-12345abcde";  // Security nightmare!
}

Why This Fails in MAS:

  • Can't change config without recompiling
  • Credentials in source code (audit failure waiting to happen)
  • Different values needed per environment
  • Doesn't work with secrets management
  • Major security and compliance risk

The MAS Way: Property/Secret Management

# Automation Script using System Properties
from psdi.server import MXServer

endpoint = MXServer.getMXServer().getProperty("custom.erp.endpoint")
username = MXServer.getMXServer().getProperty("custom.erp.username")

# For sensitive data, use encrypted properties or external secrets
import os
api_key = os.getenv("ERP_API_KEY")  # Injected by Kubernetes secrets
Key insight: Every anti-pattern has a MAS-safe replacement. The shift is consistent: move from infrastructure-level access (filesystem, database, hardcoded config) to application-level access (APIs, DOCLINKS, system properties, Kubernetes secrets). The MAS way is more work upfront but dramatically more maintainable, scalable, and upgrade-safe.

MAS Customization Toolbox

Automation Scripts

Replaces 80% of custom Java. Use for validation, defaults, workflow logic, and integrations.

Launch Point — Use Case

Object — Business logic on records

Attribute — Field-level validation

Action — Toolbar buttons, escalations

Library — Reusable modules

Domains & Conditions

No-code logic for dynamic dropdowns and field restrictions.

Conditional UI Expressions

Show/hide fields, set read-only, or require fields based on conditions.

Integration Framework

Outbound: REST, GraphQL, Kafka, Webhooks | Inbound: REST, Kafka subscriptions

MAS-Safe Checklist

  • [ ] No direct database access or triggers
  • [ ] API-first integrations
  • [ ] No file system dependencies
  • [ ] No hardcoded values or credentials
  • [ ] Works in SaaS (stateless, scalable)
  • [ ] Upgrade-safe

ROI Summary

Short-term: 2-3x initial effort for redesign

Long-term: Quarterly upgrades (vs. 6-12 month projects), 40-60% support cost reduction, SaaS-ready, audit-compliant

Key insight: The 2-3x initial redesign investment pays for itself within 12-18 months. Organizations that modernize their customizations report 40-60% reduction in ongoing support costs, plus the ability to adopt quarterly MAS updates without the multi-month upgrade projects that plagued 7.6.x.

Key Takeaways

  1. Legacy patterns must go -- database triggers, custom Java, direct SQL are all incompatible
  2. Automation scripts replace 80% of custom Java with better maintainability
  3. API-first everything -- REST/GraphQL for integrations, no direct DB access
  4. Configuration over code -- domains, conditions, and no-code tools first

Resources for Your Journey

IBM Official

Community

Training

Previous: The 7.6 to MAS Migration Playbook

Next: Integration Modernization

Series: THINK MAS -- Modern Maximo | Part 4 of 12

About TheMaximoGuys: We help Maximo developers and teams make the transition from 7.6.x thinking to MAS mastery. We've been through the journey ourselves--and we're here to make yours smoother.

Part of the "THINK MAS" Series | Published by TheMaximoGuys | [Subscribe for Updates](#)