Acknowledged
CIQQA-4737

`Properties.getValue` crashes the app uncatchably, instead of throwing `InvalidKeyException`, when no property is declared

ENVIRONMENT
Connect IQ SDK: 9.2.0 (connectiq-sdk-mac-9.2.0-2026-06-09-92a1605b2)
OS: macOS 15.7.2 (build 24G322)
Build tooling: monkeyc / monkeydo from the command line; no VS Code, no extension involved
Java: 22.0.1
Reproduced on: simulator, device epix2pro47mm
App type: watch-app, minApiLevel 3.1.0, project.typecheck = 3 (Strict)
Reproduced in the simulator only; I have not tried it on hardware. The complete project is attached;
one source file, one string resource and a launcher icon, nothing else.
SUMMARY
If an app declares no properties, Properties.getValue(key) does not throw. It fails with a
system-level error that no catch clause intercepts, and the app crashes. Declaring a single,
otherwise unused property in properties.xml is enough to make the very same call throw the documented
Properties.InvalidKeyException, which the very same catch then handles.
The documentation states the contract twice. The API reference for
Toybox.Application.Properties.getValue:
Throws: (Properties.InvalidKeyException) — Thrown if key does not exist in Application Settings
and Core Topics → Persisting Data:
If a key that is not present in application properties is passed to Properties.getValue(), an
exception will be thrown
"No properties are declared" is the degenerate case of "key does not exist", and neither statement
carves it out. So InvalidKeyException is what an app is entitled to expect. Instead the failure is
fatal and unguardable.
REPRODUCTION
A complete, standalone project; one source file, no library code. resources/ contains only
strings.xml and a launcher-icon drawable; there is no properties.xml.
using Toybox.Application;
using Toybox.Application.Properties;
using Toybox.System;
using Toybox.WatchUi;
import Toybox.Lang;
class PropertiesReproApp extends Application.AppBase {
function initialize() {
AppBase.initialize();
}
function getInitialView() as [WatchUi.Views] or [WatchUi.Views, WatchUi.InputDelegates] {
System.println("before getValue");
System.println("Properties has :getValue = " + (Properties has :getValue).toString());
try {
Properties.getValue("noSuchProperty");
System.println("getValue returned without throwing");
} catch (e instanceof Properties.InvalidKeyException) {
System.println("caught InvalidKeyException --- would fall back to the default");
} catch (e) {
System.println("caught some other exception --- would fall back to the default");
}
System.println("after getValue --- still running");
return [new ReproView()];
}
}
class ReproView extends WatchUi.View {
function initialize() {
View.initialize();
}
}
Built with monkeyc -d epix2pro47mm -w and project.typecheck = 3: BUILD SUCCESSFUL, no errors,
no warnings. Run with monkeydo repro.prg epix2pro47mm.
Clear the simulator's saved settings between runs, or the bug hides itself; see A stale settings
file masks the bug below. Delete GARMIN/APPS/SETTINGS/<APPNAME>.SET under the simulator's emulated
filesystem, which on macOS lives at $TMPDIR/com.garmin.connectiq. Every output quoted here was
captured from that cleared state, with one binary lineage.
Expected: caught InvalidKeyException --- would fall back to the default, then
after getValue --- still running.
Actual:
before getValue
Properties has :getValue = true
Error: Unexpected Type Error
Details: Failed invoking <symbol>
Stack:
- getInitialView() at .../source/PropertiesRepro.mc:24 0x1000008a
Encountered an app crash.
Neither catch runs. Line 24 is the Properties.getValue call.
THE VARIABLE IS THE PROPERTY TABLE, NOT THE SYMBOL
Same project, same code, only resources/properties/properties.xml changed between runs:
1. No properties.xml; crash, as above.
2. properties.xml present but empty:
<properties>
</properties>
Identical crash, byte for byte. Compiles with no warning.
3. properties.xml declaring one property, never read by the app:
<properties>
<property id="anyProperty" type="number">0</property>
</properties>
before getValue
Properties has :getValue = true
caught InvalidKeyException --- would fall back to the default
after getValue --- still running
The documented exception, from the identical getValue("noSuchProperty") call. So what the call needs
is a non-empty property table; the key it is asked for is still absent in all three runs.
DECLARING NO PROPERTIES IS LEGAL; WHERE THE WORDING IS AMBIGUOUS, THE BEHAVIOUR IS STILL WRONG
One might argue the app is at fault for omitting the resource. The SDK itself says otherwise, in three
independent places.
The schema allows it. bin/resources.xsd, shipped with the SDK, defines the <properties> element
as:
<xs:complexType name="propertiesListType">
<xs:sequence>
<xs:element name="property" type="propertyType" minOccurs="0" maxOccurs="unbounded" />
</xs:sequence>
</xs:complexType>
minOccurs="0". An empty <properties> element is valid by Garmin's own schema; case 2 above, which
crashes.
The documentation never requires a properties resource. No page states that an app must declare
properties, or that a properties file must exist; the string properties.xml does not appear in the
documentation at all, since properties may live in any resource file. The dependency is documented in
the other direction; Properties and Settings:
You can define a property as a default value and not define an associated setting but you cannot
define a setting without tying it to a property
Settings need properties; properties need nothing.
The one sentence that could be read as a requirement, and why it does not rescue the behaviour.
Both the API reference and Persisting Data introduce getValue with a sentence of the form
Property values must be defined in the application settings xml
which is genuinely ambiguous. It can be read as an app must define properties, or as properties,
where an app uses them, must come from a resource rather than being invented at runtime. The second
reading is the one consistent with the schema's minOccurs="0" and with settings depending on
properties rather than the reverse.
The ambiguity does not change the conclusion, because the behaviour is wrong under either reading:
- Permissive reading. An app with no properties is well-formed, and getValue on any key owes the
caller the documented InvalidKeyException. It crashes instead.
- Strict reading. The app is malformed; in which case the SDK should say so where a developer can
act on it: minOccurs="1" in the schema, or a compiler diagnostic. Crashing at runtime, with an
error no catch intercepts, is the one response that helps nobody.
The strict reading also makes the paragraph contradict itself. The very next sentence, in both places,
is the promise that a key "not present in application properties" throws an exception; and with no
properties defined, that is the state of every key. One half of the paragraph would forbid the
situation the other half specifies the behaviour for.
The compiler agrees. All three cases above build successfully at project.typecheck = 3 with -w,
with no error and no warning: no resource at all, an empty <properties> element, and one declared
property.
So an app that declares no properties is well-formed by the schema and by the toolchain, and the
documentation nowhere clearly says otherwise. It is also the normal state of any app that has not added
settings yet; and of many that never will. The only component that treats it as invalid is the
runtime, and it does so by crashing rather than by throwing the exception the documentation promises.
A STALE SETTINGS FILE MASKS THE BUG
Worth knowing before trying to reproduce this, and possibly a defect in its own right.
The simulator persists an app's property table to
GARMIN/APPS/SETTINGS/<APPNAME>.SET in its emulated filesystem
($TMPDIR/com.garmin.connectiq on macOS). That file survives a rebuild, and it survives restarting
the simulator.
So once case 3 has run, the property it declared stays in REPRO.SET:
00000000: abcd abcd 0000 000e 000c 616e 7950 726f ..........anyPro
00000010: 7065 7274 7900 da7a da7a 0000 000f 0b00 perty..z.z......
A rebuild of case 1 or case 2 then declares no property at all, as Rez.mcgen and the .prg both
confirm, yet it runs without crashing, serving a table the current binary does not declare.
Deleting the .SET file brings the crash straight back. I lost a while to this before spotting it, and
anyone reproducing the report will hit it too.
Two consequences:
- The crash is what a user gets, because a fresh install has no .SET file. A developer who has
ever built the same app with a property may never see it.
- Independently of the crash: a build that removes a property still reads that property's table after
reinstall. That looks wrong on its own terms.
WHY AN APP CANNOT DEFEND ITSELF
- Properties has :getValue is true in the crashing build, as the output above shows. The symbol
is linked regardless of whether any property is declared, so the usual has guard does not apply.
Application has :Properties, Application has :getApp and Toybox has :Application are all true
too. I know of no runtime expression that distinguishes case 1 from case 3.
- try/catch does not help, which is the point of the report: a catch-all and a typed catch were
both in place above.
- Nothing is flagged at build time. Strict type checking accepts a getValue call in a project that
declares no properties, with no error and no warning, and an empty <properties> element compiles
silently while producing no usable table.
The practical shape of this is a settings helper that returns a default when a key is unavailable; a
common utility, easy to write, correct-looking, clean at Strict. It works the moment the app has one
property and crashes on its first call in any app that has none, with a stack trace pointing at a
getValue line that the surrounding catch was written to cover.
SUGGESTED FIXES, IN ORDER OF PREFERENCE
1. Throw Properties.InvalidKeyException in the no-table case, matching both the documentation and the
behaviour already observed as soon as one property exists. Nothing else needs to change: apps that
guard the call today would start working.
2. If the runtime genuinely cannot serve getValue without a non-empty property table, then say so
where it can be acted on; tighten resources.xsd to minOccurs="1", and have monkeyc reject or
warn about a Properties.getValue / setValue call in a project that declares no properties. A
build-time error is recoverable; a crash on a user's watch is not.
3. At minimum, document that the call is fatal and uncatchable without a declared property, and that
has cannot detect the condition. Today both the API reference and Persisting Data promise an
exception.