HMCL-dev/HMCL · error · JsonParseException
PortablePath must be a string: " + in.peek()
Error message
PortablePath must be a string: " + in.peek()
What it means
PortablePath's Gson TypeAdapter expects the JSON value for a path field to be a JSON string. This JsonParseException is thrown when the reader encounters a non-string, non-null token (number, boolean, object, array), since a PortablePath can only be deserialized from a string.
Solutions
- Fix the JSON so the path field is a string (add quotes around the value)
- Locate which file/field failed — the message includes the offending JsonToken — and correct that entry
- Restore the config from a known-good copy
- If a schema legitimately changed, update the adapter or mapping to handle the new shape
Example fix
// before
{ "path": 123 }
// after
{ "path": ".minecraft/mods" } Defensive patterns
Strategy: validation
Validate before calling
// before Gson parsing, sanity-check path fields are strings
for (Map.Entry<String, JsonElement> e : obj.entrySet())
if (e.getKey().toLowerCase().endsWith("path") && !e.getValue().isJsonPrimitive())
throw new JsonSyntaxException(e.getKey() + " must be a string"); Type guard
static boolean isPortablePathJson(JsonElement el) {
return el != null && el.isJsonPrimitive() && el.getAsJsonPrimitive().isString();
} Try / catch
try {
PortablePath p = gson.fromJson(json, Config.class).path;
} catch (JsonParseException e) {
if (e.getMessage().startsWith("PortablePath must be a string")) restoreDefaultConfig();
else throw e;
} Prevention
- Validate config JSON against a schema before parsing
- Quote all path values in hand-edited configs
- Version config schemas and migrate rather than silently changing field types
When it happens
Trigger: Deserializing JSON (e.g. modpack/instance config) where a field mapped to PortablePath holds a number, boolean, object, or array instead of a string — usually a schema drift or corrupted/hand-edited config.
Common situations: Hand-edited config files quoting inconsistently; a schema change where a path field became an object; automated tools writing typed values into path fields.
Understand the failure class
Background: "failed to unmarshal" / json.Unmarshal errors: why parsing a response into a Go struct fails and how to fix it — this error's family across 23 libraries.
Related errors
AI-assisted analysis of HMCL-dev/HMCL@24702dc5a0 (2026-09-10).
Data as JSON: /api/errors/2922707cc2605079.
Report an issue: GitHub.
Appendix: source
Thrown at HMCLCore/src/main/java/org/jackhuang/hmcl/util/PortablePath.java:138
/// Gson adapter that serializes portable paths as strings.
@NotNullByDefault
public static final class Adapter extends TypeAdapter<@Nullable PortablePath> {
/// Writes a portable path as its stored path string, or JSON null when the value is null.
@Override
public void write(JsonWriter out, @Nullable PortablePath value) throws IOException {
out.value(value == null ? null : value.getPath());
}
/// Reads a portable path from a string or JSON null.
@Override
public @Nullable PortablePath read(JsonReader in) throws IOException {
if (in.peek() == JsonToken.NULL) {
in.nextNull();
return null;
}
if (in.peek() != JsonToken.STRING) {
throw new JsonParseException("PortablePath must be a string: " + in.peek());
}
return PortablePath.of(in.nextString());
}
}
}
View on GitHub (pinned to 24702dc5a0)