Fixing ActionController::RespondToMismatchError in Rails
In the early days of Rails — before version 5.0 — we had a problem with content negotiation. When a controller action used respond_to to declare specific formats but received an unsupported one, Rails typically defaulted to HTML. This worked for web browsers, though it masked mismatches that could confuse API clients or bots crawling our endpoints.
Rails 5.0 changed that by raising ActionController::RespondToMismatchError explicitly.1 Now, when your action receives a request format it doesn’t handle, we see the issue clearly – before it causes silent failures down the line.
Of course, this error serves a purpose. It helps us build more maintainable applications over time. Before we examine triggers and fixes, let’s see how Rails negotiates formats and why we handle them explicitly in long-term projects.
Understanding the Error
Rails negotiates formats through the Accept header primarily, falling back to URL extensions like .json or parameters. Before respond_to executes, Rails matches the request against declared formats in the block. A mismatch raises the error early.
You might wonder what happens when a request asks for something outside the list, say via an Accept: application/xml header. Rails raises ActionController::RespondToMismatchError.2 Rather than defaulting silently – as it did pre-5.0 – this forces deliberate handling, which pays dividends in production over years.
Let’s look at a typical example. Suppose we have this controller action:
# app/controllers/articles_controller.rb
def show
@article = Article.find(params[:id])
respond_to do |format|
format.html
format.json { render json: @article }
end
end
Notice how it supports only HTML and JSON. Let’s trigger a mismatch with curl. First, ensure your server runs and an Article with id 1 exists.
$ curl -H “Accept: application/xml” http://localhost:3000/articles/1
You see:
ActionController::RespondToMismatchError (ActionController::RespondToMismatchError)
The request for format 'xml' could not be fulfilled:
app/controllers/articles_controller.rb:57:in `show'
The stack trace points to the respond_to block. $ curl -H “Accept: application/xml” http://localhost:3000/articles/1
Rails raises:
ActionController::RespondToMismatchError (ActionController::RespondToMismatchError: The request for format ‘xml’ could not be fulfilled):
Before 5.0, this often defaulted to HTML, though that hid real mismatches from API clients. Now we address them upfront - which matters more as our apps grow and attract diverse clients.{[Ruby on Rails Core Team. "Action Controller Overview." *Ruby on Rails Guides*. Accessed March 25, 2026. https://guides.rubyonrails.org/action_controller_overview.html#actioncontroller-respondtomismatcherror.]}
## Common Triggers
Before we examine fixes, let's catalog scenarios where you typically encounter this error. Each highlights aspects of your application's exposure to the wider world.
Users sometimes append formats manually:
$ curl http://localhost:3000/articles/1.pdf
Rails raises the same mismatch error. No action code beyond format check runs.
Frontend code might request the wrong format:
```javascript
fetch('/articles/1', {
headers: { 'Accept': 'application/xml' }
})
Third-party clients using outdated expectations hit deprecated formats like /api/v1/users/123.xml.
Bots and scanners probe with formats like /admin/dashboard.json or /users/1.csv to test for leaks. These common patterns remind us that our endpoints face more than just browsers.
Resolution Approaches
Of course, we have several ways to address these mismatches. We’ll examine four common approaches here, though others exist — like middleware filters, which we won’t cover in depth. For each, note the trade-offs: when it shines, and where it falls short for long-term maintenance.
Add Support for the Format
If the request represents a legitimate need — say, clients expect PDF exports — extend your respond_to block:
def show
@article = Article.find(params[:id])
respond_to do |format|
format.html
format.json { render json: @article }
format.pdf do
pdf = generate_pdf(@article) # Your PDF logic, e.g., via wicked_pdf gem
send_data pdf.render, filename: "#{@article.title}.pdf", type: 'application/pdf'
end
end
end
Gems like wicked_pdf or prawn handle PDFs. For CSV, @article.to_csv works well. This meets legitimate needs precisely. Though, each additional format adds maintenance – we must keep renderers updated and secure over time.3
Rescue at the Controller Level
Catch the error globally for clean production handling:
# app/controllers/application_controller.rb
class ApplicationController < ActionController::Base
rescue_from ActionController::RespondToMismatchError, with: :handle_format_mismatch
private
def handle_format_mismatch(exception)
logger.warn "Format mismatch: #{request.format} for #{request.path} (#{request.user_agent})"
respond_to do |format|
format.html { render file: "#{Rails.root}/public/406.html", status: :not_acceptable, layout: false }
format.any { head :not_acceptable }
end
end
end
This returns HTTP 406 Not Acceptable – semantically correct per RFC 7231.4
Note that your action code — database queries, say — runs before rescue_from catches the error. For expensive operations, this wastes resources. Logs let us track patterns, though high bot traffic can generate noise.
Approach 3: Use Routing Constraints
Restrict acceptable formats at the routing level to prevent invalid requests from reaching your controller:
# config/routes.rb
resources :articles, constraints: { format: /(html|json)/ }
# Or for a single route
get 'articles/:id', to: 'articles#show', constraints: { format: /html|json/ }
Unsupported formats return 404 Not Found before hitting the controller — no wasted computation. However, 404 signals “resource missing” rather than “format unsupported,” which might mislead API clients expecting 406.5
Approach 4: Remove respond_to Entirely
If your action only needs one format, eliminate the respond_to block:
def show
@article = Article.find(params[:id])
# No respond_to block needed for HTML-only actions
end
Rails defaults to the HTML template — no error for browsers. This keeps controllers lean. Non-HTML requests still fail, though, unless handled explicitly.
For API-only endpoints, render directly:
def show
@article = Article.find(params[:id])
render json: @article, status: :ok
end
No respond_to means no mismatch risk, though you lose format negotiation flexibility.
Choosing the Right Approach
Consider your client mix:
- Public web apps with browsers and occasional bots: Rescue with 406 (Approach 2).
- Strict APIs: Routing constraints (Approach 3) for early rejection.
- HTML-only pages: Omit respond_to (Approach 4).
- Verified new formats: Add support (Approach 1).6
Common Pitfalls
Rescue_from runs after action code. Heavy queries waste cycles. Use routes for performance-critical paths to reject early.
Bot traffic fills logs. Filter by user_agent or rate-limit at the edge.
Tests miss formats often. Test mismatches explicitly:
test "rejects XML" do
get article_url(@article, format: :xml)
assert_response :not_acceptable
end
Pre-5.0 Rails defaults silently. Post-7.0 negotiation grows stricter with better MIME parsing.
Debugging Tips
Check Your Routes
Verify which formats your routes accept:
rails routes | grep articles
rails routes -c ArticlesController
Look for format constraints in the output.
Examine Request Headers
In development, check the Rails logs for the actual request format:
Started GET "/articles/1.xml" for 127.0.0.1 at 2026-03-25 10:23:45 -0500
Processing by ArticlesController#show as XML
The as XML portion shows what format Rails detected.
Test Format Handling
Write controller tests for each supported format:
# test/controllers/articles_controller_test.rb
test "show as JSON" do
article = articles(:one)
get article_url(article, format: :json)
assert_response :success
end
test "show as XML returns not acceptable" do
article = articles(:one)
assert_raises ActionController::RespondToMismatchError do
get article_url(article, format: :xml)
end
end
This ensures your format handling behaves as expected.
Monitor Production Errors
Log unsupported formats to identify patterns over time:
rescue_from ActionController::RespondToMismatchError do |exception|
Rails.logger.warn "Unsupported format '#{request.format}' for #{request.path} from #{request.user_agent}"
Sentry.capture_exception(exception, extra: { format: request.format.symbol, path: request.path })
respond_to do |format|
format.html { render file: "#{Rails.root}/public/406.html", status: :not_acceptable, layout: false }
format.any { head :not_acceptable }
end
end
Aggregate these logs quarterly. Do patterns suggest legitimate new formats, or just noise? This balances client evolution against maintenance costs over years.
Long-Term Maintenance
Review logs quarterly for format trends. Legitimate new clients may warrant support. Otherwise, tighten constraints via routes. This balances evolution against maintenance as apps age. Consider alternatives like content-negotiation middleware if needs grow complex.
Conclusion
ActionController::RespondToMismatchError surfaces mismatches early. We choose handling based on context and trade-offs for sustainable apps over years.
Citations
- Ruby on Rails Core Team. “5.0 Release Notes: ActionController::RespondToMismatchError.” . Accessed March 25, 2026. https://guides.rubyonrails.org/5_0_release_notes.html#actioncontroller-respondtomismatcherror. ↩
- Ruby on Rails Core Team. “Action Controller Overview.” . Accessed March 25, 2026. https://guides.rubyonrails.org/action_controller_overview.html#method-i-respond_to. ↩
- Ruby on Rails Core Team. “Action Controller Overview.” . Accessed March 25, 2026. https://guides.rubyonrails.org/action_controller_overview.html#method-i-render. ↩
- Fielding, R. T., et al. “Hypertext Transfer Protocol (HTTP/1.1): Semantics and Content.” . Internet Engineering Task Force (IETF), June 2014. https://datatracker.ietf.org/doc/html/rfc7231#section-6.5.7. ↩
- Ruby on Rails Core Team. “Rails Routing from the Outside In.” . Accessed March 25, 2026. https://guides.rubyonrails.org/routing.html#specifying-format-in-routes. ↩
- Ruby on Rails Core Team. “Action Controller Overview.” . Accessed March 25, 2026. https://guides.rubyonrails.org/action_controller_overview.html#rendering-by-content-type. ↩
Further Reading
Action Controller Overview
Ruby on Rails GuidesRails controller fundamentals and MIME responds.
respond_to API Documentation
Ruby on Rails APIrespond_to and format handling API.
Rails Routing from the Outside In
Ruby on Rails GuidesFormat constraints and route restrictions.
Testing Rails Applications
Ruby on Rails GuidesTesting controller formats and errors.
Active Support Instrumentation
Ruby on Rails GuidesMonitoring request formats and errors.
You May Also Like
Fixing ActiveRecord::ValueTooLong Errors in Rails
Understand why Rails throws ActiveRecord::ValueTooLong exceptions, diagnose the root cause, and implement practical solutions including column resizing, validations, truncation strategies, and error handling for PostgreSQL, MySQL, and SQLite.
Fix Broken CI Builds from Rails 7.1 Deprecation Warnings
How to fix broken CI builds caused by Rails 7.1 deprecation warnings.
Understanding CVE-2007-5380: Session Fixation via URL-Based Sessions in Early Rails
An in-depth look at CVE-2007-5380, a session fixation vulnerability in early Ruby on Rails versions caused by URL-based session identifiers.