File Handling in Dart

By Syncfusion®, Inc. · Last updated

How to read and write files and folders with dart:io, and how to save and load data as JSON with dart:convert, model classes and safe parsing.

What Is File Handling?

File handling = reading data from files and writing data to files on a disk.

  • Variables live only while the program runs. When it ends, they are gone.
  • Files stay after the program ends. Next time, you can read them again.
  • In Dart, file tools come from the library dart:io.

Simple lesson: files are the program's long-term memory.

A Real-World Example

Think of the class attendance register.

  • Every morning, the teacher reads yesterday's page.
  • She adds today's names at the bottom. She does not tear out old pages.
  • At the end of the year, a new register is created, and old ones are kept in a cupboard (a folder).
  • If the register is missing, she reports it, instead of giving up teaching.

Try each action on a pretend file:

Every Run box on this page has its own pretend disk, made fresh for each run. It already holds:

  • notes.txt, marks.csv and a read-only locked.txt
  • a folder data with students.json and config.json

On your computer, dart:io uses your real disk. dart:io does not work in web apps.

What You Will Learn

In this lesson, you will learn:

  1. Paths, and how to read and write files, sync and async.
  2. How to append, check file details and work with folders.
  3. How to handle missing files and permissions.
  4. How to encode and decode JSON.
  5. How to write model classes with fromJson and toJson, and parse safely.

Paths

Path = the address of a file on the disk.

KindStarts fromExample
Absolute paththe top of the disk/home/student/notes.txt or C:\school\notes.txt
Relative paththe current folder of the programnotes.txt, data/students.json
  • .. means "the folder above".
  • Prefer / in Dart code. It works on Windows too.

A relative path and its absolute form

import 'dart:io';

void main() {
  var f = File('data/students.json');
  print(f.path);
  print(f.absolute.path);
  print(Directory.current.path);
}

Simple lesson: a relative path starts from where the program runs, and an absolute path starts from the top of the disk.

Reading Files

Whole File, Lines, or Bytes

MethodGives
readAsStringSync()the whole file as one String
readAsLinesSync()a List<String>, one item per line
readAsBytesSync()a List<int> of raw bytes

Reading the notes file

import 'dart:io';

void main() {
  var notes = File('notes.txt');
  var text = notes.readAsStringSync();
  print(text.length);

  var lines = notes.readAsLinesSync();
  for (var i = 0; i < lines.length; i++) {
    print('${i + 1}. ${lines[i]}');
  }
}

Reading a CSV File

CSV = "comma-separated values": a table written as text, one row per line.

Average marks from a CSV file

import 'dart:io';

void main() {
  var rows = File('marks.csv').readAsLinesSync();
  print('Header: ${rows.first}');
  for (var row in rows.skip(1)) {
    var parts = row.split(',');
    var maths = int.parse(parts[1]);
    var science = int.parse(parts[2]);
    print('${parts[0]}: ${((maths + science) / 2).toStringAsFixed(1)}');
  }
}

Simple lesson: read the whole text, the lines, or the bytes, whichever suits your file.

Sync and Async

Every file method has two versions:

SyncAsyncDifference
readAsStringSync()readAsString()async returns a Future<String>
writeAsStringSync(t)writeAsString(t)async returns a Future<File>
existsSync()exists()async returns a Future<bool>
  • Sync waits right there. Simple, fine for small scripts.
  • Async lets the program do other work while the disk is busy. Use it in apps and servers. (See Async Basics and Futures.)

The async way with await

import 'dart:io';

Future<void> main() async {
  var file = File('notes.txt');
  if (await file.exists()) {
    var lines = await file.readAsLines();
    print('${lines.length} notes found');
    print('First: ${lines.first}');
  }
}

Simple lesson: ...Sync() waits for the disk, and the version without Sync returns a Future.

Writing Files

Write and Replace

writeAsStringSync(text) creates the file if it is missing, and replaces everything if it exists.

Append

Append = add to the end of a file, keeping what was there. Pass mode: FileMode.append.

Write, then append

Saving a small report

import 'dart:io';

void main() {
  var report = File('report.txt');
  report.writeAsStringSync('Class 7A report\n');
  var marks = {'Asha': 92, 'Ravi': 75, 'Priya': 85};
  for (var e in marks.entries) {
    report.writeAsStringSync('${e.key}: ${e.value}\n', mode: FileMode.append);
  }
  print(report.readAsStringSync());
}

Without mode: FileMode.append, every write wipes the file first.

Many beginners lose their saved data this way. Use append when you are adding records.

Simple lesson: a normal write replaces the file, and FileMode.append adds to its end.

File Details

MethodGives
existsSync()true if the file is there
lengthSync()size in bytes
lastModifiedSync()a DateTime of the last change
deleteSync()removes the file
renameSync('new.txt')moves or renames it

Checking, measuring and deleting

import 'dart:io';

void main() {
  var f = File('old_notice.txt');
  f.writeAsStringSync('Holiday on Friday');
  print(f.existsSync());
  print(f.lengthSync());
  f.deleteSync();
  print(f.existsSync());
}

Simple lesson: check exists before you read, and use length and lastModified to learn about a file.

Directories

Directory = a folder. Use the Directory class.

Making a folder and listing it

import 'dart:io';

void main() {
  var reports = Directory('reports/2026');
  reports.createSync(recursive: true);
  File('reports/2026/7A.txt').writeAsStringSync('Class 7A');
  File('reports/2026/7B.txt').writeAsStringSync('Class 7B');

  for (var item in reports.listSync()) {
    print(item.path);
  }
  print(Directory('data').listSync().length);
}
  • recursive: true also makes the missing parent folders (reports).
  • listSync() gives the files and folders inside. list() is the async version, a Stream.
  • deleteSync(recursive: true) removes a folder with everything in it. Be careful.

Simple lesson: Directory makes, lists and deletes folders, just like File does for files.

When Things Go Wrong

File work fails more than most code: the file is missing, or you are not allowed to touch it.

  • FileSystemException = the parent type for file problems.
  • PathNotFoundException = a kind of FileSystemException: the path does not exist.

Handling a missing file and a locked file

import 'dart:io';

void main() {
  try {
    File('homework.txt').readAsStringSync();
  } on PathNotFoundException {
    print('homework.txt is missing. Starting a new one.');
  }

  try {
    File('locked.txt').writeAsStringSync('Changed!');
  } on FileSystemException catch (e) {
    print('Could not write: ${e.message}');
  }
}

Easy way to remember

  • import 'dart:io'; first.
  • read: readAsString, readAsLines, readAsBytes.
  • write: writeAsString replaces, mode: FileMode.append adds.
  • check: exists, length, lastModified, delete.
  • Wrap file work in try with on FileSystemException.

Simple lesson: always expect file errors, and catch FileSystemException so the program can recover.

What Is JSON?

JSON (JavaScript Object Notation) = a simple text format for data. Almost every app and server uses it to send data.

  • Object = { "key": value, ... }. In Dart it becomes a Map<String, dynamic>.
  • Array = [value, value, ...]. In Dart it becomes a List<dynamic>.
  • Values can be strings (in double quotes), numbers, true, false, null, objects and arrays.

Simple lesson: JSON is data written as text, so any program can read it.

A Real-World Example

The school app shows your marks.

  • The marks live on the school's server.
  • The server sends them to your phone as JSON text: {"name": "Asha", "marks": [92, 88]}.
  • The app decodes the text into Dart values, and shows them on screen.
  • When you update your profile, the app encodes your data as JSON and sends it back.

Edit the JSON text and see what Dart gets:

jsonEncode and jsonDecode come from dart:convert. Remember the import.

JSON Structure

JSONDartExample
object { }Map<String, dynamic>{"name": "Asha"}
array [ ]List<dynamic>[92, 88]
stringString"Blue"
numberint or double13, 1.45
true / falsebooltrue
nullnullnull

JSON is strict:

  • Keys must be in double quotes: "name", not name or 'name'.
  • No comma after the last item.
  • No comments.

Simple lesson: JSON has objects, arrays and simple values, and Dart turns them into maps, lists and values.

Encoding and Decoding

jsonEncode

Encode = Dart value → JSON text.

A map becomes JSON text

import 'dart:convert';

void main() {
  var student = {'name': 'Asha', 'age': 13, 'monitor': true, 'marks': [92, 88]};
  var text = jsonEncode(student);
  print(text);
  print(text.length);
}

For text that people read, use JsonEncoder.withIndent:

Pretty JSON

import 'dart:convert';

void main() {
  var config = {'school': 'Green Valley', 'year': 2026, 'houses': ['Red', 'Blue']};
  print(JsonEncoder.withIndent('  ').convert(config));
}

jsonDecode

Decode = JSON text → Dart values.

From text to a map

Simple lesson: jsonEncode writes Dart data as text, and jsonDecode reads text back into Dart data.

Maps, Lists and Nested JSON

jsonDecode returns dynamic. Tell Dart what you expect, so the rest of the code is typed:

Nested JSON from a server

import 'dart:convert';

void main() {
  var text = '''
  {
    "class": "7A",
    "teacher": {"name": "Mrs. Iyer", "subject": "Science"},
    "students": [
      {"name": "Asha", "marks": 92},
      {"name": "Ravi", "marks": 75}
    ]
  }
  ''';
  var data = jsonDecode(text) as Map<String, dynamic>;
  var teacher = data['teacher'] as Map<String, dynamic>;
  var students = data['students'] as List<dynamic>;

  print('${data['class']} is taught by ${teacher['name']}');
  for (var s in students) {
    print('${s['name']}: ${s['marks']}');
  }
}
  • An object inside an object: read it as another map.
  • An array of objects: a list whose items are maps.

Simple lesson: follow the shape of the JSON: maps inside maps, and lists of maps.

Model Classes

Reading s['name'] everywhere is easy to get wrong. A model class gives the data real fields.

  • fromJson = a factory constructor that builds an object from a map.
  • toJson = a method that turns the object into a map.
  • jsonEncode automatically calls toJson() on objects it does not know.

Student.fromJson and toJson

import 'dart:convert';

class Student {
  final String name;
  final int age;
  final List<int> marks;

  Student(this.name, this.age, this.marks);

  factory Student.fromJson(Map<String, dynamic> json) {
    return Student(
      json['name'] as String,
      json['age'] as int,
      (json['marks'] as List).cast<int>(),
    );
  }

  Map<String, dynamic> toJson() => {'name': name, 'age': age, 'marks': marks};

  int get total => marks.fold(0, (a, b) => a + b);
}

void main() {
  var s = Student.fromJson(jsonDecode('{"name": "Asha", "age": 13, "marks": [92, 88]}'));
  print('${s.name} scored ${s.total}');
  print(jsonEncode(s));
}

Lists of Objects and Nested Objects

A class with its students

import 'dart:convert';

class Student {
  final String name;
  final int marks;
  Student(this.name, this.marks);

  factory Student.fromJson(Map<String, dynamic> j) => Student(j['name'], j['marks']);
  Map<String, dynamic> toJson() => {'name': name, 'marks': marks};
}

class Section {
  final String code;
  final List<Student> students;
  Section(this.code, this.students);

  factory Section.fromJson(Map<String, dynamic> j) => Section(
        j['code'],
        (j['students'] as List).map((s) => Student.fromJson(s)).toList(),
      );

  Map<String, dynamic> toJson() => {'code': code, 'students': students};
}

void main() {
  var text = '{"code": "7B", "students": [{"name": "Priya", "marks": 85}, {"name": "Imran", "marks": 79}]}';
  var section = Section.fromJson(jsonDecode(text));
  print('${section.code} has ${section.students.length} students');
  print(section.students.last.name);
  print(jsonEncode(section));
}

jsonEncode(section) calls section.toJson(), which holds Student objects, so it calls their toJson() too.

jsonEncode fails on objects with no toJson(), and on things like DateTime and Set.

Convert them first: a date to date.toIso8601String(), and a set to set.toList().

Simple lesson: fromJson turns a map into an object, and toJson turns it back, so the rest of your code uses real fields.

Parsing Safely

Data from outside can be wrong: a key may be missing, a value may be null, or a number may be text.

Bad JSON Text

Catching broken JSON

import 'dart:convert';

void main() {
  for (var text in ['{"name": "Asha"}', "{name: 'Asha'}"]) {
    try {
      var data = jsonDecode(text);
      print('OK: ${data['name']}');
    } on FormatException {
      print('Not valid JSON: $text');
    }
  }
}

Missing Keys and null

Use the tools from Null Safety: ?, ?? and type checks:

Defaults for missing data

import 'dart:convert';

void main() {
  var data = jsonDecode('{"name": "Ravi", "house": null}') as Map<String, dynamic>;
  var name = data['name'] as String? ?? 'Unknown';
  var house = data['house'] as String? ?? 'Not chosen yet';
  var age = data['age'] as int? ?? 0;
  print('$name, $house, age $age');
}

Validating with Patterns

A pattern (from Pattern Matching) checks keys and types in one step:

Accepting only well-formed students

import 'dart:convert';

void main() {
  var replies = [
    '{"name": "Asha", "marks": 92}',
    '{"name": "Ravi", "marks": "ninety"}',
    '{"marks": 70}',
  ];
  for (var r in replies) {
    var data = jsonDecode(r);
    if (data case {'name': String name, 'marks': int marks}) {
      print('Saved $name with $marks');
    } else {
      print('Rejected: $r');
    }
  }
}

Where JSON Is Used

UseExample
API responsesthe server sends a student list
Config filesconfig.json with the school name and settings
Local storagesaving a user's choices to a file

Easy way to remember

  • import 'dart:convert';
  • encode = Dart → text. decode = text → Dart.
  • object → Map<String, dynamic>, array → List<dynamic>.
  • fromJson in, toJson out.
  • Never trust outside data: catch FormatException, use ??, and check with patterns.

Simple lesson: treat outside JSON as untrusted, and check it before you use it.

TL;DR

  1. dart:io gives File and Directory. Not available on the web.
  2. Read: readAsString, readAsLines. Write: writeAsString, FileMode.append. Each has a Sync version.
  3. Handle FileSystemException for missing or locked files.
  4. JSON (dart:convert): jsonEncode → text, jsonDecode → maps and lists.
  5. Model classes: fromJson and toJson. Parse safely with ??, type checks and patterns.

Common mistakes

MistakeWrong ideaCorrect ideaWhat goes wrong
Forgetting import 'dart:io';File is built inFile comes from dart:ioUndefined name 'File'.
Writing without appendWrites add to the fileA normal write replaces itOld data is lost.
Reading without checkingThe file is always thereCheck exists or catch the errorPathNotFoundException.
Mixing readAsString() with no awaitIt gives the textIt gives a Future<String>You print Instance of 'Future<String>'.
createSync() on a/b/cParent folders appearPass recursive: trueA PathNotFoundException.
Using dart:io in a web appIt works everywhereBrowsers have no file system access like thisThe import fails.
Forgetting import 'dart:convert';JSON tools are built inImport dart:convertMethod not found: 'jsonEncode'.
Single quotes in JSON textJSON is like DartJSON needs double quotesFormatException.
Reading data.nameDecoded JSON has fieldsIt is a map: data['name']A compile error, or NoSuchMethodError.
Encoding an object with no toJson()Dart knows howWrite toJson()Converting object to an encodable object failed.
Trusting every keyThe server is always rightUse ?? and type checksA crash on missing data.
json['marks'] as List<int>The cast convertsUse (json['marks'] as List).cast<int>()A type error.

Check Yourself

Exercises

What to Remember

  • Files keep data after the program ends.
  • A normal write replaces a file, and FileMode.append adds to it.
  • Expect file errors, and catch FileSystemException.
  • JSON is text data, and jsonEncode and jsonDecode convert it.
  • Model classes with fromJson and toJson keep JSON code clean.

Next: Dart Cheat Sheet. All the syntax from this course on one page.