Skip to content
FlutterDocs

[Solved] Unexpected character (at character) Error in Flutter

Verified Solution Tested on Flutter 3.47.7 (Stable) • Dart 3.13.5
Jump to Updated Flutter Example ↓

In this post, we are going to show you how to solve “Unexpected character (at character)” error in Flutter/Dart. This error usually happens while decoding an invalid JSON string, or if you are getting invalid JSON from REST API in the Dio package.

Error Message:

══╡ EXCEPTION CAUGHT BY WIDGETS LIBRARY ╞═════════════════
The following FormatException was thrown building Builder:
Unexpected character (at character 25)

This error happens when you are decoding invalid JSON with json.decode() or jsonDecode().

I/flutter (16776): DioError [DioErrorType.other]: 
FormatException: Unexpected character (at character 1)
I/flutter (16776): <br />
I/flutter (16776): ^
I/flutter (16776):
I/flutter (16776): Source stack:
I/flutter (16776): #0      DioMixin.fetch (package:dio/src/dio_mixin.dart:473:35)
I/flutter (16776): #1      DioMixin.request (package:dio/src/dio_mixin.dart:468:12)
I/flutter (16776): #2      DioMixin.get (package:dio/src/dio_mixin.dart:55:12)

This error happens when you are getting invalid JSON data from REST API. You need to remind, that Dio() package returns decoded objects from JSON strings.

Cause of Error:

The cause of this error is an invalid JSON string, either JSON string inside code lines or JSON string data fetched from REST API.

How to Solve the Error:

Case 1: If you are using jsonDecode() or json.decode() to decode the JSON string, you need to verify if the JSON string is invalid. For example:

String jsonstring = '{"error":false,"msg":"",aa"data":[{"name":"Canada","capital":"Ottawa"}]}';
var jsonobj = jsonDecode(jsonstring); // Runtime Error: Unexpected character (at character)

To solve this error let’s verify if the JSON string is valid or not. You can use any JSON validator. I’m using https://jsonlint.com/ and I got the error like below when validating the JSON.

Error: Parse error on line 3:
...false,	"msg": "",	aa "data": [{		"nam
---------------------^
Expecting 'STRING', got 'undefined'

Its means, I have an invalid JSON string, for example, there is “aa”, an unexpected character after “msg” in the JSON string above code.

You can escape this error with try…catch:

try{
 var jsonobj = jsonDecode(jsonstring);
}catch(e){
  print(e);
}

Case 2: If you are fetching JSON string from REST API, or the internet, you may get an error message generated by the compiler in server-side while generating the JSON string. You can use API tester software to check if the REST API is returning valid JSON, For example:

Here, after checking the response from REST API with API tester software, I got the message like below:

[Solved] Unexpected character (at character) Error in Flutter screenshot

This means, we got the error from the server-side, and the error message is returned by REST API along with JSON string. You can resolve this error on a server-side script or, disable the error message on the compiler.

You can escape this error from Dio() like below:

try{
  response = await dio.get(url);
}catch(e){
  print(e);
}

In this way, you can resolve the “Unexpected character (at character)” Error in Flutter/Dart. This syntax error typically occurs during JSON parsing; see our guide on generating structured JSON data and encoding and decoding strings in Dart.

Updated Flutter Example
SDK: Flutter 3.47.7 (Stable) • Dart 3.13.5 Verified on Flutter 3.47.7 (Stable)

Updated Flutter Example

Safe JSON Parsing & FormatException Error Guard

Key Implementation Details & Modern Differences:

  • Safe Result Tuple: Returns a structured typed record ({dynamic data, JsonParseFailure? failure}) preventing uncaught runtime exceptions.
  • HTML Error Page Detection: Explicitly catches server HTTP 500/502 HTML responses before JSON deserialization to diagnose misleading 'Unexpected character' errors.
  • Offset Diagnostic: Pinpoints the character index and suggests actionable remediations (e.g. single-quote or trailing-comma fixes).
Updated Flutter Example Code
import 'dart:convert';

import 'package:flutter/material.dart';

void main() {
  runApp(const MaterialApp(home: ModernJsonErrorDemo()));
}

/// Typed representation of a JSON parsing error with context and remediation advice.
class JsonParseFailure {
  final String rawInput;
  final String message;
  final int? offset;
  final String remediation;

  const JsonParseFailure({
    required this.rawInput,
    required this.message,
    this.offset,
    required this.remediation,
  });

  @override
  String toString() => 'JsonParseFailure: $message (offset: $offset)';
}

/// Robust JSON parsing utility that guards against typical format exceptions.
class SafeJsonParser {
  /// Safely decodes [source] into structured JSON data or returns a [JsonParseFailure].
  static ({dynamic data, JsonParseFailure? failure}) decode(String source) {
    final trimmed = source.trim();
    if (trimmed.isEmpty) {
      return (
        data: null,
        failure: const JsonParseFailure(
          rawInput: '',
          message: 'Input string is empty',
          remediation:
              'Ensure server response body is not empty before parsing.',
        ),
      );
    }

    // Guard against server returning HTML error pages (e.g., 500 / 404 / 502)
    if (trimmed.startsWith('<!DOCTYPE html>') ||
        trimmed.startsWith('<html>') ||
        trimmed.startsWith('<head>')) {
      return (
        data: null,
        failure: JsonParseFailure(
          rawInput: trimmed,
          offset: 0,
          message: 'Received HTML document instead of JSON payload',
          remediation: 'The server likely returned an unhandled error page (HTTP 500/502). Check API URL, status code, and Content-Type header.',
        ),
      );
    }

    try {
      final decoded = jsonDecode(trimmed);
      return (data: decoded, failure: null);
    } on FormatException catch (e) {
      return (
        data: null,
        failure: JsonParseFailure(
          rawInput: trimmed,
          offset: e.offset,
          message: e.message,
          remediation: 'Verify JSON syntax: ensure strings and keys use double quotes, no trailing commas, and brackets match.',
        ),
      );
    } catch (e) {
      return (
        data: null,
        failure: JsonParseFailure(
          rawInput: trimmed,
          message: e.toString(),
          remediation: 'An unexpected error occurred during deserialization.',
        ),
      );
    }
  }
}

class ModernJsonErrorDemo extends StatefulWidget {
  const ModernJsonErrorDemo({super.key});

  @override
  State<ModernJsonErrorDemo> createState() => _ModernJsonErrorDemoState();
}

class _ModernJsonErrorDemoState extends State<ModernJsonErrorDemo> {
  final TextEditingController _controller = TextEditingController();
  dynamic _parsedData;
  JsonParseFailure? _failure;

  static const _sampleValid =
      '{"id": 101, "title": "Flutter 3.47 Guide", "active": true}';
  static const _sampleHtml =
      '<!DOCTYPE html><html><body><h1>500 Internal Server Error</h1></body></html>';
  static const _sampleBadQuotes = "{'id': 102, 'name': 'Single Quotes'}";

  @override
  void initState() {
    super.initState();
    _controller.text = _sampleValid;
    _runParse();
  }

  @override
  void dispose() {
    _controller.dispose();
    super.dispose();
  }

  void _runParse() {
    final result = SafeJsonParser.decode(_controller.text);
    setState(() {
      _parsedData = result.data;
      _failure = result.failure;
    });
  }

  @override
  Widget build(BuildContext context) {
    final theme = Theme.of(context);

    return Scaffold(
      appBar: AppBar(title: const Text('Safe JSON Parser & Error Guard')),
      body: SingleChildScrollView(
        padding: const EdgeInsets.all(20.0),
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.stretch,
          children: [
            Text('Input Payload:', style: theme.textTheme.titleMedium),
            const SizedBox(height: 8),
            TextField(
              controller: _controller,
              maxLines: 4,
              decoration: const InputDecoration(
                border: OutlineInputBorder(),
                hintText: 'Enter JSON or API response body...',
              ),
              onChanged: (_) => _runParse(),
            ),
            const SizedBox(height: 12),
            Wrap(
              spacing: 8,
              runSpacing: 8,
              children: [
                OutlinedButton(
                  onPressed: () {
                    _controller.text = _sampleValid;
                    _runParse();
                  },
                  child: const Text('Valid JSON'),
                ),
                OutlinedButton(
                  onPressed: () {
                    _controller.text = _sampleHtml;
                    _runParse();
                  },
                  child: const Text('HTML Error Page'),
                ),
                OutlinedButton(
                  onPressed: () {
                    _controller.text = _sampleBadQuotes;
                    _runParse();
                  },
                  child: const Text('Single Quotes Error'),
                ),
              ],
            ),
            const SizedBox(height: 24),
            Text('Parser Status:', style: theme.textTheme.titleMedium),
            const SizedBox(height: 8),
            if (_failure != null)
              Card(
                color: theme.colorScheme.errorContainer,
                child: Padding(
                  padding: const EdgeInsets.all(16.0),
                  child: Column(
                    crossAxisAlignment: CrossAxisAlignment.start,
                    children: [
                      Row(
                        children: [
                          Icon(
                            Icons.error_outline,
                            color: theme.colorScheme.error,
                          ),
                          const SizedBox(width: 8),
                          Expanded(
                            child: Text(
                              _failure!.message,
                              key: const Key('json-error-message'),
                              style: theme.textTheme.titleSmall?.copyWith(
                                color: theme.colorScheme.onErrorContainer,
                                fontWeight: FontWeight.bold,
                              ),
                            ),
                          ),
                        ],
                      ),
                      const SizedBox(height: 8),
                      Text(
                        'Remediation: ${_failure!.remediation}',
                        style: theme.textTheme.bodyMedium?.copyWith(
                          color: theme.colorScheme.onErrorContainer,
                        ),
                      ),
                    ],
                  ),
                ),
              )
            else
              Card(
                color: theme.colorScheme.primaryContainer,
                child: Padding(
                  padding: const EdgeInsets.all(16.0),
                  child: Column(
                    crossAxisAlignment: CrossAxisAlignment.start,
                    children: [
                      Row(
                        children: [
                          Icon(
                            Icons.check_circle_outline,
                            color: theme.colorScheme.primary,
                          ),
                          const SizedBox(width: 8),
                          Text(
                            'Valid JSON Deserialized',
                            key: const Key('json-success-message'),
                            style: theme.textTheme.titleSmall?.copyWith(
                              color: theme.colorScheme.onPrimaryContainer,
                              fontWeight: FontWeight.bold,
                            ),
                          ),
                        ],
                      ),
                      const SizedBox(height: 8),
                      Text(
                        'Parsed: $_parsedData',
                        style: theme.textTheme.bodyMedium?.copyWith(
                          color: theme.colorScheme.onPrimaryContainer,
                        ),
                      ),
                    ],
                  ),
                ),
              ),
          ],
        ),
      ),
    );
  }
}
Document History & Archival Record

Original reference snapshot archived March 22, 2023. Updated working solution verified on modern Flutter & Dart SDKs.