Mendix Intermediate Developer common mistakes
Mendix Intermediate Developer · Understand the traps and how to avoid them.
Models a relationship as a plain attribute (for example Order_CustomerID as a Long or String) instead of an association, then in microflows retrieves the parent with a constraint like [ID = $Order/CustomerID] or does the join by hand in a loop.
It is the habit carried over from relational/SQL modelling, it avoids having to decide association type and ownership when drawing the line, and it appears to work because auto-numbered IDs are unique and the local dataset is small.
What to do instead
Replace the attribute with a real association of the correct type and multiplicity (reference or reference set, one-way only when the child truly is the sole navigator). Delete the ID attribute, rewrite the retrieves as association XPath such as [Module.Order_Customer = $Customer], and let the platform maintain referential integrity and cascade behaviour.
Retrieves an object with a constraint that matches several rows and uses the result directly (Retrieve single object, or List operation 'Head'), assuming the newest or 'correct' record comes back, without any sort configured on the retrieve.
It passes in the developer's local database because rows are usually returned in insertion order, and the microflow looks shorter than adding a sort or a unique constraint; the assumption is never written down so nobody re-checks it.
What to do instead
Make the retrieve deterministic: either tighten the XPath so it can only match one row (unique business key), or switch to a Retrieve list with an explicit sort on a unique or timestamp attribute and take 'Head'. Add an index on the sorted/constrained attribute if the table is large, and add an empty-check before using the result.
Splits one business transaction across several microflows triggered by successive page buttons or wizard steps, committing partial results at each step, so a validation failure or error in step three leaves steps one and two permanently in the database.
Multi-step UI is easier to build one page at a time, and each step visibly 'works' in local testing because the happy path always completes; the partial-write problem only appears when an error occurs mid-way.
What to do instead
Move the whole sequence into a single microflow with a single commit at the end, keeping the microflow's error handling on Rollback so uncommitted changes are discarded on failure. Hold wizard state in a non-persistent entity passed between steps, and persist only once the final validation succeeds.
Hides a widget with conditional visibility (or leaves it off the page) to 'make a field optional', but the entity still carries a validation rule or a required-attribute setting, so the commit still fails with a validation message the user cannot see or act on.
Conditional visibility is a client-side layout mechanism and looks like it removes the field from the form; validation rules are enforced in the runtime on commit regardless of what the client is displaying, and the mismatch is invisible in the happy-path demo.
What to do instead
Do not use visibility as a substitute for validation. Either remove the static validation rule and enforce the condition in a before-commit microflow that only validates when the dependent field is set, or use a microflow returning a validation feedback object so the message is attached to a visible widget. Keep visibility purely presentational.
Grants a module role access to a page and expects that to protect the data, while entity access for that entity is left at 'None' or is granted broadly (create/read/write for all, no XPath constraint), so grids come up empty for some roles and other users can reach the same records through a different page, a published REST service or a microflow-triggered action.
Page access is the visible, clickable security setting in the model, while entity access is a separate document that is easy to overlook; empty grids get 'fixed' by widening entity access rather than by scoping it.
What to do instead
Treat entity access as the source of truth: set Create/Read/Write/Delete per module role per entity, and add an XPath constraint that scopes records to the logged-in user or their organisation. Verify it by testing each role against every entry point, including published services, which run with their own configured user.
Calls a REST endpoint with the Call REST service activity left on default error handling and then immediately maps the response body into an object or a string variable, assuming a 2xx response, so a 400/401/500 produces an unexpected exception, an unparsed body, or an empty object that flows on through the rest of the microflow.
It works against the sandbox or test endpoint that always returns 200, and the default error handling hides the branching, so the failure path is never designed or exercised.
What to do instead
Set the Call REST activity to custom error handling with an explicit error flow, check the returned HTTP status code and response body before mapping, and return a typed error to the caller (for example an operation result entity with a message). Add a timeout, log the request ID and status, and test the error flow by forcing a 4xx response.
Writes a Java action that reads an entity parameter and calls getValue() on it without checking whether the object is empty, and/or sets attributes on the IMendixObject assuming Java-side changes are automatically persisted.
The Java signature declares the parameter as an entity, which reads as a guarantee that it is non-null, and in local testing the caller always passes a real object; similarly, setValue looks like a save because the object updates in memory.
What to do instead
Guard every object parameter with a null check (or declare it optional and handle empty) and return a defined error instead of throwing a NullPointerException. After setValue, commit explicitly (Core.commit in the Java action, or let the calling microflow commit at the correct point) and document which layer owns the commit. Add a unit test that calls the action with an empty parameter.
Works on the main line for days and commits the whole model in one go, then resolves the resulting conflict on a shared microflow or the project file by choosing their own version wholesale instead of merging.
Studio Pro presents the conflict as a binary choice with an obvious 'keep mine' button, and the impact of the discarded changes is not visible until the other developer's feature is reported broken, often after deployment.
What to do instead
Commit small, coherent units and update from the repository before starting a work session. When a conflict appears, open the document and merge per element in Studio Pro rather than taking one side wholesale; for conflicts you cannot merge safely, revert your copy, pull, and re-apply the change. Never commit the deployment folder, and review the changed-documents list before every commit.
Retrieves every object of an entity (or every child per parent inside a loop) and filters or aggregates them with a loop and an if-statement, rather than expressing the filter in the XPath and letting the database do the work.
Loop-and-filter is the most natural thing to write, it is trivially readable, and it is fast on the few hundred rows in the local database - the cost only shows up on a cloud node with production volume, by which time the microflow is everywhere.
What to do instead
Push constraints and sorting into the database: put the filter in the Retrieve XPath, use aggregate list operations (Count, Sum) instead of building lists, and retrieve a parent's children once with an association constraint instead of retrieving inside the loop. Add an index on attributes used in constraints and sorts, and measure with the performance/log tools on representative data volume rather than local data.
Diagnoses a failing flow by adding message boxes, extra commits or trial-and-error model changes instead of reading the actual error in the console/log or setting a breakpoint, so the change is made in a microflow that is not the one throwing and the real cause stays in place.
Message boxes are quick to add and feel like debugging, and in the cloud the log is a separate screen most developers do not open; the stack trace naming the source microflow, the error type and the level is easy to skim past.
What to do instead
Reproduce, then read the error in the console (local) or the Logs page of the environment (cloud), noting the microflow name, error type and message before changing anything. Set breakpoints and step through with the variables view, or add temporary log messages at the appropriate level rather than message boxes. Fix the root cause, remove the diagnostics, and add an explicit error flow so the same failure is reported properly next time.