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.
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.
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.
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.
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.
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.
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.
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
List all Fortran tutorials.