Golang os.Stat: File Metadata, File Existence, and Lstat

Deepak Prasad
Tested on RHEL 10.2 with Go 1.27.0
Package go 1.27.0
Applies to Any host with Go 1.20+ installed
Privilege Normal user
Scope os.Stat and os.Lstat for file metadata, existence checks, regular file vs directory, and error handling with errors.Is. Does not cover directory walking, chmod, file watchers, or decoding FileInfo.Sys().
Related guides Golang os package
Read a file in Go
Read and update the same file
Get current directory in Go
Getting started with Go

os.Stat is the usual entry point when you need file metadata or want to know whether a path exists without opening it for reads or writes:

go
info, err := os.Stat("file.txt")

On success, info is an fs.FileInfo describing that filesystem path. os.Stat follows symbolic links, so when the path is a symlink the metadata normally describes the target, not the link node.


Get file metadata with os.Stat

func Stat(name string) (fs.FileInfo, error) stats the named path. FileInfo in the os package is a type alias for fs.FileInfo:

go
type FileInfo = fs.FileInfo

The methods you use most often:

Method Meaning
Name() Base name of the file
Size() Length in bytes for regular files; system-dependent for other file types
Mode() File type and permission bits
Mode().Perm() Permission bits only (rwx for owner, group, other)
ModTime() Last modification time
IsDir() Whether the path is a directory

Sys() returns platform-specific stat data (inode numbers, device IDs). Skip it unless you are writing low-level tooling; the portable fields above cover everyday checks.

This program prints a few FileInfo fields from a file in the current directory:

go
package main

import (
	"fmt"
	"log"
	"os"
	"time"
)

func main() {
	fi, err := os.Stat("file.txt")
	if err != nil {
		log.Fatal(err)
	}
	fmt.Printf("name=%s size=%d mode=%s mod=%s isDir=%v\n",
		fi.Name(), fi.Size(), fi.Mode(), fi.ModTime().UTC().Format(time.RFC3339), fi.IsDir())
}
text
name=file.txt size=6 mode=-rw-r--r-- mod=2026-08-23T08:00:00Z isDir=false

Create file.txt before you run; your size, mode, and timestamp will differ.


Check whether a path exists

The common pattern is to call os.Stat and branch on the error. Use errors.Is with os.ErrNotExist so wrapped errors still match:

go
package main

import (
	"errors"
	"fmt"
	"os"
)

func main() {
	path := "file.txt"
	_, err := os.Stat(path)

	switch {
	case err == nil:
		fmt.Println(path, "exists")
	case errors.Is(err, os.ErrNotExist):
		fmt.Println(path, "does not exist")
	default:
		fmt.Println(path, "stat failed:", err)
	}
}
text
file.txt exists

Permission denied is not the same as “does not exist.” If you map every error to false, you hide real problems—a path you cannot read might still be there. Return or propagate non-ErrNotExist errors instead of treating them as “missing.”

os.IsNotExist still works for older code, but errors.Is(err, os.ErrNotExist) is the idiomatic choice in new programs.


Check whether the path is a regular file or directory

After os.Stat succeeds, use the type bits on FileInfo:

go
if info.IsDir() {
	// directory
}
if info.Mode().IsRegular() {
	// ordinary file
}

!info.IsDir() only tells you the path is not a directory. Named pipes, devices, sockets, and other special files also satisfy that test. When you specifically need a regular file, call info.Mode().IsRegular().

A directory path still “exists” in the existence sense: os.Stat returns nil and info.IsDir() is true.


os.Stat vs (*os.File).Stat

os.Stat(path) works from a path string alone. When you already have an open *os.File, call f.Stat() to get metadata for that open file without doing another path-based os.Stat lookup:

go
f, err := os.Open("file.txt")
if err != nil {
	log.Fatal(err)
}
defer f.Close()

info, err := f.Stat()

That is handy right after os.Open or os.Create when you want size or mode without a second path lookup. For “does this path exist?” or symlink behavior, start with os.Stat on the path.


Path os.Stat os.Lstat
Normal file Describes the file Describes the file
Symlink to existing file Describes the target Describes the symlink
Broken symlink Error (target missing) Describes the symlink

os.Stat follows the final link and reports the target’s metadata. os.Lstat stops at the link itself. On a broken symlink, os.Stat fails with an error (typically os.ErrNotExist after unwrapping) because the target path is gone; os.Lstat still succeeds and shows that the path is a symlink.

Pick Lstat when symlink identity matters—for example, a tool that removes or rewrites the link rather than the target.

After os.Lstat succeeds, test the symlink bit on Mode:

go
info, err := os.Lstat(path)
if err != nil {
	return err
}

if info.Mode()&os.ModeSymlink != 0 {
	fmt.Println("symbolic link")
}

Handle os.Stat errors correctly

os.Stat errors are returned as *os.PathError, which is an alias of fs.PathError. The error string includes the operation, path, and underlying cause.

go
_, err := os.Stat(path)
if errors.Is(err, os.ErrNotExist) {
	// path missing
}
if errors.Is(err, os.ErrPermission) {
	// cannot stat (permission denied)
}

To read the path from the error value:

go
var pathErr *os.PathError
if errors.As(err, &pathErr) {
	fmt.Println("failed on", pathErr.Path)
}

Do not match substrings in err.Error() such as "no such file". Wording varies by platform and wrapped layers; errors.Is and errors.As stay stable.


Summary

os.Stat returns portable fs.FileInfo metadata—name, size, mode, modification time, and whether the path is a directory—without opening the file. For existence checks, treat errors.Is(err, os.ErrNotExist) as “missing” and handle every other error on its own; permission denied does not mean the path is absent.

When you need a regular file, use info.Mode().IsRegular(), not !info.IsDir(). For symlinks, remember that os.Stat follows links and fails on broken targets, while os.Lstat describes the link node itself. For broader file I/O beyond metadata, see the Golang os package overview.


References


Frequently Asked Questions

1. How do I check if a file exists in Go with os.Stat?

Call os.Stat on the path. When err is nil the path exists. When errors.Is(err, os.ErrNotExist) is true the path is missing. Any other error means you cannot conclude the path is absent—permission denied is not the same as not found.

2. What does os.Stat return?

On success os.Stat returns fs.FileInfo with Name, Size, Mode, ModTime, and IsDir. On failure it returns a nil FileInfo and an error, usually a wrapped fs.PathError that includes the path and underlying cause.

3. What is the difference between os.Stat and os.Lstat?

os.Stat follows the final symbolic link and describes the target. os.Lstat does not follow the link and describes the symlink node itself, including when the target is missing.

4. Does os.Stat follow symbolic links?

Yes. os.Stat resolves the final symlink and returns metadata for the target file or directory. Use os.Lstat when you need information about the link itself.

5. What happens when os.Stat is called on a broken symlink?

os.Stat returns an error because the target path does not exist—typically wrapped as os.ErrNotExist. os.Lstat still succeeds and reports symlink metadata for the link node even when the target is missing.

6. How do I check whether the path is a regular file instead of a directory?

After a successful os.Stat, call info.IsDir() for directories and info.Mode().IsRegular() when you specifically need an ordinary file. !info.IsDir() is not enough because pipes, sockets, and devices are also non-directories.
Tuan Nguyen

Data Scientist

Proficient in Golang, Python, Java, MongoDB, Selenium, Spring Boot, Kubernetes, Scrapy, API development, Docker, Data Scraping, PrimeFaces, Linux, Data Structures, and Data Mining. With expertise spanning these technologies, he develops robust solutions and implements efficient data processing and management strategies across various projects and platforms.

  • Go (programming language)
  • Python (programming language)
  • Java (programming language)
  • MongoDB
  • Kubernetes