Mendix Java Actions Skill
SkillDev toolsLets your agent write custom Java actions for Mendix and call them from microflows.
Available today. Use it from your connected AI after setup.
No other account needed.
Connect ahel once, and every AI you use reads what you have installed.
Then ask your AI: use the Mendix Java Actions Skill skill
About this capability
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 express, or an integration only available in Java.
What this skill tells your AI
The instructions your AI receives, as published by mendixlabs/mxcli in .claude/skills/mendix/java-actions/SKILL.md and read by ahel’s review.
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
create java action Module.ActionName(param1: type, param2: type) returns ReturnType
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:
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).
-- 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:
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;
$$;
Type parameter names can be mixed with regular parameter types:
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:
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:
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.
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) |
Examples
Simple Action (No Parameters)
/** Returns the current timestamp. */
create java action MyModule.GetCurrentTimestamp() returns datetime
as $$
return new java.util.Date();
$$;
Action with Primitive Parameters
/** 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)
/** 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
/** 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)
/** 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
/** 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
/**
* 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
/**
* 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+)
/**
* 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
/**
* 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
-- Basic Java action
create java action Module.Name(Param: type not null) returns boolean
as $$
return true;
$$;
-- With type parameters (generics)
-- ENTITY <pEntity> = entity type selector, bare pEntity = entity instances
create java action Module.Name(
EntityType: entity <pEntity> not null,
Obj: pEntity not null
) returns boolean
as $$
return Obj != null;
$$;
-- With EXPOSED AS (toolbox visibility)
create java action Module.Name(Amount: decimal) returns string
exposed as 'Format Amount' in 'Formatting'
as $$
return Amount.toString();
$$;
-- Combined type parameters + EXPOSED AS
create java action Module.Name(
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");
Signals
- GitHub stars
- 122
- Forks
- 49
- Last commit
- Sep 2026
Advanced
- Catalog kind
- skill
- Gateway key
java-actions- Source
- github.com/mendixlabs/mxcli