java-actions
Create and call custom Java actions — extending Mendix with server-side Java, and invoking those actions from microflows in MDL. Use when logic needs a Java library, an algorithm microflows cannot exp
SKILL.md.claude/skills/mendix/java-actions/SKILL.md
GitHub<!-- skill-index: generated by scripts/skill-index.py; make check-skill-index -->
<!-- /skill-index -->
Find it fast
sed -n '<from>,<to>p' SKILL.md one section rather than reading the file;
./mxcli syntax <topic> answers how do I write this for a fraction of either.
| lines | section |
|---|---|
| 28-39 | Reference files |
| 40-48 | When to Use This Skill |
| 49-55 | Overview |
| 56-332 | Part 2.5: Creating Java Actions in MDL |
| 333-526 | Part 3: Calling Java Actions from MDL |
| 527-540 | Validation Checklist |
| 541-551 | Common Errors |
| 552-559 | Related Documentation |
| 560-641 | Quick Reference |
Mendix Java Actions Skill
This skill provides comprehensive guidance for creating and using custom Java actions in Mendix projects.
Reference files
SKILL.md covers declaring a Java action in MDL and calling it from a microflow —
the parts that touch the model. The Java side is next door:
reference/writing-java.md— creating the action in Studio Pro, writing theexecuteActionbody, the Core API surface you have, and the recurring patterns (HTTP calls, file handling, batch work).reference/best-practices.md— error handling, transactions and rollback, logging, threading, security, and performance. Read it before shipping a Java action that touches data or an external system.
When to Use This Skill
Use this skill when:
- You need to extend Mendix with custom Java logic
- Building integrations with external Java libraries
- Implementing complex algorithms not feasible in microflows
- Calling Java actions from MDL microflows
- Debugging Java action calls
Overview
Java actions allow you to extend Mendix with custom Java code. The workflow is:
- Define the Java action in Studio Pro (parameters, return type)
- Implement the Java code in Eclipse/IDE
- Call the Java action from microflows using MDL
Part 2.5: Creating Java Actions in MDL
MDL supports defining Java actions with inline Java code using create java action.
Basic Syntax
mdl 1;
create java action Module.ActionName(param1: string, param2: integer) returns boolean
as $$
// java code here
return result;
$$;
AS $$ ... $$ is mandatory. The body cannot be omitted even for placeholder or stub actions. Omitting it causes a parse error: no viable alternative at input '...'. Use a minimal body if the real implementation is not yet written:
mdl 1;
create java action Module.Stub() returns boolean
as $$
return false;
$$;
Type Parameters (Generics)
Type parameters let Java actions accept any entity type dynamically. Use entity <pEntity> in a parameter type to declare the type parameter inline. That parameter becomes the entity type selector (receives the entity type name, e.g., 'Module.Entity'). Bare pEntity parameters become parameterized entity params (receive entity instances, e.g., $Variable).
mdl 1;
-- ENTITY <pEntity> declares the type parameter; bare pEntity references it
create java action Module.Validate(
EntityType: entity <pEntity> not null,
InputObject: pEntity not null
) returns boolean
as $$
return InputObject != null;
$$;
Multiple type parameters use separate entity <...> declarations:
mdl 1;
create java action Module.Transform(
SourceType: entity <pSource> not null,
TargetType: entity <pTarget> not null,
source: pSource not null,
Target: pTarget not null
) returns boolean
as $$
return true;
$$;
A list of type-parameter instances is list of pEntity (Studio Pro's "List of <type parameter>").
A type parameter with no entity selector — the object the caller passes binds it — is declared in a header after the action name: create java action Module.JA_Describe<pEntity>(InputObject: pEntity not null) returns String as $$ … $$;. Without the header, a bare pEntity is neither an entity nor a type parameter, and check refuses it (MDL-JA01); before, exec wrote it as the entity .pEntity (CE1613, mendixlabs/mxcli#1404).
A type parameter named after a primitive (String, Integer, … — Studio Pro allows it): declare it with entity <String>, and refer to it quoted — "String", list of "String". Unquoted String is always the primitive. DESCRIBE quotes such names for you, so its output re-creates the action.
Type parameter names can be mixed with regular parameter types:
mdl 1;
create java action Module.CopyAttributes(
EntityType: entity <pEntity> not null,
source: pEntity not null,
Target: pEntity not null,
AttributeNames: string not null
) returns boolean
as $$
return true;
$$;
When calling these actions from microflows, the entity type selector receives the entity type name as a string literal, while instance params receive variables:
$Result = call java action Module.CopyAttributes(
EntityType = 'Module.ProcessResult',
source = $source,
Target = $Target,
AttributeNames = 'Name,Status'
);
EXPOSED AS (Toolbox Visibility)
The exposed as 'caption' in 'Category' clause makes the Java action appear as a toolbox item in Studio Pro's microflow editor:
mdl 1;
create java action Module.FormatCurrency(
Amount: decimal not null,
CurrencyCode: string not null
) returns string
exposed as 'Format Currency' in 'Formatting'
as $$
return String.format("%.2f %s", Amount, CurrencyCode);
$$;
Type parameters and EXPOSED AS can be combined:
mdl 1;
create java action Module.DeepClone(
EntityType: entity <pEntity> not null,
Original: pEntity not null
) returns boolean
exposed as 'Deep Clone Object' in 'Object Utils'
as $$
return true;
$$;
Toolbox icon and image
The entry also carries four PNG bitmaps — an icon and a larger image, each with a dark-mode variant. Paths resolve against the directory of the .mdl file, so a script and its artwork travel together:
exposed as 'Format Currency' in 'Formatting'
icon 'assets/currency-64.png'
icon dark 'assets/currency-64-dark.png'
image 'assets/currency-256.png'
The icon should be 64x64 and the image 256x192. A different size is written with a warning (Studio Pro scales it); a file that is not a PNG is refused, because Studio Pro renders nothing for one.
An omitted clause preserves — it does not clear
This is the rule to remember, and it is not the obvious one:
| You write | What happens |
|---|---|
exposed as 'X' in 'Y' |
Caption and category from MDL; icon and image carried from what was stored |
| (no clause at all) | The whole entry is preserved, bitmaps included |
not exposed |
The entry is removed |
exposed as 'X' in 'Y' drop icon dark |
Clears one bitmap; the other three are untouched |
Rewriting a Java action to change its body must not cost it the icon a designer
set in Studio Pro, and saying nothing about the toolbox is not the same as
asking to be taken out of it. Nothing below Studio Pro can see the difference:
an action with no toolbox entry is a valid action, so mx check reports 0
errors either way.
describe java action reports the bitmaps as comments rather than re-emitting
icon/image clauses — the clause names a file on disk and the model holds
only bytes. They survive a describe → exec round trip regardless, because an
omitted bitmap is preserved.
Microflows have this too, and two of them: exposed as microflow action and
exposed as workflow action, for Studio Pro's two toolboxes. See
write-microflows. Nanoflows and rules do not — only Microflows$Microflow
stores a toolbox entry, and the clause is refused on the other two with a
message saying so.
Studio Pro-authored Java actions: there is no alter java action. Before
re-executing describe output for one, note its export level; a create or modify
from describe output has changed an action's export level to Public. Check it again after
exec (see choose-edit-mode).
Supported Parameter Types
| MDL Type | Description |
|---|---|
string |
Text value |
integer |
Whole number |
long |
Large whole number |
decimal |
Decimal number |
boolean |
True/false |
datetime |
Date and time |
Module.Entity |
Entity reference |
list of Module.Entity |
List of entities |
stringtemplate(sql) |
SQL/OQL query template with parameters |
stringtemplate(text) |
Text template with parameters |
entity <pEntity> |
Type parameter declaration (entity type selector) |
enum Module.EnumName |
Enumeration type |
enumeration(Module.EnumName) |
Enumeration type (alternative syntax) |
pEntity (type param ref) |
Type parameter reference (entity instance) |
list of pEntity |
List of type-parameter instances |
In the $$ body, a parameter is the generated field — not the raw value. An entity
parameter is its proxy class (module.proxies.Entity; .getMendixObject() for the
IMendixObject), a list a java.util.List of proxies, integer a java.lang.Long, an
enumeration its proxy enum (but an enumeration return is a String). The full table is
in writing-java.md; it is what mxbuild
generates, so the body compiles both under --watch and after a full build.
Examples
Simple Action (No Parameters)
mdl 1;
/** Returns the current timestamp. */
create java action MyModule.GetCurrentTimestamp() returns datetime
as $$
return new java.util.Date();
$$;
Action with Primitive Parameters
mdl 1;
/** Calculates tax amount. */
create java action Finance.CalculateTax(Amount: decimal, TaxRate: decimal) returns decimal
as $$
if (Amount == null || TaxRate == null) {
return java.math.BigDecimal.ZERO;
}
return Amount.multiply(TaxRate).divide(java.math.BigDecimal.valueOf(100), 2, java.math.RoundingMode.HALF_UP);
$$;
Action with StringTemplate (SQL/OQL)
mdl 1;
/** Executes an OQL statement with parameterized query. */
create java action Database.ExecuteOQLStatement(OqlStatement: stringtemplate(sql) not null) returns boolean
as $$
// execute the parameterized OQL statement
// The stringtemplate handles parameter substitution safely
return true;
$$;
Action with NOT NULL Parameter
mdl 1;
/** Validates an email address - email is required. */
create java action Validation.ValidateEmail(EmailAddress: string not null) returns boolean
as $$
string emailRegex = "^[a-zA-Z0-9_+&*-]+(?:\\.[a-zA-Z0-9_+&*-]+)*@(?:[a-zA-Z0-9-]+\\.)+[a-zA-Z]{2,7}$";
return EmailAddress.matches(emailRegex);
$$;
Action with Type Parameter (Generic)
mdl 1;
/** Validates any entity - checks that required fields are filled. */
create java action Validation.ValidateEntity(
EntityType: entity <pEntity> not null,
InputObject: pEntity not null
) returns boolean
as $$
return InputObject.getMembers().values().stream()
.allMatch(m -> !m.isRequired() || m.getValue(getContext()) != null);
$$;
Action with Type Parameter + EXPOSED AS
mdl 1;
/** Deep clones any entity (toolbox-visible). */
create java action Utils.DeepClone(
EntityType: entity <pEntity> not null,
Original: pEntity not null
) returns boolean
exposed as 'Deep Clone Object' in 'Object Utils'
as $$
return true;
$$;
Part 3: Calling Java Actions from MDL
Basic Syntax
-- Call Java action (no return value)
call java action Module.JA_ActionName(
ParamName1 = value1,
ParamName2 = value2
);
-- Call Java action with return value
$Result = call java action Module.JA_ActionName(
ParamName1 = value1,
ParamName2 = value2
);
Avoiding Duplicate Variables (CE0111)
$Var = call java action ... creates a new variable. Do NOT declare a variable with the same name first:
-- WRONG: DECLARE + CALL both create $Success → CE0111
declare $success boolean = false;
$success = call java action Module.DoWork();
-- CORRECT: Use a separate name when you need a default
declare $success boolean = false;
$WorkResult = call java action Module.DoWork();
set $success = $WorkResult;
-- CORRECT: Simple pass-through (no default needed)
$success = call java action Module.DoWork();
return $success;
When calling Java actions in multiple branches, use unique result variable names:
declare $success boolean = false;
if $Priority = 'HIGH' then
$UrgentResult = call java action Module.SendUrgent(Msg = $Email);
set $success = $UrgentResult;
else
$NormalResult = call java action Module.SendNormal(Msg = $Email);
set $success = $NormalResult;
end if;
Expression Escaping in String Arguments
Single quotes within string literal arguments must be doubled (''):
-- OQL with embedded quotes — use '' to escape
$count = call java action Module.ExecuteOQL(
Statement = 'SELECT * FROM Module.Entity WHERE Status = ''Active'''
);
Complete Examples
Example 1: Simple Calculation
mdl 1;
/**
* Calculate tax using custom Java action
*/
create microflow Tax.ACT_CalculateOrderTax($order: Tax.Order)
returns decimal as $taxAmount
begin
declare $subtotal decimal = $order/Subtotal;
declare $taxRate decimal = 0.21;
-- Call Java action for complex calculation
$taxAmount = call java action Tax.JA_CalculateTax(
Amount = $subtotal,
TaxRate = $taxRate
);
change $order (TaxAmount = $taxAmount);
commit $order;
return $taxAmount;
end;
Example 2: External API Integration
mdl 1;
/**
* Send notification via external service using Java action
*/
create microflow Notifications.ACT_SendOrderConfirmation($order: Sales.Order)
returns boolean as $success
begin
declare $customerEmail string = $order/Sales.Order_Customer/Email;
declare $orderNumber string = $order/OrderNumber;
-- Call Java action that integrates with external email service
$success = call java action Notifications.JA_SendEmail(
ToAddress = $customerEmail,
Subject = 'Order Confirmation: ' + $orderNumber,
body = 'Your order has been confirmed.',
TemplateName = 'OrderConfirmation'
);
if $success then
change $order (NotificationSent = true);
commit $order;
else
log warning node 'Notifications' 'Failed to send email for order: ' + $orderNumber;
end if;
return $success;
end;
Example 3: OQL Bulk Operations (Mendix 11.6+)
mdl 1;
/**
* Bulk update using OQL via Java action
*/
create microflow Finance.ACT_ArchiveOldTransactions()
returns integer as $rowsAffected
begin
-- Use built-in OQL execution Java action
$rowsAffected = call java action CustomActivities.ExecuteOQLStatement(
OqlStatement = 'UPDATE Finance.Transaction SET Status = ''ARCHIVED'' WHERE TransactionDate < ''2024-01-01'' AND Status = ''COMPLETED'''
);
log info node 'Finance' 'Archived ' + toString($rowsAffected) + ' transactions';
return $rowsAffected;
end;
Example 4: OQL with Parameters
/**
* Parameterized OQL update via Java action
*/
create microflow Finance.ACT_UpdateTransactionStatus(
$oldStatus: string,
$newStatus: string,
$cutoffDate: datetime
)
returns integer as $rowsUpdated
begin
$rowsUpdated = call java action CustomActivities.ExecuteOQLStatementPars(
OqlStatement = 'UPDATE Finance.Transaction SET Status = {1} WHERE Status = {2} AND TransactionDate < {3}' with (
{1} = $newStatus,
{2} = $oldStatus,
{3} = $cutoffDate as datetime
)
);
return $rowsUpdated;
end;
Example 5: Returning Objects
mdl 1;
/**
* Create complex object structure using Java action
*/
create microflow Import.ACT_ParseCSVFile($fileDocument: System.FileDocument)
returns list of Import.ImportRecord as $records
begin
-- Java action parses CSV and returns list of objects
$records = call java action Import.JA_ParseCSV(
FileDocument = $fileDocument,
HasHeader = true,
Delimiter = ','
);
if $records = empty then
log warning node 'Import' 'No records parsed from file';
else
log info node 'Import' 'Parsed ' + toString(length($records)) + ' records';
end if;
return $records;
end;
Validation Checklist
Before deploying Java actions, verify:
- Java action has
JA_prefix naming convention - All parameters are defined with correct types
- Return type matches what you return in Java
- Code is only between
begin user CODE/end user CODEmarkers - Proper null checks for all parameters
- Exception handling with logging
- No hardcoded credentials or sensitive data
- Entity and attribute names match model exactly
- Unit tests cover main scenarios
Common Errors
| Error | Cause | Fix |
|---|---|---|
ClassNotFoundException |
Missing library | Add JAR to userlib/ folder |
NullPointerException |
Null parameter | Add null checks |
Could not find entity |
Wrong entity name | Use exact qualified name |
attribute not found |
Wrong attribute name | Check model for exact name |
ClassCastException |
Wrong type cast | Check parameter types |
no viable alternative at input '...' (parse error) |
AS $$ ... $$ body is missing — it is mandatory even for void/stub actions |
Add as $$ return null; $$; (or appropriate stub) |
Related Documentation
- Mendix Java Actions Reference
- Build Microflow Actions with Java
- Java Programming in Mendix
- Write Microflows Skill
- Validation Microflows Skill
Quick Reference
Java Action Definition Syntax
mdl 1;
-- Basic Java action
create java action Module.JA_IsValid(Param: string not null) returns boolean
as $$
return true;
$$;
-- With type parameters (generics)
-- ENTITY <pEntity> = entity type selector, bare pEntity = entity instances
create java action Module.JA_HasObject(
EntityType: entity <pEntity> not null,
Obj: pEntity not null
) returns boolean
as $$
return Obj != null;
$$;
-- With EXPOSED AS (toolbox visibility)
create java action Module.JA_FormatAmount(Amount: decimal) returns string
exposed as 'Format Amount' in 'Formatting'
as $$
return Amount.toString();
$$;
-- Combined type parameters + EXPOSED AS
create java action Module.JA_ValidateObject(
EntityType: entity <pEntity> not null,
Obj: pEntity not null
) returns boolean
exposed as 'Validate Object' in 'Validation'
as $$
return Obj != null;
$$;
Java Action Call Syntax
-- Without return value
call java action Module.JA_ActionName(Param1 = value1, Param2 = value2);
-- With return value
$Result = call java action Module.JA_ActionName(Param1 = value1);
-- With OQL parameters (Mendix 11.6+)
$Rows = call java action Module.JA_ExecuteOQL(
Statement = 'UPDATE Module.Entity SET Attr = {1} WHERE Id = {2}' with (
{1} = $value1,
{2} = $value2 as integer
)
);
Core API Quick Reference
// context
IContext context = getContext();
// create
IMendixObject obj = Core.instantiate(context, "Module.Entity");
// read
object value = obj.getValue(context, "attributename");
// update
obj.setValue(context, "attributename", newValue);
// Save
Core.commit(context, obj);
// delete
Core.delete(context, obj);
// query
list<IMendixObject> results = Core.createXPathQuery("//Module.Entity[attr = 'value']").execute(context);
// log
Core.getLogger("ModuleName").info("message");