Part 2 of 2 - Troubleshooting the development environment
In Part 1, I covered how I approached setting up a reliable .NET MAUI development environment on macOS.
This article focuses on what happened when things didn't work as expected.
The interesting part of setting up a development machine isn't always installing the tools. Sometimes the tools are installed, the expected commands exist, and everything looks correct - but the actual application still cannot build or run.
That's where troubleshooting becomes important.
During my setup, I encountered several situations where the first assumption wasn't the actual cause of the problem.
That led me to a simple principle:
Don't troubleshoot based only on what is installed. Troubleshoot based on what the application and build tools can actually see and use.
Table of Contents
- 1. Start With the Error, Not the Installation
- 2. Xcode Is Installed - So Why Isn't It Working?
- 3. When Xcode and MAUI Versions Need to Align
- 4. When Android Setup Doesn't Complete Cleanly
- 5. The JDK Exists, But the Build Can't Find It
- 6. From ADB to a Real Physical Device
- 7. Build Success Isn't the Finish Line
- 8. A Systematic Troubleshooting Approach
- My Troubleshooting Checklist
- What I Learned
- Conclusion
1. Start With the Error, Not the Installation
When a development environment isn't working, the natural reaction is often:
"Something isn't installed correctly."
Sometimes that's true.
But reinstalling everything isn't necessarily the best first step.
A better starting point is the actual error.
Think about the environment as a dependency chain:
Project
↓
.NET SDK
↓
.NET MAUI workload
↓
Platform tooling
↓
Build tools
↓
Simulator / Device
↓
Application
If the application fails somewhere in that chain, the error usually gives you a starting point.
For example:
iOS build failure
↓
Check Xcode
↓
Check selected developer directory
↓
Check .NET / MAUI workload
↓
Check compatibility
Or:
Android build failure
↓
Check Android SDK
↓
Check JDK
↓
Check JDK discovery
↓
Check build configuration
The goal is to narrow the problem down before changing anything.
A useful rule
Find the failing layer before trying to fix the environment.
This sounds obvious, but it can prevent a lot of unnecessary reinstallations.
2. Xcode Is Installed - So Why Isn't It Working?
One of the first issues I encountered was with Xcode.
The full Xcode application was installed, but the active developer directory wasn't pointing to it.
I checked:
xcode-select -p
and got:
/Library/Developer/CommandLineTools
This was an important clue.
Having Apple's Command Line Tools selected doesn't necessarily mean the full Xcode installation is the active developer environment.
I then checked the actual Xcode command-line tooling:
xcodebuild -version
and the simulator tooling:
xcrun simctl list
These checks helped distinguish between:
Xcode is installed
and:
The expected Xcode tooling is available to the development environment
If necessary, the active developer directory can be changed:
sudo xcode-select --switch /Applications/Xcode.app
Then verify again:
xcode-select -p
xcodebuild -version
The lesson
Don't stop at:
"Xcode is installed."
Ask:
"Is the correct Xcode installation active, and can the command-line tooling use it?"
This was one of the first examples that changed how I looked at the setup.
3. When Xcode and MAUI Versions Need to Align
Another important part of the setup was version compatibility.
A .NET MAUI application isn't dependent on a single version.
There are several layers:
.NET SDK
↓
.NET MAUI workload
↓
.NET iOS workload
↓
Xcode
↓
Apple SDK
The project repository can also define specific versions.
For an existing application, files such as:
global.json
Directory.Build.props
*.csproj
can tell you what the project expects.
In my setup, the repository pinned the .NET SDK and MAUI versions. The project used .NET SDK 10.0.203 and MAUI 10.0.60, so simply installing the newest available versions wasn't the goal.
The goal was to reproduce the environment expected by the project.
This is particularly important when setting up an existing application rather than starting a brand-new project.
Capture the versions together
When troubleshooting an iOS build, I found it useful to capture:
dotnet --info
dotnet workload list
xcodebuild -version
Instead of looking at these as independent applications, treat them as one toolchain.
For example:
.NET SDK
+
MAUI workload
+
Xcode
+
Apple SDK
=
iOS development toolchain
If one component changes, the others may become relevant.
The lesson
For an existing project, compatibility is more important than simply using the newest version.
4. When Android Setup Doesn't Complete Cleanly
Android setup presented a different challenge.
The initial machine didn't have all the Android development components available.
The setup process needed to establish:
Android SDK
JDK
Build Tools
Platform Tools
ADB
An automated setup process installed Microsoft OpenJDK 21 and bootstrapped the Android SDK, but the overall setup eventually timed out while installing packages.
At that point, it would have been easy to assume:
"The Android setup failed. Start again."
Instead, I checked what had actually been installed.
For example:
java -version
and:
adb version
Then I inspected the Android SDK and its installed components.
This matters because a timeout doesn't necessarily mean that every operation performed before the timeout was unsuccessful.
You may already have:
- the JDK
- Android SDK
- platform tools
- build tools
- SDK platforms
installed and ready.
The better approach
Instead of:
Setup failed
↓
Reinstall everything
try:
Setup failed / timed out
↓
Inspect current state
↓
Identify what's missing
↓
Install only what's missing
↓
Continue verification
The lesson
When an automated setup fails, inspect the resulting environment before starting over.
5. The JDK Exists, But the Build Can't Find It
This was the most interesting Android issue I encountered.
The JDK was installed.
Java was available on the machine.
But the .NET MAUI Android build still failed.
The build reported an XA5300 error because it couldn't locate the Java SDK.
The important part of the investigation was that the problem wasn't simply:
"Java isn't installed."
The JDK existed.
The problem was JDK discovery.
The build process was using Java discovery through:
/usr/libexec/java_home
but it wasn't resolving the installed JDK in the way the build expected.
This highlighted an important distinction:
JDK installed
≠
JDK discoverable
≠
JDK usable by the build
Step 1: Verify Java
First:
java -version
Then:
/usr/libexec/java_home -V
These commands answer slightly different questions.
The first tells you whether Java is available.
The second helps determine what JDK installations macOS can discover through its Java tooling.
Step 2: Find the actual JDK
The next step was to determine where the JDK was actually installed.
In this setup, the JDK was installed under the Android developer directory.
At this point, the question changed from:
"Is Java installed?"
to:
"What JDK path should the MAUI Android build use?"
Step 3: Explicitly configure the JDK
Rather than continuing to rely on automatic discovery, I configured the build to use the explicit JDK location.
After that change, the Android build progressed successfully.
Why this matters
This pattern applies to many development tools.
A dependency can be:
- installed
- visible in the file system
- available from your terminal
and still be unavailable to the process that actually needs it.
The same idea applies to SDKs, compilers, runtimes, command-line tools, and environment variables.
The lesson
When a build says a dependency is missing, verify whether it is actually missing or simply undiscoverable.
That distinction can save a lot of time.
6. From ADB to a Real Physical Device
Once the Android build environment was working, I wanted to verify the complete development workflow on a physical device.
A physical Android device introduces another layer:
Mac
↓
Android SDK
↓
ADB
↓
Physical device
↓
MAUI application
↓
Debugger
The first check was deliberately independent of MAUI:
adb devices
The device appeared as an authorized device.
That confirmed:
Mac
↓
ADB
↓
Device
But that still wasn't enough.
ADB seeing the device doesn't automatically prove that:
- the MAUI project can build
- the application can deploy
- the application can launch
- the debugger can attach
So I treated these as separate verification steps.
The complete sequence
1. Connect device
↓
2. Enable USB debugging
↓
3. Authorize the Mac
↓
4. Verify with adb devices
↓
5. Build the MAUI project
↓
6. Deploy the application
↓
7. Launch the application
↓
8. Attach the debugger
This is much more useful than simply checking whether the device appears in ADB.
The lesson
Verify each boundary separately.
When something fails, you immediately know which layer to investigate.
7. Build Success Isn't the Finish Line
Another lesson from the setup was that a successful build isn't the same as a ready development environment.
It's possible to reach:
Build succeeded
and still have problems with:
- deployment
- simulator/device detection
- application startup
- debugger attachment
- runtime behavior
That's why I prefer validating the complete workflow:
Restore
↓
Build
↓
Deploy
↓
Launch
↓
Debug
↓
Breakpoint
For iOS, this means verifying that the Simulator can run the application.
For Android, it means going beyond a successful build and validating deployment on an emulator or physical device.
The final test isn't:
"Did the command finish successfully?"
It's:
"Can I actually use this machine for my normal development workflow?"
That is a much stronger definition of "ready."
8. A Systematic Troubleshooting Approach
After going through these issues, I found a simple process useful.
Step 1: Read the error carefully
Don't immediately jump to a fix.
Identify what the error is actually saying.
For example:
Can't find Java SDK
doesn't necessarily mean:
Java isn't installed
It could mean:
The build can't discover the installed Java SDK
Those require different actions.
Step 2: Identify the failing layer
Use the dependency chain:
Project
↓
.NET SDK
↓
MAUI workload
↓
iOS / Android tooling
↓
Build tools
↓
Simulator / Device
↓
Application
Find the smallest layer that explains the failure.
Step 3: Verify the dependency independently
Use simple commands to validate individual components.
For .NET:
dotnet --info
dotnet workload list
For Xcode:
xcode-select -p
xcodebuild -version
For Java:
java -version
/usr/libexec/java_home -V
For Android:
adb version
adb devices
The goal is to separate:
Project problem
from:
Environment problem
Step 4: Compare expected vs actual
This is especially important for existing projects.
Ask two questions:
What does the project expect?
and:
What is the machine actually using?
For example:
Expected SDK version
vs
Active SDK version
or:
Expected JDK location
vs
JDK location discovered by the build
Step 5: Change one thing at a time
If you simultaneously change:
- .NET SDK
- MAUI workload
- Xcode
- JDK
- Android SDK
and the build starts working, you may never know which change fixed it.
Whenever possible:
Observe
↓
Change one thing
↓
Verify
↓
Continue
This makes troubleshooting much easier to reason about.
Step 6: Re-test the real workflow
After making a change, don't stop at the individual command that previously failed.
Run the actual workflow again:
Restore
↓
Build
↓
Deploy
↓
Run
↓
Debug
That's the final validation.
My Troubleshooting Checklist
.NET
- [ ]
dotnet --infoshows the expected SDK - [ ]
global.jsonrequirements checked - [ ] MAUI workload installed
- [ ] Workload version verified
iOS
- [ ] Xcode installed
- [ ] Correct Xcode selected
- [ ]
xcodebuildworks - [ ] iOS runtimes available
- [ ] Simulator launches
- [ ] Project builds for iOS
Android
- [ ] JDK installed
- [ ] JDK discoverable by the build
- [ ] Android SDK installed
- [ ] Required platform installed
- [ ] Build tools installed
- [ ] Platform tools installed
- [ ]
adbavailable - [ ] Device authorized
- [ ] Project builds for Android
Application
- [ ] Dependencies restored
- [ ] Application builds
- [ ] Application deploys
- [ ] Application launches
- [ ] Debugger attaches
- [ ] Breakpoints work
What I Learned
The biggest change in my approach was moving away from installation-based troubleshooting.
It is easy to think:
"The JDK is installed, so Java isn't the problem."
Or:
"Xcode is installed, so iOS development should work."
But the more useful questions are:
What does the project require?
What is actually installed?
What is currently selected?
Can the build process discover it?
Can the application actually use it?
That gives us a much more useful model:
PROJECT
│
▼
What does it need?
│
▼
What is installed?
│
▼
What is actually selected?
│
▼
Can the build process find it?
│
▼
Does the build work?
│
▼
Can the application run?
│
▼
Can I actually debug it?
Every failed step reduces the area you need to investigate.
Conclusion
Setting up a development machine isn't really about installing a list of applications.
It's about creating a working chain of dependencies.
When something breaks, the fastest approach isn't always to reinstall everything.
Instead:
Inspect → Isolate → Verify → Fix → Re-test
Start with the actual failure.
Understand which layer is failing.
Verify the dependency independently.
Make the smallest necessary change.
Then test the complete application workflow again.
The most useful distinction I took away from this experience is:
Installed ≠ configured ≠ discoverable ≠ usable.
A tool is only useful when the application and build system can actually find and use it.
That's what turns a development machine from a collection of installed tools into a reliable development environment.
If you haven't read Part 1 yet, it covers the initial setup and verification process that led to these troubleshooting scenarios.
Part 1: Setting Up a Reliable .NET MAUI Development Environment on macOS
Top comments (0)