| name | java-actions |
| description | Mendix Java Actions — create in Studio Pro, write Java code, create via MDL with type parameters and EXPOSED AS, call from microflows, CE0111 avoidance, and best practices |
| compatibility | opencode |
Mendix Java action author — define, implement, and call custom Java actions from MDL microflows.
> Comprehensive guide for creating and using custom Java actions. Covers Studio Pro definition workflow, Java code patterns, MDL create java action syntax (with type parameters and EXPOSED AS), calling from microflows (CE0111 avoidance), and testable architecture with implementation classes.
Load when:
- Extending Mendix with custom Java logic
- Building integrations with external Java libraries
- Implementing complex algorithms not feasible in microflows
- Calling Java actions from MDL microflows
- Getting CE0111 "duplicate variable" errors on Java action calls
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
Only code between // begin user CODE and // end user CODE is preserved. Everything else is regenerated by Studio Pro.
<creating_via_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;
$$;
Type Parameters (Generics)
ENTITY <pEntity> declares 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).
-- ENTITY <pEntity> = entity type selector, bare pEntity = entity instances
create java action Module.Validate(
EntityType: entity <pEntity> not null,
InputObject: pEntity not null
) returns boolean
as $$
return InputObject != null;
$$;
EXPOSED AS (Toolbox Visibility)
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);
$$;
Supported Parameter Types
| MDL Type | Description |
|---|
string, integer, long, decimal, boolean, datetime | Primitives |
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 |
pEntity (type param ref) | Type parameter reference (entity instance) |
Examples
/** Returns the current timestamp. */
create java action MyModule.GetCurrentTimestamp() returns datetime
as $$
return new java.util.Date();
$$;
/** 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);
$$;
/** Validates an email address. */
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);
$$;
</creating_via_mdl>
<calling_from_microflows>
-- 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 (''):
$count = call java action Module.ExecuteOQL(
Statement = 'SELECT * FROM Module.Entity WHERE Status = ''Active'''
);
Complete Example
create microflow Tax.ACT_CalculateOrderTax($order: Tax.Order)
returns decimal as $taxAmount
begin
declare $subtotal decimal = $order/Subtotal;
declare $taxRate decimal = 0.21;
$taxAmount = call java action Tax.JA_CalculateTax(
Amount = $subtotal,
TaxRate = $taxRate
);
change $order (TaxAmount = $taxAmount);
commit $order;
return $taxAmount;
end;
</calling_from_microflows>
<java_code_patterns>
Basic Structure
package mymodule.actions;
import com.mendix.systemwideinterfaces.core.IContext;
import com.mendix.webui.CustomJavaAction;
import com.mendix.core.Core;
import com.mendix.systemwideinterfaces.core.IMendixObject;
public class JA_CalculateTax extends CustomJavaAction<java.math.BigDecimal>
{
private java.math.BigDecimal amount;
private java.math.BigDecimal taxRate;
public JA_CalculateTax(IContext context, java.math.BigDecimal amount, java.math.BigDecimal taxRate)
{
super(context);
this.amount = amount;
this.taxRate = taxRate;
}
@java.lang.Override
public java.math.BigDecimal executeAction() throws Exception
{
if (this.amount == null || this.taxRate == null) {
return java.math.BigDecimal.ZERO;
}
return this.amount.multiply(this.taxRate);
}
}
Core API Reference
| Method | Description |
|---|
Core.instantiate(context, "Module.Entity") | Create new object |
Core.commit(context, object) | Save to database |
Core.delete(context, object) | Delete object |
Core.rollback(context, object) | Discard uncommitted changes |
Core.retrieveId(context, id) | Retrieve by GUID |
Core.retrieveXPathQuery(context, xpath) | Query with XPath |
Core.microflowCall(name).execute(context) | Call microflow |
</java_code_patterns>
<testable_architecture>
Keep Java action code minimal — only handle parameter extraction and delegation. Put the actual implementation in separate classes under <modulename>.impl.
javasource/
├── mymodule/
│ ├── actions/
│ │ └── JA_ProcessOrder.java # Generated action (minimal code)
│ └── impl/
│ ├── processorder/
│ │ ├── OrderProcessor.java # Pure business logic (no Mendix API)
│ │ ├── OrderData.java # Plain Java data object
│ │ └── MendixOrderAdapter.java # Adapter: Mendix ↔ Java
│ └── shared/
│ └── EmailService.java # Shared utilities
Java Action (Thin Wrapper):
@java.lang.Override
public java.lang.Boolean executeAction() throws Exception
{
MendixOrderAdapter adapter = new MendixOrderAdapter(getContext());
OrderProcessor processor = new OrderProcessor();
OrderData orderData = adapter.toOrderData(this.Order);
ProcessResult result = processor.process(orderData, this.SendNotification);
if (result.isSuccess()) {
adapter.applyResult(this.Order, result);
return true;
} else {
Core.getLogger("MyModule").warn("Order processing failed: " + result.getMessage());
return false;
}
}
Keep OrderProcessor free of Mendix dependencies — then it can be unit tested with plain JUnit without running the Mendix runtime.
</testable_architecture>
<best_practices>
| Element | Convention | Example |
|---|
| Java Action | JA_ prefix + PascalCase | JA_CalculateTax, JA_SendEmail |
| Parameters | PascalCase, descriptive | OrderAmount, CustomerEmail |
- Always wrap in try-catch with logging and MendixRuntimeException for user-facing errors
- Validate inputs early — null-check all parameters
- Batch operations when possible:
Core.commit(context, list) instead of per-object commits
- Use pagination for large datasets (batchSize = 1000)
</best_practices>
<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 |
| CE0111 duplicate variable | DECLARE + CALL both create same variable | Use unique names (see above) |
</common_errors>
<output_rules>Output MDL code only in code blocks. Keep explanations concise.</output_rules>