Modernize Fortran Code with Claude Code (2026)
Legacy Fortran code forms the backbone of many scientific and engineering applications, from climate models to computational physics. However, maintaining these aging codebases presents significant challenges. This guide demonstrates how Claude Code can accelerate Fortran modernization while preserving correctness and improving maintainability.
Modernizing Fortran is not merely a cosmetic exercise. It directly affects your team’s ability to onboard new developers, integrate with modern toolchains, and take advantage of compiler optimizations that have improved dramatically over the past two decades. Claude Code shortens what can otherwise be a months-long manual effort into a systematic, verifiable process.
Setting Up Claude Code for Fortran Development
Claude Code provides solid support for Fortran development through its file editing, bash execution, and code analysis capabilities. Before beginning modernization, ensure your environment is configured properly:
- Install a modern Fortran compiler (GFortran 12+ or Intel oneAPI Fortran)
- Configure your build system (CMake 3.20+, GNU Make, or the Fortran Package Manager)
- Set up testing frameworks (FRUIT, pFUnit, or custom test harnesses)
- Confirm your editor or IDE understands Fortran source extensions (
.f,.f77,.f90,.f95,.f03,.f08)
Claude Code can interact with your compiler and build tools directly through bash commands, enabling smooth integration with existing workflows. A useful first session is simply asking Claude Code to compile your project and report any warnings. older code frequently generates hundreds of deprecation warnings that tell you exactly where modernization effort is needed.
A Quick Environment Check
Verify compiler version
gfortran --version
Check for obvious anti-patterns across a directory tree
grep -rn "COMMON /" src/
grep -rn "GOTO " src/
grep -rn "IMPLICIT " src/ | grep -v "NONE"
Claude Code can run these searches, parse the output, and give you a count by file. turning a vague sense of “a lot of technical debt” into a concrete inventory.
Analyzing Legacy Fortran Codebases
The first step in modernization is understanding what you have. Claude Code excels at code analysis through its file-reading and grep capabilities. Here’s how to use them effectively:
Assessing Code Structure
Use Claude Code to scan your codebase and identify key characteristics:
- Locate all source files (
.f,.f77,.f90,.f95,.f03,.f08) - Identify the predominant coding style (fixed-format vs. free-format)
- Find common anti-patterns (goto statements, common blocks, unformatted I/O)
- Count subroutine and function definitions versus module procedures
Claude Code can review entire directories and provide comprehensive reports on code structure, helping you plan your modernization strategy. A representative analysis output might look like this:
| Category | Count | Priority |
|---|---|---|
Fixed-format files (.f, .f77) |
47 | High |
| COMMON block declarations | 312 | High |
| GOTO statements | 89 | Medium |
| Implicit typing (no IMPLICIT NONE) | 61 files | High |
| Hardcoded array dimensions | 203 | Medium |
| EQUIVALENCE statements | 14 | Low |
This kind of inventory lets you prioritize. Files with COMMON blocks and implicit typing are the highest-risk targets and should be modernized first because the bugs they hide are the most dangerous.
Modernization Patterns and Examples
Converting from Fixed-Format to Free-Format
Legacy Fortran often uses fixed-format source code with columns-based positioning. The constraint that code must start in column 7 and continuation characters appear in column 6 makes modern tooling painful. Free-format offers better readability and removes those column restrictions:
Before (Fixed-Format Fortran 77):
SUBROUTINE MATVEC(A,X,Y,N)
REAL A(100,100),X(100),Y(100)
DO 10 I=1,N
Y(I)=0.0
DO 10 J=1,N
10 Y(I)=Y(I)+A(I,J)*X(J)
RETURN
END
After (Free-Format Fortran 95):
subroutine matvec(A, x, y, n)
implicit none
real, intent(in) :: A(:,:), x(:)
real, intent(out) :: y(:)
integer, intent(in) :: n
integer :: i, j
do i = 1, n
y(i) = 0.0
do j = 1, n
y(i) = y(i) + A(i, j) * x(j)
end do
end do
end subroutine matvec
The differences matter beyond aesthetics. intent(in) and intent(out) attributes let the compiler catch argument misuse at compile time. Assumed-shape arrays (A(:,:)) eliminate the hardcoded 100x100 limit. The labeled DO loop disappearing in favor of separate end do statements makes the control flow explicit and grep-friendly.
Claude Code can systematically convert these patterns across entire files, adding proper implicit none declarations and improving variable naming.
Replacing Common Blocks with Modules
Common blocks are the single largest source of bugs in legacy Fortran. They share raw memory between program units with no type checking. if one unit declares COMMON /PHYSICS/ MASS, VELOCITY as reals and another accidentally declares them in a different order, the compiler says nothing and you get silent data corruption.
Before:
COMMON /PHYSICS/ MASS, VELOCITY, DT
REAL MASS, VELOCITY, DT
After:
module physics_state
implicit none
private
public :: mass, velocity, dt
real :: mass
real :: velocity
real :: dt
contains
subroutine initialize_physics(m, v, timestep)
real, intent(in) :: m, v, timestep
mass = m
velocity = v
dt = timestep
end subroutine initialize_physics
end module physics_state
The module version enforces privacy by default, documents which names are intentionally public, and allows encapsulation of initialization logic. Every source file that previously contained the COMMON declaration now simply has use physics_state. and if any file tries to reference a name that was not declared public, the compiler catches it.
Claude Code can identify all common block usages across multiple files and generate corresponding module definitions, handling the cross-file dependency analysis that makes this refactoring tedious to do by hand.
Introducing Derived Types for Structured Data
One of the most impactful modernization steps is replacing scattered parallel arrays with derived types. Scientific code often has patterns like x_pos(i), y_pos(i), z_pos(i), x_vel(i), y_vel(i), z_vel(i). six separate arrays where a single array of a structured type is clearer and safer:
module particle_system
implicit none
type :: particle
real :: position(3)
real :: velocity(3)
real :: mass
real :: charge
end type particle
type :: particle_ensemble
type(particle), allocatable :: particles(:)
integer :: count
end type particle_ensemble
contains
subroutine update_particle(p, dt)
type(particle), intent(inout) :: p
real, intent(in) :: dt
p%position = p%position + p%velocity * dt
end subroutine update_particle
subroutine update_ensemble(ensemble, dt)
type(particle_ensemble), intent(inout) :: ensemble
real, intent(in) :: dt
integer :: i
do i = 1, ensemble%count
call update_particle(ensemble%particles(i), dt)
end do
end subroutine update_ensemble
end module particle_system
With this approach, you can pass a single particle to any subroutine instead of threading six separate arrays through every call signature. When you later add a temperature field, you add it in one place rather than adding a seventh array across the entire codebase.
Eliminating GOTO with Structured Control Flow
Legacy Fortran with heavy GOTO usage is notoriously difficult to reason about. Claude Code can trace control flow and convert GOTO spaghetti into structured alternatives:
Before:
I = 1
20 IF (I .GT. N) GOTO 30
IF (A(I) .LT. 0.0) GOTO 40
SUM = SUM + A(I)
I = I + 1
GOTO 20
40 PRINT *, 'Negative value at index', I
STOP
30 CONTINUE
After:
do i = 1, n
if (a(i) < 0.0) then
write(*,'(a,i0)') 'Negative value at index ', i
error stop
end if
total = total + a(i)
end do
The structured version is not only readable but also allows compilers to generate better code because the control flow is predictable.
Testing and Validation Strategies
Modernization must preserve correctness. A common failure mode is making syntactic improvements that inadvertently change numerical behavior. Claude Code can help establish solid testing before you touch a single line of production code:
- Create test drivers that verify numerical accuracy against known outputs
- Set up regression tests comparing old and new implementations side-by-side
- Generate tolerance-aware comparisons for floating-point computations
- Build golden reference datasets from the original code that new code must reproduce
module test_utils
implicit none
real, parameter :: DEFAULT_TOLERANCE = 1.0e-6
contains
logical function assert_close(expected, actual, tolerance) result(matches)
real, intent(in) :: expected, actual
real, intent(in), optional :: tolerance
real :: tol
tol = DEFAULT_TOLERANCE
if (present(tolerance)) tol = tolerance
matches = abs(expected - actual) < tol * max(abs(expected), 1.0)
if (.not. matches) then
write(*,'(a,g15.8,a,g15.8)') &
'FAIL: expected ', expected, ' got ', actual
end if
end function assert_close
subroutine run_test(name, passed)
character(len=*), intent(in) :: name
logical, intent(in) :: passed
if (passed) then
write(*,'(a,a)') 'PASS: ', name
else
write(*,'(a,a)') 'FAIL: ', name
end if
end subroutine run_test
end module test_utils
The strategy is to run both the old and new implementation against identical inputs and compare outputs. For numerical code, exact equality is rarely appropriate. use relative tolerances that reflect the expected precision of your algorithm.
Performance Optimization with Claude Code
Modernized Fortran can achieve better performance through several well-understood techniques. Claude Code can identify optimization opportunities by reading your code and suggesting modern alternatives:
Array syntax vs. explicit loops:
! Before: Explicit loop
do i = 1, n
c(i) = a(i) + b(i)
end do
! After: Array syntax (compiler vectorizes automatically)
c = a + b
! Before: Scalar accumulator in a loop
total = 0.0
do i = 1, n
total = total + a(i)
end do
! After: Intrinsic function
total = sum(a(1:n))
Contiguous memory access patterns:
Fortran arrays are column-major (unlike C, which is row-major). A matrix loop that iterates over columns in the outer loop and rows in the inner loop hits cache lines efficiently. Claude Code can identify loops where the iteration order is wrong:
! Cache-unfriendly (C-style thinking applied to Fortran)
do j = 1, m
do i = 1, n
result(i, j) = a(i, j) * scalar ! OK. Fortran is column-major
end do
end do
! Cache-friendly for Fortran matrix operations
do j = 1, m ! outer loop over columns
do i = 1, n ! inner loop over rows. contiguous in memory
b(i, j) = a(i, j) + c(i, j)
end do
end do
| Optimization | Before | After | Typical Speedup |
|---|---|---|---|
| Array syntax | explicit DO loop | c = a + b |
1.2x–3x (vectorization) |
| Intrinsic SUM | manual accumulator | sum(array) |
1.5x–4x |
| Allocatable arrays | fixed-size PARAMETER | allocatable |
Flexibility gain |
| Module procedures | EXTERNAL + interface | module contains |
Compile-time checking |
intent attributes |
none | intent(in/out/inout) |
Optimization hints |
Practical Workflow for Modernization Projects
A realistic modernization project follows this sequence:
- Inventory and Assessment: Use Claude Code to scan and categorize files, producing a prioritized list
- Test harness first: Before changing anything, build a test suite that verifies current behavior
- Compiler warning pass: Add
-Wall -Wextra -fimplicit-noneto your compile flags and fix every warning without changing logic - Incremental module conversion: Move COMMON blocks to modules one block at a time, recompiling and running tests after each
- Format conversion: Convert fixed-format to free-format (tools like
findentcan automate much of this) - Derived types: Introduce structured types for related data, starting with the most-used data structures
- GOTO elimination: Refactor control flow, verifying with tests after each function
- Performance pass: Apply array syntax and intrinsics where the code is a bottleneck
The key discipline is never making two kinds of changes at once. If you convert format and logic simultaneously, test failures become ambiguous. Convert format first, verify tests pass, then change logic.
Claude Code fits into every step: it reads multiple source files to track dependencies, executes build commands to verify compilability, and can generate test skeletons for subroutines based on their signatures.
Conclusion
Claude Code transforms Fortran modernization from a daunting task into a manageable process. Its ability to read, analyze, edit, and validate code makes it an invaluable tool for scientific computing teams. By following the patterns and strategies in this guide, you can systematically modernize legacy Fortran code while maintaining correctness and improving long-term maintainability.
The key is starting small, testing thoroughly, and gradually adopting modern Fortran features. Treat each COMMON block converted and each GOTO eliminated as a measurable win. Over the course of weeks, these incremental improvements compound into a codebase that new team members can read, that static analysis tools can check, and that modern compilers can optimize aggressively.
With Claude Code as your assistant, you have a powerful partner in preserving and improving your scientific computing infrastructure. The institutional knowledge embedded in decades-old Fortran code is worth protecting. modernization ensures it survives into the next generation of hardware and toolchains.
Try it: Paste your error into our Error Diagnostic for an instant fix.
Related Reading
- Claude Code Cold Fusion Modernization Workflow Guide
- Best Way to Use Claude Code for Large File Refactoring
- Claude Code Dependency Injection Refactoring
Built by theluckystrike. More at zovo.one
Find the right skill → Browse 155+ skills in our Skill Finder.