A deploy goes through cleanly, and then the first user to open a page gets this:
Dependent class is invalid and needs recompilation: Class StDepB : Method does not exist or incorrect signature: void twice(Integer) from the type StDepA
StDepA and StDepB are two small test classes we built for this post. StDepA has one static method, twice():
public class StDepA {
public static Integer twice(Integer n) { return n * 2; }
}
StDepB depends on it by calling that method:
public class StDepB {
public static Integer run() { return StDepA.twice(21); }
}
We then renamed the method in StDepA and saved it:
public class StDepA {
public static Integer doubleIt(Integer n) { return n * 2; }
}
Salesforce saved the change without complaint. StDepB was left calling twice(), which no longer exists, and nothing flagged it until StDepB ran. In a real org, swap in any class that calls a method on another class, such as a controller calling a service or a trigger handler calling a utility.
The deploy didn’t catch it because nothing forced StDepB to compile. Winter ‘27 adds two ways to find these classes before a user does: Compile only invalid classes buttons in Setup and a new Tooling API endpoint, apexCompileResults. We tested both with these two classes and found that two Salesforce documents give opposite answers about whether the buttons reset a class’s IsValid flag.
What “invalid” means
Every Apex class has an IsValid flag. When a class changes, Salesforce marks the classes that depend on it as invalid and recompiles them the next time they run. If the dependency changed in a way the caller can’t compile against, that recompile fails and you get the error above.
The part most developers miss is that IsValid = false doesn’t mean the class is broken. We made a harmless change and the caller was still flagged. Starting again from the original versions of the two classes above, we tracked both with one query:
SELECT Name, IsValid, LastModifiedDate FROM ApexClass WHERE Name LIKE 'StDep%'
| Step | What we did | StDepB.IsValid |
|---|---|---|
| 1 | Created both classes | true |
| 2 | Added an unrelated method to StDepA | false |
| 3 | Setup → Apex Classes → Compile only invalid classes | still false |
| 4 | Ran System.debug(StDepB.run()); in Anonymous Apex | true |
| 5 | Renamed twice to doubleIt in StDepA (the save succeeded) | false |
| 6 | Ran StDepB.run() again | runtime error shown above |
Two results stand out. Salesforce let us save a change that broke a dependent class, with no warning. And the only thing that reset IsValid was running the class, which is also the moment the error reaches a user.
Salesforce’s two documents disagree
Two official Salesforce pages describe the same feature and give opposite answers about whether the Setup buttons reset IsValid.
Source 1: the Winter ‘27 release note. Reduce Overhead by Recompiling Only Invalid Apex Classes and Triggers says a successful compile through either the Compile only invalid buttons or the apexCompileResults endpoint doesn’t update the class’s isValid field. In its words: “The value remains false.”
Source 2: the Tooling API reference. Retrieve Compilation Results for Invalid Apex with the apexCompileResults Resource, in its Usage section, says a successful compile through the Setup buttons “updates the IsValid field on the corresponding Apex class or trigger to true.” Only the endpoint, it says, leaves the field unchanged.
Our step 3 settles it: after clicking the button, StDepB stayed false, which matches Source 1. The practical consequence: you can’t use IsValid to confirm the button worked. A query for IsValid = false after clicking it returns the same classes as before, so check compile results instead.
The two Winter ‘27 tools
Both need API version 68.0 or later, and the feature is available in Enterprise, Performance, Unlimited and Developer editions.
The buttons. Setup → Apex Classes has Compile only invalid classes, and Setup → Apex Triggers has Compile only invalid triggers. Both limit the work to what Salesforce has flagged, which matters in large orgs where compiling everything is slow enough that teams avoid it.
The endpoint. POST /services/data/v68.0/tooling/apexCompileResults/ takes an empty JSON body, runs synchronously, and needs the Author Apex permission. It returns OK when everything invalid compiles, or PARTIAL_FAILURE with line and column for each class or trigger that doesn’t. Classes that compile cleanly aren’t listed. After our breaking change, StDepB appeared like this:
{
"name": "StDepB",
"namespace": "",
"problems": [
{
"column": 49,
"line": 2,
"message": "Method does not exist or incorrect signature: void twice(Integer) from the type StDepA"
}
],
"success": false,
"warnings": []
}
That’s the same compile error the user saw at runtime, reported before anyone ran the class.
What the first call found
Our first request to the endpoint returned PARTIAL_FAILURE before we had broken anything. A few existing classes in the org referenced objects that weren’t enabled there, so each one failed with Invalid type errors. Nobody had noticed, because nothing had called them.
Expect the same in most long-lived orgs. Run the endpoint once by hand before you automate it, and decide for each failing class whether to fix it, delete it, or exclude it from your pipeline check.
Add it to your pipeline
Run the endpoint straight after every deploy and fail the job if anything doesn’t compile. With the Salesforce CLI and jq:
ORG_JSON=$(sf org display --target-org "$TARGET_ORG" --json)
TOKEN=$(echo "$ORG_JSON" | jq -r '.result.accessToken')
INSTANCE=$(echo "$ORG_JSON" | jq -r '.result.instanceUrl')
RESULT=$(curl -s -X POST "$INSTANCE/services/data/v68.0/tooling/apexCompileResults/" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{}')
if [ "$(echo "$RESULT" | jq -r '.status')" != "OK" ]; then
echo "Apex that no longer compiles:"
echo "$RESULT" | jq -r '.results[] | "\(.name): \(.problems[0].message) (line \(.problems[0].line))"'
exit 1
fi
If you have known failures you’ve decided to live with, filter them out by name before the status check so the job only fails on new breakage.
Version bumps are a common trigger for this. Raising a class’s API version can change how it compiles, as the API v67 user mode post shows, and dependents are flagged when you do.
A routine that works
| When | Do |
|---|---|
| After every deploy | Call apexCompileResults and fail on PARTIAL_FAILURE |
| After a major release upgrade | Run it by hand and review anything new |
| Before peak traffic | Execute or test the classes you just changed, since running them is what we saw reset IsValid |
When IsValid = false appears | Check the compile results before assuming the class is broken |
Winter ‘27 has other Apex changes worth reading next to this one, including larger heap limits and the new Web Console and Developer Console toggles.