Skip to content
← Writing

Apex Programming for Beginners: Write a Bulk-Safe Trigger

Apex programming for beginners: learn syntax, SOQL and DML, then write your first bulk-safe trigger in a free Developer Edition org, step by step.

··8 min read·Salesforce AI

Apex programming is writing code in Salesforce's strongly typed, object-oriented, Java-like language for custom server-side logic. You reach for it when clicks can't do the job, and you can learn it free in a Developer Edition org. Start with variables, conditionals, loops, SOQL and DML, then write triggers that handle a whole list of records.

Key takeaways

  • Apex is Salesforce's Java-like, strongly typed language for server-side logic, and you can learn it for free in a Developer Edition org.
  • Learn five things first: variables, conditionals, loops, SOQL and DML. Triggers come straight after.
  • Every trigger receives a list of records, so write for the list from your first line.
  • Write the test before you call the trigger finished.

Most apex programming tutorials teach you to write a trigger that works on one record. Then you load 200 records and it fails.

That's the whole problem with beginner material, and it's why I've built this guide the other way round. Every example here is bulk-safe from the first line. You'll run all of it in a free org, with no install and no credit card.

What is Apex programming, and when should you use it?

Apex is Salesforce's strongly typed, object-oriented language for custom server-side logic, and you reach for it when clicks can't do the job.

The syntax looks like Java, it runs on Salesforce's servers, and it's case-insensitive. Its main job is reading and changing your org's records.

Use Flow first for simple automation. Use Apex when you need complex logic, transaction control or integrations that Flow handles badly. If Flow is new to you, start with how to debug a Salesforce Flow, because you'll end up debugging both.

Salesforce's own Trailhead module on creating code with Apex says conditionals, loops and triggers are the building blocks you'll use on virtually every project. I agree, and that's the order I teach below.

How do you set up a free Developer Edition org for Apex?

Sign up for a free Developer Edition org and use the Developer Console, so you can run code within ten minutes.

  1. Create your org at the Salesforce Developer Edition sign-up page.
  2. Log in, click the gear icon, and choose Developer Console.
  3. Open Debug > Open Execute Anonymous Window.
  4. Paste code, tick Open Log, and click Execute.

Later you'll want Visual Studio Code with the Salesforce extensions. The official quick start for Apex development walks through a custom object, a class, a trigger and a test. Don't start there. The Developer Console gets you running faster.

What is the basic syntax of Apex? Variables, conditionals and loops

Apex syntax is short enough to learn in an afternoon: typed variables, if/else, and for loops over lists.

Paste this into the Execute Anonymous window:

String name = 'Asha';
Integer score = 72;

if (score >= 60) {
    System.debug(name + ' passed with ' + score);
} else {
    System.debug(name + ' needs another attempt');
}

List<Integer> marks = new List<Integer>{ 55, 72, 91 };
Integer total = 0;
for (Integer m : marks) {
    total += m;
}
System.debug('Total: ' + total);

Three things to notice. Every variable has a type. Statements end with semicolons. System.debug() is your print statement, and its output appears in the log.

The collection you'll use most is the List. Triggers hand you lists, queries return lists and DML takes lists. Get comfortable with them early.

What are SOQL and DML in Apex, and why do governor limits matter?

SOQL reads records and DML writes them, and the rule that keeps you safe is to do each once per list, never once per record.

List<Account> accs = [SELECT Id, Name FROM Account LIMIT 5];
for (Account a : accs) {
    a.Description = 'Reviewed';
}
update accs;

One query, one loop, one update. That shape is the habit to build.

Here's why it matters. Apex runs on a shared platform, so Salesforce enforces governor limits. Put a query inside a loop of 101 records and the transaction fails.

PatternWhat happens at 200 recordsFix
Logic that reads Trigger.new[0] onlyRecords 2 to 200 are silently ignoredLoop over Trigger.new

My take: Beginners are taught that governor limits are an advanced topic. I think that's backwards. Limits are the first thing you should learn, because they shape how you write every loop. A fresher who writes "query once, loop, write once" from day one will outperform one who learns it after their first production error.

Your first Apex trigger, step by step

A trigger is Apex code that runs automatically before or after a record is inserted, updated, deleted or undeleted.

The official trigger syntax is trigger TriggerName on ObjectName (trigger_events) { code_block }, and the events are comma-separated, such as before insert, before update.

Create it in the Developer Console with File > New > Apex Trigger, name it AccountTrigger and choose Account. Replace the contents with this:

trigger AccountTrigger on Account (before insert, before update) {
    for (Account acc : Trigger.new) {
        if (String.isBlank(acc.Description)) {
            acc.Description = 'Needs description';
        }
    }
}

Save it. Here's what each part does:

  • Trigger.new is the list of records being saved. It's a list, even if you saved one account.
  • before events let you change the incoming records directly. You don't need a DML statement, because the changes are saved with the record.
  • String.isBlank handles both null and empty text.

Now test it. Create an Account in the UI with no description and check the field. Then test the case that matters, which is 200 records at once:

List<Account> accs = new List<Account>();
for (Integer i = 0; i < 200; i++) {
    accs.add(new Account(Name = 'Bulk Test ' + i));
}
insert accs;

Integer filled = [SELECT COUNT() FROM Account
    WHERE Name LIKE 'Bulk Test%'
    AND Description = 'Needs description'];
System.debug('Filled: ' + filled);

You should see 200.

Collect everything in a list inside the loop and run one DML statement after it.

Change the trigger to this:

trigger AccountTrigger on Account (before insert, before update, after insert) {
    if (Trigger.isBefore) {
        for (Account acc : Trigger.new) {
            if (String.isBlank(acc.Description)) {
                acc.Description = 'Needs description';
            }
        }
    }

    if (Trigger.isAfter && Trigger.isInsert) {
        List<Task> followUps = new List<Task>();
        for (Account acc : Trigger.new) {
            followUps.add(new Task(
                    Subject = 'Welcome call',
                    WhatId = acc.Id,
                    OwnerId = acc.OwnerId,
                    ActivityDate = Date.today().addDays(3)
            ));
        }
        insert followUps;
    }
}

We use after insert here because the Account's Id only exists once the record is saved. The loop builds the tasks, and the single insert runs once for all 200 accounts. That's one DML statement instead of 200.

Once this works, move the logic out of the trigger into a handler class. Keep one trigger per object and let the trigger only decide which method to call. It's a good habit, and interviewers like it. My Salesforce developer interview questions post covers how they ask about it.

How do you test an Apex trigger and reach 75% code coverage?

A bulk test is the one worth writing.

The Apex Developer Guide (version 68.0, Winter '27, last updated 23 October 2026) covers testing in full. Here's a test that proves your trigger handles 200 records:

@isTest
private class AccountTriggerTest {
    @isTest
    static void setsDescriptionForBulkInsert() {
        List<Account> accs = new List<Account>();
        for (Integer i = 0; i < 200; i++) {
            accs.add(new Account(Name = 'Test ' + i));
        }

        Test.startTest();
        insert accs;
        Test.stopTest();

        Integer filled = [SELECT COUNT() FROM Account
            WHERE Description = 'Needs description'];
        Assert.areEqual(200, filled);
    }
}

Run it from Test > New Run in the Developer Console. Test methods don't see your org's existing data by default, so the count is exactly the 200 you created. Coverage is a floor. The assertion is what proves the logic.

What are the most common Apex mistakes beginners make?

Nearly every beginner failure comes from writing for one record, hardcoding something, or skipping the test.

  • SOQL or DML inside a loop. It works in your demo and breaks on the first data load.
  • Reading Trigger.new[0]. It ignores every other record in the batch.
  • Hardcoded record IDs. They change between sandbox and production. Query by name or use custom metadata.
  • Everything in the trigger. It becomes unreadable. Use a handler class.
  • A Flow and a trigger on the same object doing the same job. Know the order of execution in Salesforce before you add the second one.
  • No test, or a test with no assertion. Coverage with no Assert proves nothing.

My take: AI coding assistants, including Agentforce Vibes, will write Apex for you. I'd still learn to write it first. The generated code often looks right and works on one record, and you're the one who has to spot the query inside the loop. Reading Apex critically is becoming the skill, and you only build it by writing it.

What should you learn after your first Apex trigger?

Follow this order: SOQL relationships, handler pattern, asynchronous Apex, then integrations.

Work through the Create Code with Apex unit on Trailhead for the official path. If you're building toward a job, my guide to Salesforce jobs for freshers in 2026 shows which roles ask for Apex and which don't. I also run hands-on sessions for new developers, listed on my events page. More guides like this are on the Salesforce AI hub.

Try this week:

  • Create a free Developer Edition org and run the loop example in Execute Anonymous.
  • Create AccountTrigger and prove it works on 200 records, not one.
  • Write the test class and get it to pass with a real assertion.
  • Deliberately put a SOQL query inside a loop, load 101+ records, and read the error. You'll never forget it.

Frequently asked questions

Is Apex programming hard to learn for beginners?

Apex is easier than most people expect if you know basic programming, because the syntax is small and Java-like. The harder part is learning the platform, especially governor limits and how triggers behave in bulk. Plan for a few weeks of regular practice to write and test your first trigger confidently.

Do I need to know Java before learning Apex?

No. Apex looks like Java, so Java experience helps, but it isn't required. You need variables, conditionals, loops and collections, and you can learn all of those inside Apex itself.

What is the difference between Apex and Flow?

Flow is the declarative tool where you build automation by clicking, and Apex is code. Use Flow for straightforward automation and Apex when you need complex logic, fine control over transactions or integration work Flow handles badly. Many orgs use both, so it's worth learning how they interact.

What are governor limits in Apex?

Governor limits are caps Salesforce places on each transaction so one customer's code can't starve others on the shared platform. Writing bulk-safe code is how you stay inside them.

Can I practise Apex for free?

Yes. A Developer Edition org is free from the Salesforce developer sign-up page, and Trailhead also offers free Playgrounds. Both give you the Developer Console, so you can run every example in this guide without paying.

Want to put this to work in your business?

Join one of my live sessions, or tell me what you're trying to do with AI.

Newsletter

Get the next one by email.

New writing on Salesforce AI, AI agents and the AI-Native Enterprise. Confirm by email; unsubscribe any time.

Keep reading