ZetCode

Fortran error handling

last modified November 14, 2026

Fortran has no exceptions. Nothing throws, nothing is caught, and a routine that fails does not unwind the stack: it returns a number that says what happened, and the caller decides what to do with it. The standard library follows the same rule through the iostat and stat arguments, and a program that checks them can report a precise problem instead of producing a wrong answer or dying with a message about an unformatted transfer.

This article covers the two ways to end a program, reads the status and the message of a failed conversion, opens files that do not exist, allocates memory with a status argument, defines its own status codes, passes a failure up through three levels of subroutines, and checks arithmetic before it overflows. The last program also shows what happens when the processor is told to halt on an invalid operation instead of returning a NaN.

Stopping a program

stop ends the program and returns the exit status zero, which means success. stop n ends it with the code n, and error stop ends it with a nonzero code while announcing the failure on the standard error stream; error stop 'text' prints the text as the reason. The difference is intent: stop is a normal end chosen by the programmer, and error stop is an abnormal one that a calling script can detect without reading the output.

h1.f90
program h1
    implicit none
    character(len=16) :: arg
    integer :: status

    call get_command_argument(1, arg, status=status)

    if (status /= 0) then
        print '(a)', 'no instruction given, the program finishes normally'
        stop
    else if (trim(arg) == '2') then
        print '(a)', 'stopping with the code 2'
        stop 2
    else
        print '(a)', 'about to fail'
        error stop 'missing configuration file'
    end if
end program h1

The three runs take different branches of the same if construct. With no argument the program ends through a bare stop, prints nothing extra and exits with zero. With the argument 2 it prints the message and the runtime adds STOP 2, so the shell sees 2. With any other argument it reaches error stop, the runtime prints ERROR STOP followed by the text, and the exit status is 1, the code gfortran uses for every character stop code.

$ gfortran h1.f90 -o h1
$ ./h1; echo "exit: $?"
no instruction given, the program finishes normally
exit: 0
$ ./h1 2; echo "exit: $?"
stopping with the code 2
STOP 2
exit: 2
$ GFORTRAN_ERROR_BACKTRACE=0 ./h1 fatal; echo "exit: $?"
about to fail
ERROR STOP missing configuration file
exit: 1

Errors in input conversion

A read that cannot convert its input does not have to kill the program. The iostat argument receives zero on success and a nonzero code on failure, and iomsg receives a sentence that explains the failure in words. The actual nonzero value is chosen by the compiler and the standard places no meaning on it, so a portable test is ios /= 0; the text in iomsg is meant for a human, not for a program.

h2.f90
program h2
    implicit none
    character(len=80) :: line, msg
    integer :: n, ios

    line = '12 apples'
    read (line, *, iostat=ios, iomsg=msg) n
    print '(a, i0, a, i0)', '12 apples: value ', n, ', status ', ios

    line = 'abc'
    read (line, *, iostat=ios, iomsg=msg) n
    print '(a, i0)', 'abc: status ', ios
    print '(a, a)', 'message: ', trim(msg)

    line = '  42  '
    read (line, *, iostat=ios, iomsg=msg) n
    print '(a, i0, a, i0)', 'padded 42: value ', n, ', status ', ios

    line = '3.7'
    read (line, *, iostat=ios, iomsg=msg) n
    print '(a, i0)', '3.7 as an integer: status ', ios
    print '(a, a)', 'message: ', trim(msg)
end program h2

The first line reads the number at the start of 12 apples and ignores the rest, which is how list-directed input works. The word abc fails with status 5010 and the message names the item and the kind of input. Leading spaces do not matter, but 3.7 is not an integer and fails with the same status and the same message. On another compiler the number would differ, and a program that compares it with a literal would break; comparing it with zero does not.

$ gfortran h2.f90 -o h2
$ ./h2
12 apples: value 12, status 0
abc: status 5010
message: Bad integer for item 1 in list input
padded 42: value 42, status 0
3.7 as an integer: status 5010
message: Bad integer for item 1 in list input

Errors in file operations

open, read, write and close all accept iostat, and open also accepts iomsg. A failed open leaves the unit unconnected, so the program must not read from it; the status is the only reliable sign. A read past the end of a file returns a negative status, which is the normal way to end a loop over records, while a positive status means a real error.

h3.f90
program h3
    implicit none
    character(len=256) :: msg, line
    integer :: unit, ios

    open (newunit=unit, file='absent.txt', status='old', action='read', &
        iostat=ios, iomsg=msg)
    print '(a, i0)', 'opening a missing file: status ', ios
    print '(a, a)', 'message: ', trim(msg)

    open (newunit=unit, file='notes.txt', status='replace', action='write', &
        iostat=ios, iomsg=msg)
    print '(a, i0)', 'creating a file: status ', ios
    write (unit, '(a)') 'first note'
    write (unit, '(a)') 'second note'
    close (unit, iostat=ios)
    print '(a, i0)', 'closing it: status ', ios

    open (newunit=unit, file='notes.txt', status='old', action='read', &
        iostat=ios, iomsg=msg)
    read (unit, '(a)', iostat=ios) line
    print '(a, a)', 'first line:  ', trim(line)
    read (unit, '(a)', iostat=ios) line
    print '(a, a)', 'second line: ', trim(line)
    read (unit, '(a)', iostat=ios) line
    print '(a, i0, a, l1)', 'past the end: status ', ios, ', negative: ', &
        ios < 0
    close (unit)
end program h3

The first open fails with status 2 and a message that repeats the file name and the operating system's reason. The second open with status='replace' creates the file, writes two records and closes it with status zero. Reading the records back works, and the third read returns -1, the conventional end-of-file code, which the last line recognises with a simple comparison against zero. Nothing in this program stops on a failure; every status is printed and the program decides.

$ gfortran h3.f90 -o h3
$ ./h3
opening a missing file: status 2
message: Cannot open file 'absent.txt': No such file or directory
creating a file: status 0
closing it: status 0
first line:  first note
second line: second note
past the end: status -1, negative: T

Allocation failures

allocate accepts stat and errmsg for the same reason: an allocation that cannot be satisfied should not abort a program that has other work to do, such as writing a checkpoint or falling back to a smaller size. The rules of errmsg are worth knowing: it is defined only when the status is nonzero, so printing it after a success shows whatever the variable happened to contain.

h4.f90
program h4
    implicit none
    integer, allocatable :: data(:)
    integer :: ios, i
    character(len=200) :: msg

    allocate (data(100), stat=ios, errmsg=msg)
    if (ios /= 0) then
        print '(a, i0, a, a)', 'allocation failed: ', ios, ' ', trim(msg)
        stop 1
    end if

    data = [(3 * i, i = 1, 100)]
    print '(a, i0, a, i0)', 'allocated ', size(data), ' elements, last ', &
        data(100)

    deallocate (data, stat=ios)
    print '(a, i0)', 'the first deallocation returns ', ios

    deallocate (data, stat=ios, errmsg=msg)
    print '(a, i0)', 'the second one returns ', ios
    print '(a, a)', 'and says: ', trim(msg)
end program h4

The first allocation succeeds, the array is filled with multiples of three, and the first deallocate returns zero. The second deallocation of the same array returns 1 and the message "Attempt to deallocate an unallocated object", which is exactly the kind of mistake that a bare deallocate would turn into a fatal runtime error. Note that the error message is printed only in the second call, because that is the only call in which the status is nonzero.

$ gfortran h4.f90 -o h4
$ ./h4
allocated 100 elements, last 300
the first deallocation returns 0
the second one returns 1
and says: Attempt to deallocate an unallocated object

Status codes of your own

A routine of your own should use the same convention: return an integer, zero for success and a small positive number for each distinct failure, and keep the wording in one place. The function below validates a text as an integer in a range and returns one of four codes, and a companion function maps a code to a message. Keeping the codes apart from the messages means the messages can be translated or reworded without touching the tests.

h5.f90
module checking
    implicit none
    private
    public :: read_integer, message

    character(len=*), parameter :: texts(0:3) = [character(len=24) :: &
        'accepted', 'empty text', 'not an integer', 'outside 0 to 100']
contains
    integer function read_integer(text, value) result(code)
        character(len=*), intent(in) :: text
        integer, intent(out) :: value
        integer :: ios

        value = 0
        if (len_trim(text) == 0) then
            code = 1
            return
        end if
        read (text, *, iostat=ios) value
        if (ios /= 0) then
            value = 0
            code = 2
            return
        end if
        if (value < 0 .or. value > 100) then
            code = 3
            return
        end if
        code = 0
    end function read_integer

    function message(code) result(text)
        integer, intent(in) :: code
        character(len=24) :: text

        text = texts(code)
    end function message
end module checking

program h5
    use checking
    implicit none
    character(len=16), parameter :: inputs(6) = [character(len=16) :: &
        '42', '   ', '-5', 'abc', '150', '  7 ']
    integer :: i, value, code
    character(len=18) :: shown

    print '(a)', 'input               code  value  meaning'
    do i = 1, size(inputs)
        code = read_integer(inputs(i), value)
        shown = '['//trim(inputs(i))//']'
        print '(a18, i4, i7, 2x, a)', shown, code, value, trim(message(code))
    end do

    code = read_integer('42', value)
    if (code /= 0) then
        print '(a)', 'the value was rejected'
        stop 1
    end if
    print '(a, i0)', 'the accepted value doubled: ', 2 * value
end program h5

The table shows the four outcomes: an accepted value, an empty string, a well-formed number that is out of range, and a word that is not a number at all. The value returned alongside the code is the parsed number when there is one and zero otherwise, so a caller that ignores the code cannot accidentally use a stale value. The final lines show the intended use: test the code once, then work with the value.

$ gfortran h5.f90 -o h5
$ ./h5
input               code  value  meaning
[42]                 0     42  accepted
[]                   1      0  empty text
[-5]                 3     -5  outside 0 to 100
[abc]                2      0  not an integer
[150]                3    150  outside 0 to 100
[  7]                0      7  accepted
the accepted value doubled: 84

Passing the failure up the chain

Real programs have layers, and a failure deep inside has to reach the top without losing its identity. The pattern is to give every routine the same trailing stat argument, to call the next level and check the result immediately, and to add context at each level while leaving the code unchanged. The top level is the only place that decides to stop the program.

h6.f90
module chain
    implicit none
contains
    subroutine parse(text, value, stat)
        character(len=*), intent(in) :: text
        integer, intent(out) :: value
        integer, intent(out) :: stat
        integer :: ios

        value = 0
        read (text, *, iostat=ios) value
        if (ios /= 0) then
            stat = 10
        else if (value <= 0) then
            stat = 11
        else
            stat = 0
        end if
    end subroutine parse

    subroutine load(text, value, stat)
        character(len=*), intent(in) :: text
        integer, intent(out) :: value
        integer, intent(out) :: stat

        call parse(text, value, stat)
        if (stat /= 0) print '(a, i0)', 'load: the parser returned ', stat
    end subroutine load

    subroutine run(text, value, stat)
        character(len=*), intent(in) :: text
        integer, intent(out) :: value
        integer, intent(out) :: stat

        call load(text, value, stat)
        if (stat /= 0) print '(a, i0)', 'run: giving up with ', stat
    end subroutine run
end module chain

program h6
    use chain
    implicit none
    character(len=16) :: arg
    integer :: value, stat

    call get_command_argument(1, arg)
    call run(trim(arg), value, stat)
    if (stat /= 0) then
        print '(a)', 'the program exits with status 1'
        stop 1
    end if
    print '(a, i0)', 'accepted value: ', value
end program h6

A good value passes all three levels and is printed at the top. The word abc fails in the parser with code 10, which load and run report without changing, and the program exits with status 1. A negative number fails the range test with code 11, and a missing argument is an empty string, which fails like a word. Two codes travel through three routines, and the exit status stays 1 because the shell only needs to know that something went wrong.

$ gfortran h6.f90 -o h6
$ ./h6 25
accepted value: 25
$ ./h6 abc; echo "exit: $?"
load: the parser returned 10
run: giving up with 10
the program exits with status 1
STOP 1
exit: 1
$ ./h6 -3
load: the parser returned 11
run: giving up with 11
the program exits with status 1
STOP 1
$ ./h6
load: the parser returned 10
run: giving up with 10
the program exits with status 1
STOP 1

Checking arithmetic before it fails

Integer overflow is silent: the result wraps and the program continues with a wrong number, which is the hardest kind of error to find afterwards. The cure is to test before multiplying, comparing the larger operand with huge(x) / abs(other); the division cannot overflow because the divisor is at most huge(x). Real division by zero is a different case, because IEEE arithmetic defines an answer, and a program must decide whether it wants that answer or a rejection.

h7.f90
program h7
    use ieee_arithmetic, only: ieee_support_halting, ieee_set_halting_mode, &
        ieee_invalid
    use iso_fortran_env, only: int64
    implicit none
    integer(int64) :: a, b, product
    real :: x, y
    logical :: ok

    print '(a, l1)', 'halting is supported: ', ieee_support_halting(ieee_invalid)

    a = 3000000000_int64
    b = 3_int64
    call checked_mul(a, b, product, ok)
    print '(a, l1, a, i0)', 'three billion times three: ok ', ok, &
        ', value ', product

    call checked_mul(huge(a), 2_int64, product, ok)
    print '(a, l1)', 'huge(int64) times two:     ok ', ok
    a = huge(a)
    b = 2_int64
    print '(a, i0)', 'the plain product wraps to ', a * b

    ok = ratio(1.0, 0.0, x)
    print '(a, l1, a, f0.3)', 'zero divisor rejected: ', .not. ok, &
        ', the value stays ', x
    ok = ratio(1.0, 4.0, x)
    print '(a, l1, a, f0.3)', 'an ordinary division:  ', ok, ', the value ', x

    call ieee_set_halting_mode(ieee_invalid, .true.)
    print '(a)', 'halting enabled, now dividing zero by zero'
    y = 0.0
    x = y / y
    print '(a, g0)', 'this line is never reached: ', x
contains
    subroutine checked_mul(left, right, result, ok)
        integer(int64), intent(in) :: left, right
        integer(int64), intent(out) :: result
        logical, intent(out) :: ok

        ok = .false.
        result = 0
        if (right == 0) then
            ok = .true.
            return
        end if
        if (abs(left) > huge(left) / abs(right)) return
        result = left * right
        ok = .true.
    end subroutine checked_mul

    logical function ratio(top, bottom, value) result(ok)
        real, intent(in) :: top, bottom
        real, intent(out) :: value

        ok = .false.
        value = 0.0
        if (abs(bottom) > 0.0) then
            value = top / bottom
            ok = .true.
        end if
    end function ratio
end program h7

The checked multiplication accepts three billion times three, rejects huge(int64) times two, and the plain product on the next line shows what would have happened without the test: it wraps to -2, the correct answer minus two to the 64th. The ratio function refuses a zero divisor and leaves the value at zero, and reports success for an ordinary division. Then the program enables the halt on invalid operations, divides zero by zero, and dies with a floating-point exception and exit status 136, which is the shell reporting signal 8. The compile line adds -fno-backtrace so that the terminal shows that message instead of a page of addresses.

$ gfortran -fno-backtrace h7.f90 -o h7
$ ./h7; echo "exit: $?"
halting is supported: T
three billion times three: ok T, value 9000000000
huge(int64) times two:     ok F
the plain product wraps to -2
zero divisor rejected: T, the value stays .000
an ordinary division:  T, the value .250
halting enabled, now dividing zero by zero
bash: line 1: 16266 Floating point exception(core dumped) ./h7
exit: 136

Next steps

The files article and the reading input article use iostat for their loops, and the internal files article explains the conversions that fail in the second program. The allocatable arrays article covers the allocation and deallocation with status arguments, the procedures article explains the intent(out) arguments that carry the codes, the command-line arguments article shows the usage messages that accompany a nonzero exit status, and the floating-point article describes the NaN, the infinity and the exception flags that lie behind the last program.

Source

Fortran 2018 standard (J3/18-007r1)

GNU Fortran manual: STOP and ERROR STOP

GNU Fortran manual: IEEE modules

In this article we have ended a program with stop, stop n and error stop and read the three exit statuses, converted text to numbers with an iostat and an iomsg, opened files that may not exist, allocated and deallocated with stat and errmsg, written a validator with four status codes and a message table, passed a failure up three levels of subroutines, checked for overflow and a zero divisor before computing, and seen the processor halt on an invalid operation. The previous article, Fortran environment and processes, collects the exit statuses of the programs it starts.

Author

My name is Jan Bodnar, and I am a passionate programmer with extensive programming experience. I have been writing programming articles since 2007. To date, I have authored over 1,400 articles and 8 e-books. I possess more than ten years of experience in teaching programming.

List all Fortran tutorials.