Get Started with Apex Stem
Apex Stem is a thought-driven Apex development stack. You don't need to rewrite anything — start with the smallest piece that makes your code better today, and the stack grows with you.
This page is the short introduction. When you're ready for the full migration playbook, the link at the bottom takes you to the four-step guide with working code.
The code samples run on ApexEloquent v2 and later. In v3, SOQL and DML default to user mode (honouring FLS), so keep the running user's field permissions in mind.
What you'll gain
- Tests stop being laxer than production. A record fetched by real SOQL throws the moment you touch a field it never selected. An SObject you assembled yourself carries no such check — it goes soft in tests and only fails in production. ApexEloquent's mocks know what the production query selected, so they fail in the test instead.
- Unit tests run in milliseconds. Nothing touches the database, so adding flows and triggers to the org doesn't change the speed. Cheap to run means you actually run them, and the feedback loop keeps turning.
- The structure stays visible. Parent-child relationships are written as indentation. Building the query and building the test data both take the shape of the data itself.
- The convention you hand to AI stays light. The architecture declares in about 30 lines (the real thing is in Handler-Usecase Architecture). Parked in
CLAUDE.md, it leaves room for the rest of the context you want read. - No big rewrite required. One Usecase, one mockable query at a time.
The first move
If you only do one thing today, do this: take one SOQL query that's currently inline, and route it through ApexEloquent's typed builder, Scribe. Behavior is identical, but the query becomes mockable and gains SELECT-omission detection.
Before
List<Account> accounts = [
SELECT Id, Name FROM Account WHERE Industry = :industry
];
After
Scribe accountScribe = Scribe.of(Account.class)
.field('Id')
.field('Name')
.whereEqual('Industry', industry);
List<IEntry> accountEntries = new Eloquent().get(accountScribe);
Behavior doesn't change. What changes is this one query, seen from a test.
- It becomes swappable. Inject
MockEloquentinstead ofEloquentand you decide what this query returns, without touching the database. - A missing SELECT starts failing in tests. The example above only selects
IdandName. If downstream code readsIndustry, it throws right there. With a bare SObject you would have gotnull, passed quietly, and found out in production.
That's it. One query at a time is plenty. Carving out Usecases and adding test layers comes when you're ready.
Note: fields are passed as strings (
'Id','Industry'), notSObjectField. That is what lets the builder be assembled dynamically.
When you're ready for more
The full guide walks through four incremental steps with real code:
- Replace SOQL with Scribe (above, expanded)
- Keep results as IEntry — why early SObject conversion is a trap
- Carve out a Usecase — the Layered Constructor Pattern
- Test at the right layer — MockEloquent for Usecases, ApexBlueprint for Handlers
Ready to bring the frameworks into your org? → Installing Apex Stem covers the one-command Unlocked Package install and the git submodule path.