Chapter 1
What is Moqui?
Moqui is an open-source, Java-based application framework for building enterprise business applications. Think of it as a powerful toolkit that handles all the boring plumbing (database, security, transactions, UI) so you can focus purely on writing business logic.
Core Technology Stack
| Technology | Role | Why Used |
| Java | Runtime language, JVM execution | Stability, performance, enterprise-grade |
| Groovy | Dynamic scripting on JVM, service logic | Concise, Java-compatible, no recompile |
| XML | Define everything — entities, services, screens | Declarative, readable, centralised |
| FreeMarker (FTL) | HTML template rendering | Powerful templating, data-driven UI |
| JDBC | Database connectivity layer | Standard Java DB bridge, works with any DB |
| JTA / Bitronix | Transaction management | ACID compliance, automatic rollback |
| HikariCP | Connection pooling | Fast, low-latency DB connection reuse |
| JCache / Hazelcast | Caching layer | Reduces DB hits, improves performance |
The Three Golden Rules of Moqui
| Rule | What It Means |
| Convention over Configuration | Moqui guesses sensible defaults so you write less boilerplate. E.g., entity MoquiTutorial → table MOQUI_TUTORIAL automatically. |
| Everything is XML | Entities, services, screens, forms, security rules — all declared in XML. No annotations, no code sprawl. |
| Declarative, not Imperative | You describe WHAT you want ("create a Party record"), not HOW to do it (no manual SQL, no manual transaction.begin()). |
Chapter 2
How Moqui Works — The 30,000-Foot View
Every request in Moqui travels through exactly 5 layers. Each layer has one job and one job only.
↓ HTTP Request
📄 Screen (UI — XML)
ScreenDefinition
↓ Service Call
⚙️ Service (Business Logic — Groovy/XML)
ServiceDefinition
↓ Entity Operation
🗄️ Entity Engine (ORM — XML)
EntityDefinition
↓ JDBC / SQL
💾 Database (MySQL / PostgreSQL)
JDBC / SQL
📝
Nothing ever skips a layer. The UI never directly touches the DB. Services never build raw SQL. This is the entire philosophy of Moqui in one diagram.
| Layer | Job | Key Object |
| Browser | User clicks, submits forms, sends HTTP requests | HTTP Request |
| Screen | Defines what the user sees (UI in XML), calls services, handles navigation | ScreenDefinition |
| Service | Contains ALL business logic — validation, rules, calculations | ServiceDefinition |
| Entity Engine | Converts XML entity definitions → SQL, manages CRUD | EntityDefinition |
| Database | Physically stores and retrieves data (MySQL, PostgreSQL, etc.) | JDBC / SQL |
Chapter 3
Boot Sequence — How Moqui Starts Up
When you run ./gradlew run, Moqui goes through a precise startup sequence inside ExecutionContextFactoryImpl (ECFI). Think of ECFI as the restaurant building that opens every morning and sets up everything before the first customer arrives.
| # | What Happens | Key Class |
| 1 | Read MoquiInit.properties → finds runtime/ directory and conf file path | ExecutionContextFactoryImpl |
| 2 | Parse MoquiDefaultConf.xml → base configuration tree (MNode in memory) | initBaseConfig() |
| 3 | initComponents() → scan component-list, load each component directory | initComponents() |
| 4 | initConfig() → merge component MoquiConf.xml + runtime conf into final config | initConfig() |
| 5 | initClassLoader() → build MClassLoader + GroovyClassLoader chain | initClassLoader() |
| 6 | new CacheFacadeImpl() → create JCache/Hazelcast cache buckets | CacheFacadeImpl |
| 7 | new ResourceFacadeImpl() → FTL renderer, Groovy script runner, resource locators | ResourceFacadeImpl |
| 8 | new TransactionFacadeImpl() → transaction manager (Bitronix by default) | TransactionFacadeImpl |
| 9 | new EntityFacadeImpl() → entity definitions loaded from ALL entity/*.xml files | EntityFacadeImpl |
| 10 | new ServiceFacadeImpl() → service runners registered; SECA rules loaded | ServiceFacadeImpl |
| 11 | new ScreenFacadeImpl() → screen caches initialized; FTL widget templates loaded | ScreenFacadeImpl |
| 12 | postFacadeInit() → warm caches, start scheduled job runner, everything ready | postFacadeInit() |
📝
ECFI is created once when the server starts. It lives until the server shuts down. All facades hang off this one object.
Chapter 4
Configuration Files — How They Load
Moqui configuration is layered. Each layer overrides the previous one. Think of it like CSS specificity — more specific configs win.
MoquiDefaultConf.xml
Framework built-in defaults — never edit this
↓ overridden by
MoquiDevConf.xml
Your dev overrides: DB creds, debug flags, local JDBC settings
↓ overridden by
MoquiActualConf.xml
Production overrides: real DB host, passwords, production DB
↓ merged with
Component MoquiConf.xml
Per-component config — datasource groups, entity groups
JDBC Configuration Example
<datasource name="defaultDS" database-conf-name="mysql">
<inline-jdbc
jdbc-driver="com.mysql.cj.jdbc.Driver"
jdbc-uri="jdbc:mysql://127.0.0.1:3306/moqui"
jdbc-username="moqui"
jdbc-password="@12345abC"/>
</datasource>
Chapter 5
XML — The Language of Moqui
In Moqui, XML is not "config". It is the programming language. You don't write Java classes to define a database table — you write XML. You don't write controller methods — you write XML transitions.
Adarsh's Types of XML Artifacts
| XML Type | File Pattern | What It Defines |
| Entity XML | entity/*.xml | Database tables, columns, relationships, indexes, seed data |
| Service XML | service/*.xml | Business operations — inputs, outputs, logic, transactions |
| Screen XML | screen/*.xml | UI pages, forms, lists, navigation, transitions |
| SECA XML | *.secas.xml | Event → Condition → Action rules (auto-trigger services) |
| Data XML | data/*.xml | Seed and demo data records loaded into DB on startup |
| REST XML | *.rest.xml | Exposes services as REST API endpoints |
The Fundamental Rule: XML is NEVER Executed Directly
↓ Parsed by MNode parser (SAX-based, fast)
↓ Converted to Definition Objects
EntityDefinition / ServiceDefinition / ScreenDefinition
↓ Actions compiled to Groovy bytecode
JVM Class (cached forever)
↓ Executed on every request
📝
The XML file is read once. The result is cached in memory. The original file is never re-read at runtime unless you're in dev mode.
Chapter 6
Facade Pattern — The Backbone of Moqui
A Facade is a simple, clean front door to a complex system. Instead of writing 50 lines of JDBC code to run a query, you call ec.entity.find("party.Party").list() — and Moqui handles the 50 lines for you.
| Facade | You Write | What It Hides |
EntityFacade ec.entity | ec.entity.find("party.Party").list() | SQL generation, JDBC connections, connection pooling, result set mapping, caching |
ServiceFacade ec.service | ec.service.sync().name("create#Person").call() | Service discovery, parameter validation, transaction management, SECA rules |
ScreenFacade ec.screen | Auto-rendered from XML | FreeMarker templates, form rendering, navigation, widget rendering |
TransactionFacade | Auto-managed | JTA transaction begin/commit/rollback, XA transactions, savepoints |
CacheFacade ec.cache | ecfi.cacheFacade.getCache("my.cache") | JCache internals, eviction policies, expiry, distributed caching |
ResourceFacade ec.resource | ec.resource.groovy.script() | File reading, FTL rendering, Groovy compilation, resource locators |
LoggerFacade ec.logger | ec.logger.info("msg") | Log4j2 internals, log levels, log routing |
📝
All facades are created once at boot inside ECFI and shared across ALL requests. They are thread-safe. You never create a facade — you always use the one ec gives you.
Chapter 7
ExecutionContext (ec) — Your Runtime Buddy
ExecutionContext is the most important object you'll use in Moqui. It is your handle to everything — the current user, the database, services, the web request, error messages, and all facades. It lives for exactly ONE request and is destroyed after.
The Restaurant Analogy
| Moqui Concept | Restaurant Equivalent | Key Behaviour |
| ExecutionContextFactory (ECFI) | The restaurant building | One per server. Never changes. Lives forever. |
| ExecutionContext (EC) | One customer's table + waiter | One per request/thread. Created fresh. Destroyed after request. |
| Facade (EntityFacade etc.) | Kitchen department head | One per server. Shared. Thread-safe. Accessed via ec.entity etc. |
| Definition (EntityDefinition) | The recipe card | Created from XML once. Cached permanently. Never changes. |
| ContextStack | Waiter's notepad | Variables set during this request only. Read top-to-bottom. |
What EC Holds (Per-Request State)
| Property | Type | What It Carries |
ec.user | UserFacadeImpl | Current logged-in user, roles, permissions |
ec.context | ContextStack | All variables in scope for this request (like a stack of Maps) |
ec.message | MessageFacadeImpl | Errors and info messages accumulated during this request |
ec.web | WebFacadeImpl | HTTP request object, response, session, cookies, URL params |
ec.entity | EntityFacadeImpl | Database operations — shared facade, accessed per-request |
ec.service | ServiceFacadeImpl | Service calls — shared facade, accessed per-request |
⚠️
EC is NEVER shared between threads. Two simultaneous users each get their own EC. But both share the same EntityFacade, ServiceFacade, etc.
Chapter 8
Entity Engine — The Database Layer
The Entity Engine is Moqui's built-in ORM. You write zero SQL. You define your database tables in XML, and the Entity Engine does everything else — creates tables, runs queries, manages relationships, handles caching.
Core Entity Engine Operations
| Operation | Moqui Code | Generated SQL |
| Create | ec.entity.makeValue("party.Party").setAll([...]).create() | INSERT INTO PARTY (...) VALUES (...) |
| Read One | ec.entity.find("party.Party").condition("partyId","P1").one() | SELECT * FROM PARTY WHERE PARTY_ID = 'P1' |
| Read Many | ec.entity.find("party.Person").list() | SELECT * FROM PERSON |
| Update | partyValue.set("statusId","INACTIVE").update() | UPDATE PARTY SET STATUS_ID='INACTIVE' WHERE PARTY_ID='P1' |
| Delete | partyValue.delete() | DELETE FROM PARTY WHERE PARTY_ID = 'P1' |
| Auto-service create | ec.service.sync().name("create#party.Party").call() | INSERT INTO PARTY (...) — zero code needed |
Entity Engine vs Raw JDBC vs Hibernate
| Feature | Raw JDBC | Hibernate | Moqui Entity Engine |
| SQL Writing | Manual (you write every query) | Generated from annotations | Generated from XML definition |
| Boilerplate | Very high (connections, PS, RS) | Medium (session management) | None — just call find/create |
| Configuration | Manual everywhere | Annotations in code | Central XML files |
| Transactions | Manual begin/commit/rollback | Session-level | Automatic, JTA-backed |
| DB Independence | No — SQL varies by DB | Partial — dialect dependent | Full — same XML for all DBs |
| Caching | No — you build it yourself | 2-level cache (optional) | Built-in, configurable per entity |
Chapter 9
Entity XML — Complete Tag Reference
This is the master reference for every tag and attribute in a Moqui entity XML file.
9.1 <entity> Tag — The Root
The <entity> tag simultaneously defines: a database table, a Java object blueprint, and all framework-level rules for that data.
<entity entity-name="Party" package="party">
use — Entity Behaviour Type
| use value | When to Use | Caching | Example Entities |
transactional (default) | Financial/operational data where accuracy is critical | ❌ Not cached | orders, payments, inventory transactions |
nontransactional | Master/reference data that changes rarely | ✅ Can be cached | Party, Product, Customer |
configuration | Framework and app config settings | ✅ Heavily cached | system settings, enumeration types |
analytical | Reporting/aggregated data derived from transactional data | Optional | sales summaries, dashboards |
logging | High-volume log records, audit trails | ❌ Never cached | audit logs, event logs |
Primary Key Generation Attributes
| Attribute | What It Does | Default / Example |
sequence-primary-use-uuid | true → use UUID (globally unique), false → sequential integer | Default: false → 1001, 1002... |
sequence-bank-size | Pre-fetches N IDs from DB at once to avoid a DB hit on every insert | Default: 50 |
sequence-primary-stagger | Makes IDs jump randomly in range 1 to N, preventing predictable IDs | stagger=5 → 1001, 1003, 1008... |
sequence-primary-prefix | Adds a text prefix to every generated ID | prefix=ORD_ → ORD_1001 |
authorize-skip — Security Override
| Value | What Gets Skipped |
false (default) | Nothing skipped — full permission check always runs |
true | ALL checks skipped (create, update, delete, view) — dangerous, use with care |
create | Only CREATE permission check skipped — useful for anonymous user registration |
view | Only VIEW (read) check skipped — good for public data that anyone can see |
view-create | Both VIEW and CREATE skipped — for entities anyone can read and create |
9.2 <field> Tag — Column + Behaviour
| Attribute | Purpose | Notes |
name (required) | Field/property name in code | Always camelCase |
type (required) | Abstract data type — Moqui maps to Java type → SQL type | id, text-short, date-time, number-decimal, etc. |
is-pk | true = this is a primary key column. Can have multiple fields as composite PK | Adds PK + unique index |
not-null="true" | DB-level NOT NULL constraint | Throws error if null submitted |
encrypt="true" | Value is stored encrypted in DB. For passwords, tokens, sensitive data | Decrypted transparently on read |
default | Groovy expression evaluated when value is null on create/update | default="nowTimestamp()" |
create-only="true" | Once set on create, this field's value can never be changed | Like a write-once field |
9.3 <relationship> Tag — Entity Joins
| type Attribute | Purpose |
type="one" | One record on the related side. Like a FK: Order → Customer. Enforces FK constraint in DB. |
type="many" | Multiple records on the related side. Like: Customer → Orders. |
type="one-nofk" | One related record, but NO FK constraint in DB. Use for optional relationships or legacy DB compatibility. |
<relationship type="one" related="Customer">
<key-map field-name="customerId" related="customerId"/>
</relationship>
9.4 <view-entity> — Joins & Complex Queries
| Child Tag | SQL Equivalent | Purpose |
<member-entity> | FROM / JOIN clause | Defines which entities/tables participate and how they join |
<alias> | SELECT column AS name | Selects a specific field, optionally with aggregation |
<alias-all> | SELECT alias.* | Pulls ALL fields from a member entity into the view |
<entity-condition> | WHERE clause | Filters records — static conditions built into the view |
📝
join-optional="true" = LEFT JOIN. Without it = INNER JOIN.
Chapter 10
Field Types — The Complete Mapping
Moqui maps types through a 3-step chain: Moqui Abstract Type → Java Type → Database-Specific SQL Type. This is what makes Moqui database-independent.
id
String
VARCHAR(60)
Primary keys, foreign keys, short identifiers
id-long
String
VARCHAR(255)
Long identifiers, external IDs
text-short
String
VARCHAR(63)
Short strings: names, codes, status values
text-medium
String
VARCHAR(255)
Standard strings: descriptions, titles, URLs
text-long
String
TEXT (up to 65K)
Long text: notes, descriptions, HTML content
text-very-long
String
LONGTEXT (up to 4GB)
Very large text: XML, JSON blobs, article bodies
date-time
java.sql.Timestamp
DATETIME
Full timestamp: created date, order date
number-integer
Long
DECIMAL(20,0)
Whole numbers: quantity, count, sequence
currency-amount
BigDecimal
DECIMAL(22,2)
Money values (2 decimal places): price, tax
text-indicator
String
CHAR(1)
Y/N boolean indicators (Moqui style for flags)
⚠️
Never use number-float for money! Floating point arithmetic is imprecise. 0.1 + 0.2 ≠ 0.3 in float. Always use currency-amount for financial values.
Chapter 11
JDBC in Moqui
JDBC (Java Database Connectivity) is the standard Java API for talking to relational databases. Moqui's Entity Engine uses JDBC internally — you almost never touch it directly.
Standard JDBC 6-Step Workflow (What Moqui Does Internally)
| # | Action | Code Example |
| 1 | Load Driver | Class.forName("com.mysql.cj.jdbc.Driver") |
| 2 | Get Connection | DriverManager.getConnection("jdbc:mysql://...") |
| 3 | Create Statement | conn.prepareStatement("SELECT * FROM PARTY") |
| 4 | Execute Query | ps.executeQuery() |
| 5 | Process Results | while(rs.next()) { rs.getString("PARTY_ID"); } |
| 6 | Close Connection | conn.close() — actually returns to HikariCP pool |
Common JDBC Errors & Fixes
| Error | Cause | Fix |
ClassNotFoundException | JDBC driver JAR missing | Add mysql-connector-java.jar to runtime/lib/ |
| Connection refused | MySQL not running / wrong host or port | Start MySQL service, verify host:port in config |
| Access denied for user | Wrong credentials or missing DB grant | Check username/password; run GRANT ALL ON moqui.* TO ... |
| No suitable driver found | Driver class name typo | Verify jdbc-driver attribute in config exactly matches |
Chapter 12
Moqui ↔ MySQL Integration
Character Set — UTF-8 vs UTF8MB4
| Type | Max Bytes/Char | Emoji Support | Use When |
utf8 (MySQL) | 3 bytes | ❌ No | Legacy apps only — avoid for new projects |
utf8mb4 (MySQL) | 4 bytes | ✅ Yes — full Unicode | Always use this for modern applications |
What ./gradlew load Does
| Step | What Happens |
| 1 | Gradle reads build scripts, identifies the load task, sets up classpath |
| 2 | Starts embedded Jetty server, loads MoquiDevConf.xml + MoquiActualConf.xml |
| 3 | Reads ALL entity/*.xml files from all components, builds EntityDefinition objects |
| 4 | Connects via JDBC using configured credentials |
| 5 | Compares entity definitions with existing DB schema → generates CREATE TABLE or ALTER TABLE SQL |
| 6 | Reads all <seed-data> blocks from entity XML files → generates INSERT SQL → executes |
| 7 | If demo data flag set → loads additional demo records |
📝
Run ./gradlew load when: first setup, adding new entities, adding new seed data. It's safe to run multiple times — it only creates missing tables/columns.
Chapter 13
Party Data Model — Enterprise Design
The Party model is the foundation of enterprise ERP/CRM systems. Instead of having separate Customer, Vendor, Employee, Organization tables (which causes duplication and rigid structure), Moqui uses a single abstract 'Party' concept.
Imagine: A company (ACME Corp) is your customer AND your supplier. With traditional separate tables, you'd need two records and they'd get out of sync. With Party model, ACME Corp is ONE Party record — it just has relationships saying it's both a customer and a supplier.
Party (abstract base)
Any actor in the system
├── Person (an individual human)
└── PartyGroup (a company / organization)
ContactMech
How to contact any Party
├── Email Address
├── Phone Number
└── Postal Address
Design Patterns in the Party Model
| Pattern | Where Used | Benefit |
| Subtype Pattern | Party → Person / PartyGroup | Shared identity (partyId), specialised fields per type, no duplication |
| Enumeration Pattern | partyTypeEnumId, contactMechTypeId | Types are data, not columns — add new types without DB schema change |
| Valid-Time Modeling | PartyContactMech (fromDate, thruDate) | Full history preserved, no data loss, auditable |
| Join Table | PartyContactMech | Many-to-many: one party has many contacts, one contact can be shared |
📝
To 'change' an email: set thruDate on old record = today, create new PartyContactMech with fromDate = today. Old email is preserved for history!
Chapter 14
Service Layer — Business Logic
A Service in Moqui is NOT just a function. It is a fully managed execution unit that includes: business logic + input validation + transaction management + security + error handling.
Service Naming Convention
myapp.PersonServices.create#Person
ec.service.sync().name("create#Person").call()
Service Control Attributes — Authentication
| Attribute & Value | Meaning |
authenticate="true" (default) | User must be logged in. Full authorization check runs. |
authenticate="false" | No login required. Auth check is completely skipped. |
authenticate="anonymous-all" | No login required. Logs in as anonymous user. All actions authorized. |
authenticate="anonymous-view" | No login required. Logs in as anonymous. Only VIEW action authorized. |
Transaction Control
| Attribute | Options & Meaning |
transaction | use-or-begin (default): join existing TX or start new one. new: always start fresh TX. ignore: no TX management. |
transaction-timeout | Seconds before auto-rollback. Override per-service for long operations. |
Concurrency Control (Semaphore)
| semaphore value | Behaviour | Use Case |
none (default) | No locking — multiple instances can run simultaneously | Most services |
fail | If same service is already running → immediately throw error | Prevent duplicate processing |
wait | If same service is already running → wait until it finishes | Serial queue behaviour |
Chapter 15
Service Types — All 6 in Detail
| type | Logic Location | When to Use |
inline (default) | <actions> block inside XML | Most common — small to medium business logic |
entity-auto | No code — auto-generated | CRUD on any entity, zero boilerplate needed |
script | External .groovy file | Large logic, reusable across services |
java | Compiled Java class method | Performance-critical code, existing Java libraries |
remote-json-rpc | External JSON-RPC server | Call another Moqui instance or JSON-RPC API |
remote-rest | External REST HTTP endpoint | Call any REST API (Stripe, Twilio, custom APIs) |
entity-auto — Zero Code CRUD
| Call | What It Does | SQL Generated |
ec.service.sync().name("create#party.Party").call() | Creates a Party record | INSERT INTO PARTY (...) |
ec.service.sync().name("update#party.Party").call() | Updates a Party record | UPDATE PARTY SET ... |
ec.service.sync().name("delete#party.Party").call() | Deletes a Party record | DELETE FROM PARTY WHERE ... |
ec.service.sync().name("store#party.Party").call() | Create or Update (upsert) | INSERT or UPDATE depending on PK |
Calling Services — 3 Modes
| Mode | Code | When to Use |
| Synchronous | ec.service.sync().name("create#Person").parameters([...]).call() | Most cases — wait for result |
| Asynchronous | ec.service.async().name("send#Email").parameters([...]).call() | Background tasks — fire and forget |
| Async Distributed | ec.service.async().name("process#Report").distribute(true).call() | Cluster deployments — run on another server |
SECA Rules — Auto-Trigger Services
<seca service="create#party.Person" when="post">
<action service="send#WelcomeEmail">
<field-map field-name="toEmail" from="email"/>
</action>
</seca>
Chapter 16
Screen Layer — UI Definition
Screens are Moqui's UI layer. You define the entire user interface in XML — forms, lists, dialogs, navigation — and Moqui renders it as HTML.
Screen XML Key Tags
| Tag | Purpose |
<screen> | Root tag — defines one UI screen |
<transition> | Handles form submissions and button clicks (HTTP POST handler) |
<actions> | Runs before rendering — loads data into context |
<widgets> | Defines the visual UI components |
<form-single> | Form for creating or editing one record |
<form-list> | Displays a list of records in a table |
<container-dialog> | Creates a button that opens a modal dialog |
<auto-fields-entity> | Auto-generates form fields from entity definition |
Action Tags — Logic in XML
| Action Tag | Groovy/SQL Equivalent | Example |
<entity-find> | SQL SELECT / ec.entity.find() | <entity-find entity-name="party.Party" list="partyList"> |
<entity-find-one> | SQL SELECT + .one() | <entity-find-one entity-name="party.Party" value-field="party"> |
<service-call> | ec.service.sync().name() | <service-call name="create#party.Party" out-map="result"/> |
<set> | Variable assignment | <set field="statusId" value="ACTIVE"/> |
<if> | if/else statement | <if condition="party != null">...</if> |
<script> | Inline Groovy code | <script>ec.logger.info("Running")</script> |
Chapter 17
Screen XML — PartyScreen.xml Walkthrough
A complete, line-by-line walkthrough of a real Moqui screen that displays a list of Party records and lets you create new ones.
Screen Root — Authentication
<screen xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="http://moqui.org/xsd/xml-screen-2.1.xsd"
require-authentication="anonymous-all">
Transition — Handling Form Submission
<transition name="createParty">
<service-call name="create#party.Party"/>
<default-response url="."/>
</transition>
Actions — Loading Data Before Render
<actions>
<entity-find entity-name="party.Party" list="partyList">
<search-form-inputs/>
</entity-find>
</actions>
Widgets — The UI
<widgets>
<container-dialog id="CreatePartyDialog" button-text="Create Party">
<form-single name="CreateParty" transition="createParty">
<auto-fields-entity entity-name="party.Party" field-type="edit"/>
<field name="submitButton">
<default-field title="Create"><submit/></default-field>
</field>
</form-single>
</container-dialog>
<form-list name="ListParty" list="partyList" skip-form="true">
<auto-fields-entity entity-name="party.Party" field-type="find-display"/>
</form-list>
</widgets>
End-to-End Flow: User Creates a Party
| # | What Happens | Moqui Component |
| 1 | User opens /apps/myapp/PartyScreen | ScreenFacadeImpl resolves URL |
| 2 | Actions run: entity-find loads partyList from DB | EntityFacade → JDBC → DB |
| 3 | form-list renders partyList as HTML table | FreeMarker template → HTML |
| 4 | User clicks 'Create Party' button | container-dialog opens modal |
| 5 | User fills form and clicks Create | Browser POSTs form data |
| 6 | Transition 'createParty' receives POST | ScreenDefinition.TransitionItem |
| 7 | create#party.Party entity-auto service runs | EntityAutoServiceRunner → INSERT |
| 8 | default-response url="." → reload screen | ScreenRenderImpl re-renders |
| 9 | Updated list shown (new record visible) | entity-find reruns, FTL rerenders |
Chapter 18
Creating Forms & Services — Hands-On
PersonServices.xml — The Contract
<service verb="create" noun="Person" type="script"
location="component://party/service/party/createPerson.groovy">
<in-parameters>
<parameter name="partyId" type="String" required="true"/>
<parameter name="firstName" type="String" required="true"/>
<parameter name="lastName" type="String" required="true"/>
<auto-parameters entity-name="party.Person" include="all"/>
</in-parameters>
<out-parameters>
<parameter name="responseMessage" type="String"/>
</out-parameters>
</service>
createPerson.groovy — The Logic
def partyId = parameters.partyId
def firstName = parameters.firstName
def lastName = parameters.lastName
// Check if Party exists
def party = ec.entity.find("party.Party")
.condition("partyId", partyId).one()
if (!party) {
context.responseMessage = "Party ${partyId} does not exist"
return
}
// Create the Person record
ec.entity.makeValue("party.Person")
.setAll(parameters)
.create()
context.responseMessage = "Person ${firstName} ${lastName} created successfully"
Chapter 19
XML Processing Pipeline — What Becomes What?
| File Type | Compiled? | When Processed | Result Cached? |
Entity *.xml | No (→ MNode → EntityDefinition) | Startup | Yes (permanent) |
Service *.xml | Partially (MNode → SD; <actions> → Groovy Class) | First call (lazy) | Yes |
Screen *.xml | Partially (MNode → ScreenDef; <actions> → Groovy Class) | First request (lazy) | Yes, with mod-check |
Standalone *.groovy | Yes (→ in-memory JVM Class) | First call (lazy) | Yes |
| Inline Groovy in XML | Yes (entire <actions> → one Class) | First call (lazy) | Yes |
*.java (framework) | Yes (ahead of time, by Gradle) | Build time | Permanent (in JAR) |
*.ftl template | Parsed (→ FTL Template object) | First use | Yes |
SECA *.secas.xml | No (→ ServiceEcaRule objects) | Startup | Yes (in-memory Map) |
ClassLoader Chain
Bootstrap ClassLoader
JDK built-ins
└──
└──
MClassLoader
Loads JARs from runtime/lib, component/lib
└──
GroovyClassLoader
Extends MClassLoader — Compiles .groovy dynamically at runtime
Chapter 20
Groovy in Moqui
The Three Groovy Tools — Which Does Moqui Use?
| Tool | Analogy | Does Moqui Use It? |
GroovyShell | Calculator — type expression, get answer | Rarely (one-off expressions only) |
GroovyScriptEngine | Script file watcher — re-compiles on file change | ❌ Not used by Moqui |
GroovyClassLoader | Java class loader that also compiles Groovy | ✅ YES — Moqui's ONLY compilation tool |
Available Variables in Groovy Scripts
| Variable | Type | What It Is |
ec | ExecutionContext | The main context object — access to everything |
parameters | Map<String, Object> | Input parameters passed to this service |
context | ContextStack (Map-like) | The shared context — set values here to return as out-parameters |
ec.entity | EntityFacade | Database operations |
ec.service | ServiceFacade | Call other services |
ec.logger | LoggerFacade | Logging: ec.logger.info(), ec.logger.warn() |
ec.message | MessageFacade | Add error/info messages: ec.message.addError() |
📝
The Class is NEVER re-created per request. Only a lightweight Script wrapper is new each time. After the first call, Groovy service performance ≈ Java performance.
Chapter 21
Definition vs Facade vs EC — The Mental Model
| Concept | Analogy | Lifetime | Shared? | Purpose |
| Definition (EntityDef, ServiceDef, ScreenDef) | Recipe card — written once, read-only | Permanent (until server restart) | Yes — all threads read the same | Describes WHAT something is |
| Facade (EntityFacade, ServiceFacade, etc.) | Kitchen department head — knows where everything is | Permanent (created at boot) | Yes — one per server, thread-safe | Provides clean API to a complex subsystem |
| ExecutionContext (ec) | Waiter assigned to one table — lives for one customer's visit | Per-request (created & destroyed each time) | No — each request gets its own EC | Carries state for ONE request |
One-Liner Summaries
Definition
What an entity/service/screen IS — static blueprint, cached forever, never changes at runtime.
Facade
How you INTERACT with a subsystem — shared coordinator, always available, thread-safe.
ExecutionContext (ec)
WHO is making the request and WHAT is happening NOW — carries state for one specific request.
Chapter 22
Key Design Patterns in Moqui
Facade Pattern
EntityFacade, ServiceFacade, ScreenFacade
Hides complexity behind a simple API. You call 1 line, 50 lines run internally.
Strategy Pattern
ServiceRunner (Inline/Java/Script/EntityAuto)
Each service type is a different strategy for executing logic. Switchable without changing calling code.
Singleton Pattern
ECFI, all Facades
One instance per server. Shared globally. Thread-safe.
Factory Pattern
ECFI.getEci()
Creates new ExecutionContext instances for each request.
Subtype Pattern
Party → Person / PartyGroup
Shared base record (Party), type-specific child record (Person). Shared PK links them.
Enumeration Pattern
partyTypeEnumId, statusId, contactMechTypeId
Types are data rows, not hardcoded columns. Add new types without schema changes.
Valid-Time Modeling
PartyContactMech (fromDate, thruDate)
Historical data preserved with date ranges. No records ever deleted — just expired.
Declarative Programming
All XML definitions
You say WHAT, Moqui figures out HOW. Zero SQL, zero JDBC, zero HTML for basic operations.
Chapter 23
Common Mistakes & Debugging
Entity Mistakes
| Mistake | Symptom | Fix |
| Wrong entity name in code | EntityException: Entity not found "party.Pary" | Check spelling + package. Use exact case. |
Not running ./gradlew load after adding entity | Table doesn't exist — SQL error on first query | Always run ./gradlew load after adding/modifying entity XML. |
| Using float for currency | Price shows as 99.99999... instead of 100.00 | Use currency-amount type, not number-float. |
| Missing relationship key-map | Join produces no results or wrong join condition | Ensure <key-map field-name="x" related="y"/> is present. |
| Caching transactional data | Stale data shown after update | Never use cache="true" on transactional entities (orders, payments). |
Service Mistakes
| Mistake | Symptom | Fix |
| Wrong service name format | ServiceException: Service not found "PersonService.create" | Use verb#noun format: "create#Person" |
| Missing required parameter | Validation error at framework level | Pass all required="true" parameters in your call. |
| allow-remote not set for REST | 404 or access denied from REST API | Add allow-remote="true" to the service definition. |
| Wrong Groovy context variable name | Null value returned for out-parameter | In Groovy, set context.myOutput = value, not parameters.myOutput. |
Chapter 24
Interview Cheatsheet
Adarsh's Core Architecture Questions
What is Moqui?
An open-source Java enterprise framework. Everything is defined in XML. Uses convention over configuration. Built on Java + Groovy + JDBC.
What is ExecutionContext?
The central per-request object. Provides access to all facades (entity, service, screen). Created fresh for each HTTP request and destroyed after.
What is a Facade?
A simplified API hiding complex internal logic. EntityFacade hides all JDBC/SQL. ServiceFacade hides service discovery and transactions. Created once at boot, shared across all requests.
Does Moqui use JDBC?
Yes, internally through the Entity Engine. Developers never write raw JDBC — EntityFacade generates all SQL and manages connections via HikariCP pool.
What is the Entity Engine?
Moqui's ORM. Converts entity XML definitions into EntityDefinition objects, generates SQL tables via ./gradlew load, and handles all CRUD operations at runtime.
What is a view-entity?
The XML equivalent of a SQL VIEW. Defines joins between entities, selects specific fields, applies conditions — all in XML without writing SQL.
What is entity-auto service?
Adarsh's Auto-generated CRUD services. Call create#party.Party without writing any service code — Moqui generates the INSERT logic automatically.
What is SECA?
Service Event Condition Action — trigger one service automatically when another runs. Like database triggers, but at the service level.
One-Line Definitions (Quick Reference)
MNode
Moqui's own fast in-memory XML node class (not standard Java DOM). All XML lives as MNode after parsing.
XmlAction
Converts XML action tags (<entity-find>, <set>, <if>) into Groovy bytecode. The bridge between XML and execution.
ContextStack
A stack of Maps that holds all in-scope variables for a request. Reading goes top-to-bottom. Writing always goes to the top.
HikariCP
Connection pool used by Moqui. Pre-creates DB connections and reuses them. Faster than creating new connection per query.
Bitronix / JTA
Transaction manager. Ensures ACID compliance. Automatically wraps each service call in a transaction with rollback on error.
sequence-bank-size
Pre-fetches N primary key IDs from DB to avoid a DB hit on every single insert. Default: 50.
optimistic-lock
Prevents overwrites: checks lastUpdatedStamp before save. If record changed since you read it, throws error.
Party Model
Enterprise data model where Person and Organization both extend an abstract Party. Enables one entity (ACME Corp) to be both customer and supplier.