Open-source project
rails/spring avatar
rails/spring

spring: the Rails preloader that needs reloading on, and a flag named dangerously_allow_disabling_reloading

Rails application preloader

2,813 stars343 forksRubyMIT

At a glance

What is it?
A small Rails gem that keeps your application running in the background so tests, rake tasks and migrations do not pay boot time. Its documentation is unusually honest about the two ways it can go wrong: it requires the Rails reloader to be switched on, and it offers an opt-in that turns that off in exchange for assertions you have to write yourself.
Who is it for?
spring is worth installing in a Rails project whose test suite boots the application repeatedly, on MRI Ruby on a platform that can fork, and it is worth skipping if your suite already runs fast or your team is on JRuby or Windows, where the fork it depends on does not exist. Three things to check.
Can I use it commercially?
Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository last received commits 137 days ago.
What is it written in?
Mainly Ruby, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on November 14, 2026, and from our analysis. They are not legal advice.

From daily snapshots

Star history

  • rails/spring2,813 ★ · -2
Star history
Repository2026-10-042026-11-14
rails/spring2.8k2.8k

-2 Stars in the last 41 days, measured from the daily GitHub snapshot. Compare with other projects

Editorial analysis

Reloading has to be on, and the setting was renamed in Rails 7.1

Spring reloads application code on each run, which sounds like a feature and is also its precondition. The configuration has to have config.enable_reloading set to true in every environment Spring manages, which is typically configured in config/environments/*.rb, and the text is explicit that the test environment in particular must have it true. Rails before 7.1 used a different name for the same switch: cache_classes, which had to be set to false for Spring to work. That inversion is the trap when upgrading. A project coming from an older Rails has cache_classes false, renames it to enable_reloading true, and if the rename is done carelessly the preloader keeps booting your application in production mode inside the server process, which is the opposite of what it needs. Compatibility is stated narrowly alongside this: MRI Ruby 3.1 or newer, Rails 7.1 or newer, Bundler 2.1 or newer.

A flag named dangerously_allow_disabling_reloading, guarded by assertions you write

There is an advanced path, marked risky in its own heading, and it is the most interesting thing in this README. If every reloadable resource is configured to materialize lazily in the forked child rather than in the Spring server process, you can run Spring without Rails' in-process reloaders by putting one line in config/spring.rb:

ruby
Spring.dangerously_allow_disabling_reloading = true

The condition in the previous sentence is doing all the work. Materializing in the child rather than the parent means the server never holds a stale copy of a routes table or a translation catalogue, which is exactly the failure the reloaders exist to prevent. The README then tells you that if you take this option you must make sure you are not preloading a reloadable resource in the server, and gives you the shape of the guard: assertions at the end of the Spring.after_environment_load hook. Its last line is the advice that matters most for anyone reading quickly: when unsure, leave this disabled.

The sample assertions reach into another component's instance variable

The guard rails the README proposes are two lines long and one of them is unusual. The first checks whether the routes reloader reports itself loaded and raises a message saying routes were drawn at boot if so. The second asks the internationalisation backend for its translations through instance_variable_get and raises unless that value is nil, with a message that says the locales were loaded at boot. Reading a gem's private instance variable to assert on it is the kind of check that works until the gem changes its internals, at which point it silently stops asserting anything rather than failing. The message text also carries a typo, saying i18n locales where loaded at boot, which is a small sign of how these two lines were arrived at. Nothing in the documentation says whether these assertions run in the server or the child, which is the detail you would want to know before relying on them.

The binstub snippet only swallows the LoadErrors that mention spring

Installing means adding the gem to the development group and then running bundle install followed by bundle exec spring binstub --all. That generates a bin/spring executable and inserts this into the relevant executables in bin/:

ruby
begin
  load File.expand_path('../spring', __FILE__)
rescue LoadError => e
  raise unless e.message.include?('spring')
end

The rescue clause is the interesting part and it is narrower than it first looks. It re-raises any LoadError whose message does not contain the word spring, so only the failure of loading Spring itself is swallowed; a different LoadError raised by the file you are loading still propagates. On a platform where Spring cannot run, the load fails, the error is ignored, and the lines after the snippet execute normally, which is what makes the same binstubs portable between a developer's Mac and a Windows checkout. Using the gem with a git source is called out as not working and not supported.

Three minor releases in eight weeks for a development tool

The version history moves faster than a preloader usually does. The three newest tags are 4.5.0 on 2026-05-01, 4.6.0 on 2026-05-26 and 4.7.0 on 2026-06-23, which is three minor version bumps in eight weeks, and the last push on the default branch main is dated 2026-06-30, a week after the newest tag. Every one of those releases is a minor bump rather than a patch, so something was added or changed in each. For a gem that only runs on a developer's machine, that cadence is not a support burden the way it would be for a runtime dependency, but it does mean the version your Gemfile.lock pins may be three minors behind whatever is on main, and the behaviour differences between them are described in a CHANGELOG.md rather than in the README. The repository also carries a .devcontainer directory, which suggests the project has a defined development environment of its own.

Rake tasks run in the development environment by default

The command section starts with rake, and its first substantive statement is an environment choice rather than a flag. Rake tasks run in the development environment by default, and you can change that on the fly with the RAILS_ENV environment variable. It is also configurable through a hash on the command class, Spring::Commands::Rake.environment_matchers, which is the hook for a project that wants certain tasks to run somewhere other than development without setting an environment variable for each one. The rest of the command list is cut off in the document, so the set of built-in commands beyond rake is not visible here. What is documented elsewhere in the walkthrough gives a good sample of the surface: a status command that prints the server process and the application process with their ages and modes, a stop command that shuts the server down, and binstub with a remove flag.

Production protection is one bundler line, and it fails open

The deployment section is three sentences and one command, and it is the part of this README a reviewer should not skip. You must not install Spring on your production environment. The mechanism offered is to run a bundler configuration command before the bundle install you use on production machines, excluding two groups:

code
$ bundle config set without 'development test'
$ bundle install

Because the gem is declared in the development group only, that line is what keeps it out. So the entire production protection is a local bundler setting on each machine where gems get installed, and nothing in the gem itself checks whether it is running in production or refuses to start there. If that command is skipped on a deploy host, or if a machine's bundler configuration is regenerated, Spring installs and becomes available. The safer property to rely on is the group declaration, and the honest summary is that the documented defence is procedural.

What the demo timings actually show

The walkthrough generates a scaffold for a post model with a name string and runs one controller test three times, and the numbers in it are worth reading as what they are. The first run reports 0.127245 seconds of test time but 2.165 seconds of real time, because that run also booted the application. The status command then shows two processes, a server and an application in test mode, both three seconds old. The second run of the same command reports 0.610 seconds of real time, and the server process id changes between runs because the application process is forked fresh each time. Touching the application configuration file shows the difference the README describes between the two kinds of edit: code and test files are picked up on the next run without a restart, while configs, initializers and the Gemfile cause the application process to restart automatically while the server keeps its age.

Editorial conclusion

spring is worth installing in a Rails project whose test suite boots the application repeatedly, on MRI Ruby on a platform that can fork, and it is worth skipping if your suite already runs fast or your team is on JRuby or Windows, where the fork it depends on does not exist. Three things to check. The gem only works with reloading enabled, and the setting was renamed in Rails 7.1, so an application on an older version has the opposite value. The escape hatch is named dangerously_allow_disabling_reloading and the guard rails are sample assertions you are expected to adapt, which reach into a translation backend's internals. And production protection is a bundler configuration line rather than anything in the gem, so it fails open if nobody runs that line before installing on a production machine.

Frequently asked questions

What is the spring gem for Rails?

It is an application preloader that keeps your application running in the background, so you do not boot it for every test, rake task or migration. It reloads application code on each run and restarts the application when configs, initializers or gem dependencies change.

What does the spring gem require?

MRI Ruby 3.1 or newer, Rails 7.1 or newer and Bundler 2.1 or newer. Spring makes extensive use of Process.fork, so it provides no speedup on platforms that do not support forking, with Windows and JRuby named as the examples.

How do I install the spring gem in a Rails application?

Add gem "spring", group: :development to the Gemfile, run bundle install, then run bundle exec spring binstub --all. That generates a bin/spring executable and inserts a loader snippet into your existing bin/ executables. Using the gem with a git source is explicitly not supported.

Why does a Rails app need reloading enabled for spring?

Spring reloads application code on each run, so config.enable_reloading must be true in the environments Spring manages, typically set in config/environments/*.rb and in particular for the test environment. In Rails versions before 7.1 the same switch was called cache_classes and had to be false.

Can spring run with Rails reloading turned off?

By opting in with Spring.dangerously_allow_disabling_reloading = true in config/spring.rb, and only if every reloadable resource materializes lazily in the forked child rather than in the server process. The README suggests adding assertions at the end of Spring.after_environment_load and says to leave the option disabled when unsure.

How is spring kept out of a production environment?

The README says you must not install it in production and gives the mechanism as running bundle config set without 'development test' before the bundle install used on production machines, which excludes the group the gem is declared in. Nothing inside the gem itself refuses to run in production.

Official sources

  1. Issues
  2. License: MIT
  3. rails/spring on GitHub
  4. README
  5. Releases
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/rails-spring.svg)](https://hysenlabs.com/projects/rails-spring)

Star history chart for your README

markdown
[![Star history](https://hysenlabs.com/chart/rails-spring.svg)](https://hysenlabs.com/projects/rails-spring)

Was this useful?