The letter A styled as Alchemists logo. lchemists
Published March 15, 2023 Updated October 24, 2025
Cover
Ruby Warnings

Warnings are what you see when Ruby detects issues with your code that could cause future problems and/or catastrophic failures if not properly addressed. Sadly, warning messages are disabled by default.

Let’s correct Ruby’s default behavior by getting in the habit of ensuring warnings are always enabled and a permanent part of your workflow. This allows you to be proactive — rather than reactive — in detecting changes early. The reduced stress of not having to deal with production issues when it’s never convenient for you or your customers is another benefit.

Overview

Warnings are issued via Kernel#warn which can be used like this:

warn "A basic warning."
# A basic warning.

warn "Warning A.", "Warning B.", "...etc..."
# Warning A.
# Warning B.
# ...etc...

warn "Level 0", uplevel: 0
# (irb):8: warning: Level 0

warn "Level 1", uplevel: 1
# ~/<redacted>/irb/workspace.rb:113: warning: Level 1

warn "Level 2", uplevel: 2
# ~/<redacted>/irb/workspace.rb:113: warning: Level 2

warn "A deprecation example.", category: :deprecated
# A deprecation example.

warn "A performance example.", category: :performance
# A performance example.

warn "An experimental example.", category: :experimental
# An experimental example.

warn "A block example.", category: :strict_unused_block
# A block example.

💡 The performance category is only available in Ruby 3.3.0 or higher while the strict_unused_block is only available in Ruby 3.4.0 and higher.

This is a basic overview. We’ll look at how all of this works soon. Before moving on, it’s important to emphasize using warnings in your own implementation as a way to inform upstream consumers of changes to your implementation.

Here’s are a few common and practical examples:

# Informs of new and better usage.
warn "Demo#example is deprecated, use Demo#to_s instead.",
     category: :deprecation

# Informs this method has performance concerns and isn't viable under heavy load.
warn "Demo#slow is slow when used in large operations.",
     category: :performance

# Informs this method is unstable and behavior might change in the future.
warn "Demo#future is experimental and not fully fleshed out. Use with caution.",
     category: :experimental

This is most applicable for gems or dependent projects as a communication tool. For everything else, you can use gems, like Cogger, to log information that might not require a warning via the #warn method.

Now that we have basic understanding of how to issue warnings, we can learn how to consume and manage warnings.

Command Line Interface (CLI)

The fastest way to experiment with Ruby warnings is via the CLI. We’ll start with the basics and then work our way to more advanced usage.

Basics

You’ll want to start by looking at the help text and running the following (truncated for brevity):

ruby -h

# -w                     turn warnings on for your script
# -W[level=2|:category]  set warning level; 0=silence, 1=medium, 2=verbose

You can also use ruby --help for more detailed output (highly recommend). For instance, you’ll get detailed documentation on the following warning categories:

Warning categories:

deprecated           Deprecated features.
performance          Performance issues.
experimental         Experimental features.
strict_unused_block  Warning unused block strictly.

For even more verbosity, you can read through the Ruby manual page by running the following (truncated for brevity):

man ruby

# -W[level=2]  Turns on verbose mode at the specified level without printing the version message at the beginning. The level can be;
#
# 0  Verbose mode is "silence". It sets the $VERBOSE to nil.
# 1  Verbose mode is "medium". It sets the $VERBOSE to false.
# 2 (default) Verbose mode is "verbose". It sets the $VERBOSE to true.
# -W2 is the same as -w

We’ll focus on levels and categories shortly but first, now that you know what’s possible, let’s experiment with the -w flag. Consider the following where I use the -e option to execute Ruby code where I define the same method twice (not recommended):

ruby -e "def demo = super; def demo = super"

If you run this from the console, you’ll notice nothing happens but let’s pass the -w flag to enable warnings and see what happens:

ruby -w -e "def demo = super; def demo = super"

# -e:1: warning: method redefined; discarding old demo
# -e:1: warning: previous definition of demo was here

Much better! Now you can see the value of having warnings enabled so you can catch bad code and fix accordingly.

Levels

As you saw earlier, there are three levels (i.e. 0, 1, and 2) and don’t forget that -w is the same as -W2. To illustrate, let’s walk through each level using the bad code from earlier:

# Level 0: Turns warnings off so there will be no output.
ruby -W0 -e "def demo = super; def demo = super"

# Level 1: Turns warnings on but only at medium level. Will not get any output, though.
ruby -W1 -e "def demo = super; def demo = super"

# Level 2: Turns warnings on. This is the default and identical to using the `-w` flag.
ruby -W2 -e "def demo = super; def demo = super"

# -e:1: warning: method redefined; discarding old demo
# -e:1: warning: previous definition of demo was here

Categories

Categories were first introduced in Ruby 2.7.0 and can be listed via IRB:

Warning.categories

# [
#   :deprecated,
#   :experimental,
#   :performance,
#   :strict_unused_block
# ]

Each can be broken down as follows:

  • Deprecated: Warns when deprecated features are used and will soon be removed.

  • Performance: Warns when performance concerns are detected. Only available in Ruby 3.3.0 and higher.

  • Experimental: Warns when experimental features are used but are not yet fully supported.

  • Strict Unused Block: Warns when a block is passed to a method which never uses the block. In other words, the method doesn’t yield to or explicitly call a block. Only available in Ruby 3.4.0 and higher.

All of the above are worth enabling because you’ll have insight into where Ruby language features are headed in the future so you can stay on top of these changes. Using the CLI, here’s how to enable and disable these categories:

Defaults

ruby -e 'puts "Deprecated: #{Warning[:deprecated]}"' \
     -e 'puts "Experimental: #{Warning[:experimental]}"'

# Deprecated: false
# Experimental: true

Fully Enabled

ruby -W:deprecated \
     -W:performance \
     -e 'puts "Deprecated: #{Warning[:deprecated]}"' \
     -e 'puts "Performance: #{Warning[:performance]}"' \
     -e 'puts "Experimental: #{Warning[:experimental]}"'

# Deprecated: true
# Performance: true
# Experimental: true

Fully Disabled

ruby -W:no-deprecated \
     -W:no-performance \
     -W:no-experimental \
     -e 'puts "Deprecated: #{Warning[:deprecated]}"' \
     -e 'puts "Performance: #{Warning[:performance]}"' \
     -e 'puts "Experimental: #{Warning[:experimental]}"'

# Deprecated: false
# Performance: false
# Experimental: false

Unfortunately, most warnings are disabled by default, so please ensure they are globally enabled like via your Dotfiles as explained below.

Environment

While the CLI flags are convenient, all that typing gets tedious. Thankfully, Ruby lets you globally configure these flags via the RUBYOPT environment variable. For example, here’s my Dotfiles configuration:

export RUBYOPT="-W:deprecated -W:performance -W:strict_unused_block --yjit --debug-frozen-string-literal"

The above instructs Ruby to ensure all warnings are enabled along with YJIT and frozen string literal debugging is enabled. The RUBYOPT environment variable supports many flags which you can learn more about when running man ruby. Here’s a short snippet from the manual page:

Note that RUBYOPT can contain only -d, -E, -I, -K, -r, -T, -U, -v, -w, -W, --debug, --disable-FEATURE and --enable-FEATURE.

Exploring all options is outside the scope of what we are discussing here. Instead we’ll focus on how you can configure RUBYOPT to support different warnings. Here’s a few examples:

# All warnings, including categories, are enabled.
export RUBYOPT="-w -W:deprecated -W:performance -W:strict_unused_block"

# All warnings, including categories, are disabled.
export RUBYOPT="-W0 -W:no-deprecated -W:no-performance -W:no-strict_unused_block"

# Only deprecated warnings are enabled.
export RUBYOPT="-W:deprecated"

If you need to remember what you set for this environment variable, you can always print it out:

printf "%s\n" $RUBYOPT

# -W:deprecated
# -W:performance
# --yjit

At this point you might be thinking: "Hey, I thought you said you always enable warnings so why aren’t you setting the -w flag for the RUBYOPT environment variable?" The short answer is I tend to care about warnings that directly effect the projects I’m working on so enable them via my test suite where I have the most agency to take direct action. I’ll show you how to do this using RSpec soon.

Globals

Besides being able to configure warnings from the CLI or RUBYOPT environment variable, you can also manipulate your warning settings, globally, as follows:

# Level 0: Disabled. Equivalent to `-W0`.
$VERBOSE=nil

# Level 1: Enabled (medium). Equivalent to `-W1`.
$VERBOSE=false

# Level 2: Enabled (full). Equivalent to `-W2` or `-w`.
$VERBOSE=true

# Deprecation warnings are enabled.
Warning[:deprecated] = true

# Deprecation warnings are disabled.
Warning[:deprecated] = false

# Performance warnings are enabled.
Warning[:performance] = true

# Performance warnings are disabled.
Warning[:performance] = false

# Experimental warnings are enabled.
Warning[:experimental] = true

# Experimental warnings are disabled.
Warning[:experimental] = false

# Unused block warnings are enabled.
Warning[:strict_unused_block] = true

# Unused block warnings are disabled.
Warning[:strict_unused_block] = false

# Warnings are fully enabled.
ENV["RUBYOPT"] = "-w -W:deprecated -W:performance -W:experimental -W:strict_unused_block"

One nice aspect of being able to change warnings levels within your code — despite global mutation of objects — is you can disable experimental warnings, for example, when working with experimental features like Pattern Matching before they were fully supported or Ractors which are not fully supported yet. This allows you to silence warnings you are aware of but don’t need to be constantly reminded of. Don’t forget to reenable these warnings when done experimenting so you aren’t blind to new information and changes within the Ruby community at large.

Constants

To deprecate a constant, use Module#deprecate_constant. Example:

class Demo
  ONE = "one"
  TWO = "two"

  deprecate_constant :ONE
end

Demo::ONE # warning: constant Demo::ONE is deprecated

The method takes multiple arguments, so you can deprecate multiple constants at once by modifying the code above to deprecate both ONE and TWO. Example: deprecate_constant :ONE, :TWO.

Any constant can be deprecated like a module or class as well.

Methods

To add a warning to a method for any category, it’s nice to detail the class, method, and alternate method to use (if applicable). Here’s an example that deprecates the #speak method while informing you on which method to use instead:

class Speaker
  def initialize kernel: Kernel
    @kernel = kernel
  end

  def call(text) = kernel.puts text

  def speak text
    warn "`#{self.class}##{__method__}` is deprecated, use `#call` instead.", category: :deprecated
    call text
  end

  private

  attr_reader :kernel
end

Speaker.new.speak "An example."

# `Speaker#speak` is deprecated, use `#call` instead.
# An example.

With the above, you know the class method being deprecated along with the preferred method to use instead. All of this can be computed, dynamically, with minimal effort.

IRB

IRB supports a subset of the same flags as ruby. Mainly, these flags: -w and -W[level]. There is no support for warning categories, though.

Here’s a few examples but feel free to experiment further:

# Warnings enabled.
irb -w

# Warnings (Level 0)
irb -W0

Custom Implementations

Should you ever need to implement a custom version (and/or override) Ruby’s default warning behavior, then you’d need to adhere to the Object API by using the following method signature:

def warn(*messages, uplevel: nil, category: nil, **keywords)

You should never have to do this because monkey patching or changing Ruby’s default behavior would have larger consequences. The current implementation allows you to customize via the keyword arguments which should be sufficient.

Managing Dependencies

One aspect of working with Ruby warnings always enabled is that you’ll be exposed to bad actors within the Ruby ecosystem who don’t fix their own warnings in a timely manner. First, you always want to report these issues and, if you have time, fix them yourself by opening up a code review. This can sometimes be time consuming so a workaround that allows you to work without constant noise from your dependencies is to silence all warnings except those within your project. Thankfully, this can be easily addressed by installing the Warning gem.

At a minimum, you’ll want to add the Warning gem to your test environment like so:

group :test do
  gem "warning"
end

Then you can configure your test environment to ignore all warnings from your gem dependencies. For example, this is what I add to my RSpec spec_helper.rb:

require "warning"
Gem.path.each { |path| Warning.ignore(//, path) }

That’s it! The above ensures, by using an empty regular expression, that all gem related warnings based on their path is ignored. Now you can focus on and resolve only the warnings related to your own Ruby project. There is a lot more the Warning gem can do for you so make sure to check out the documentation.

As hinted at earlier, try not to make this a permanent fixture by keeping tabs on upstream dependencies to see if they’ve fixed their warnings so you can remove this workaround.

RSpec

As promised earlier, a nice RSpec feature is that you can configure your entire test suite to have warnings enabled by default. This allows you to avoid globally enabling the -w flag via RUBYOPT. Instead, you can keep warnings relative to projects you’re working on since the actionable impact is so much more valuable. I highly recommend doing this since it’s one of the easiest and best ways to stay on top of upstream changes. Here’s the one liner that you’ll want to add:

RSpec.configure do |config|
  config.warnings = true
end

💡 If you’d like a more in-depth look at how to configure RSpec then I’d suggest you take a look at my RSpec Antipatterns article where I detail the entire configuration.

File Output

Taking what is described in the RSpec section above — especially with warnings enabled — means you can capture all warnings to a file for as a workable TODO list to clean up your code further by running the following:

rspec 2> warnings.txt

The above is possible because all warnings are written to the standard error stream, not the standard output stream.

Conclusion

As you can see, there is a lot you can do with Ruby warnings so I hope you’ve learned a few more tricks to add to your Ruby toolbox and enabled warnings so you can stay on top of the constantly changing Ruby landscape.