Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 20 additions & 2 deletions .github/workflows/maven.yml
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,16 @@ jobs:

strategy:
matrix:
jruby_version: [ '9.3.15.0', '9.4.14.0' ]
jruby_version: [ '9.3.15.0', '9.4.14.0', '10.0.2.0' ]
java_version: [ '8', '11', '17', '21', '25' ]
rack_version: [ '~> 2.2.0' ]
exclude:
- jruby_version: '10.0.2.0'
java_version: '8' # JRuby 10 requires Java 21
- jruby_version: '10.0.2.0'
java_version: '11' # JRuby 10 requires Java 21
- jruby_version: '10.0.2.0'
java_version: '17' # JRuby 10 requires Java 21
fail-fast: false

steps:
Expand Down Expand Up @@ -59,16 +66,27 @@ jobs:
'rails70_rack22',
'rails71_rack22',
'rails72_rack22',
'rails80_rack22',
]
jruby_version: [ '9.3.15.0', '9.4.14.0' ]
jruby_version: [ '9.3.15.0', '9.4.14.0', '10.0.2.0' ]
java_version: [ '8', '11', '17', '21', '25' ]
exclude:
- jruby_version: '10.0.2.0'
java_version: '8' # JRuby 10 requires Java 21
- jruby_version: '10.0.2.0'
java_version: '11' # JRuby 10 requires Java 21
- jruby_version: '10.0.2.0'
java_version: '17' # JRuby 10 requires Java 21
- appraisal: 'rails70_rack22' # Requires Ruby 2.7 compatibility, which JRuby 9.3 does not support
jruby_version: '9.3.15.0'
- appraisal: 'rails71_rack22' # Requires Ruby 2.7 compatibility, which JRuby 9.3 does not support
jruby_version: '9.3.15.0'
- appraisal: 'rails72_rack22' # Requires Ruby 3.1 compatibility, which JRuby 9.3 does not support
jruby_version: '9.3.15.0'
- appraisal: 'rails80_rack22' # Requires Ruby 3.4 compatibility, which JRuby 9.3 does not support
jruby_version: '9.3.15.0'
- appraisal: 'rails80_rack22' # Requires Ruby 3.4 compatibility, which JRuby 9.4 does not support
jruby_version: '9.4.14.0'
fail-fast: false

env:
Expand Down
3 changes: 2 additions & 1 deletion Appraisals
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@ version_spec = ->(prefix, desc) { "~> #{desc.split(prefix).last.insert(1, ".")}.
"rails61" => %w[rack22],
"rails70" => %w[rack22],
"rails71" => %w[rack22],
"rails72" => %w[rack22]
"rails72" => %w[rack22],
"rails80" => %w[rack22]
}.each do |rails_desc, rack_descs|
rack_descs.each do |rack_desc|

Expand Down
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
- Ensure rack boot process leaves ENV['GEM_PATH'] and Gem.paths in a consistent state
- Remove undocumented and unsafe jruby.rack.env.gem_path = false option (unusable on Bundler 1.6+)
- Fix unintended Rubygems initialization too early in boot process with JRuby 9.4+
- Adds basic compatibility with JRuby 10.0

## 1.2.5

Expand Down
4 changes: 1 addition & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ For more information on Rack, visit http://rack.github.io/.

| JRuby-Rack Series | Status | Rack | JRuby | Java | Rails | Target Servlet API | Notes |
|------------------------------------------------------------|------------|-----------|------------|------|-----------|---------------------|--------------------------------------------|
| [1.2](https://github.com/jruby/jruby-rack/tree/1.2-stable) | Maintained | 2.2 | 9.3 → 9.4 | 8+ | 5.0 → 7.2 | 3.0 (Java EE 6) | Servlet 3.1 → 4.0 OK with some containers. |
| [1.2](https://github.com/jruby/jruby-rack/tree/1.2-stable) | Maintained | 2.2 | 9.3 → 10.0 | 8+ | 5.0 → 8.0 | 3.0 (Java EE 6) | Servlet 3.1 → 4.0 OK with some containers. |
| [1.1](https://github.com/jruby/jruby-rack/tree/1.1-stable) | EOL | 1.x → 2.2 | 1.6 → 9.4 | 6+ | 2.1 → 5.2 | 2.5 (Java EE 5) | Servlet 3.0 → 4.0 OK with some containers. |
| 1.0 | EOL | 0.9 → 1.x | 1.1 → 1.9 | 5+ | 2.1 → 3.x | 2.5 (Java EE 5) | |

Expand Down Expand Up @@ -209,8 +209,6 @@ as context init parameters in web.xml or as VM-wide system properties.
sub-path of the main servlet context root.
- `gem.path`: Relative path to the bundled gem repository. Defaults to
`/WEB-INF/gems`.
- `jruby.compat.version`: Set to "1.8" or "1.9" to make JRuby run a specific
version of Ruby (same as the --1.8 / --1.9 command line flags).
- `jruby.min.runtimes`: For non-threadsafe Rails applications using a runtime
pool, specify an integer minimum number of runtimes to hold in the pool.
- `jruby.max.runtimes`: For non-threadsafe Rails applications, an integer
Expand Down
15 changes: 15 additions & 0 deletions gemfiles/rails80_rack22.gemfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
# This file was generated by Appraisal

source "https://rubygems.org"

gem "rake", "~> 13.3", group: :test, require: nil
gem "rspec", group: :test

group :default do
gem "rack", "~> 2.2.0"
gem "rails", "~> 8.0.0"
end

group :development do
gem "appraisal", require: nil
end
62 changes: 62 additions & 0 deletions src/main/java/org/jruby/CompatVersion.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
package org.jruby;

/**
* Removed from JRuby Core as of 10.0; but deprecated and unused for a long time.
* <p/>
* Added back to jruby-rack 1.2.x to allow JRuby 10 support without an API changed, but
* will be removed in jruby-rack 1.3.0
* <p/>
* @link https://github.com/jruby/jruby/blob/jruby-9.3/core/src/main/java/org/jruby/CompatVersion.java
* @deprecated since JRuby 9.2 with no replacement; for removal with jruby-rack 1.3.0
*/
@Deprecated
public enum CompatVersion {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

not sure this is a good idea, with proper modularization shouldn't even work (as the package is "owned" by a different .jar).

would personally just leave 1.2.x as is and not try to hack around to get it to work with latest JRuby...

Copy link
Contributor Author

@chadlwilson chadlwilson Oct 12, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, this did occur to me. However is jruby-rack even possible to use within a modularised environment? It has no metadata. So is this an an academic concern or still real?

I'm yet to actually see such modularisation used in the wild in real applications, so I may be naive here.

Copy link
Contributor Author

@chadlwilson chadlwilson Oct 12, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you know an example of a Java web server that works in JPMS module mode? To my knowledge they all still use classpath mode (or OSGi which is a whole other can of worms jruby-rack doesn't support)

Copy link
Member

@kares kares Oct 13, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I honestly do not know, just smells like a bad idea.
There has been embedded use-cases with different class-path layouts than a typical .war.

Understand where this is coming from given the enum is self contained.
We'll still have the org.jruby.CompatVersion (with <10) class around twice and can not guarantee which one will get loaded, shouldn't matter. 🤷

If you get someone else e.g. @headius to bless this approach. 🙏

Personally would rather not go down this path, esp. given 1.3.0 without hacks is good to go but if there's smarter folks saying will do just fine... then 👍

Copy link
Contributor Author

@chadlwilson chadlwilson Oct 13, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, almost certainly a bad idea in the general case.

My thinking was just to make life easier for folks to upgrade and avoid issues like jruby/warbler#573 where someone uses JRuby 10 with Warbler and it sometimes works enough to confuse folks, except when there is a start-up error and that error is swallowed by this CompatVersion problem. Warbler doesn't define a maximum jruby version, but does define a < 1.3 jruby-rack version so there is a bit more friction there.

I was thinking that might allow us to keep the delta for 1.3 even smaller for folks - but I hear you..

Alternatively to this what we could possibly do on 1.2.x is disable the jruby-rack config capture (on init failure) for JRuby 10. The actual failure/issue right now only seems to come from the instance_methods call (JRuby MethodGatherer invocation) here:

module JRubyRackConfig
def capture
super
servlet_context = JRuby::Rack.context
methods = servlet_context.config.class.instance_methods(false) +
org.jruby.rack.DefaultRackConfig.instance_methods(false)
methods = methods.uniq.reject do |m|
m =~ /^(get|is|set)/ || m =~ /[A-Z]|create|quiet|([!?=]$)/
end
output.puts("\n--- JRuby-Rack Config",
*(methods.sort.map do |m|
"#{m} = #{servlet_context.config.send(m)}" rescue "#{m} = <error: #{$?}>"
end))
end
end

So if we removed that, and if the user does not retrieve the CompatVersion in custom config, I don't think jruby/jruby-rack will cause the CompatVersion class to be loaded, despite the imports below because the getCompatVersion() method is never invoked.

import org.jruby.CompatVersion;

Would that be better to you - to just lose the rack config dump capability on JRuby 10 for 1.2.x? (unless you can think of an even smarter way to stop the .instance_methods(false) call from inspecting everything. :-)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am reasonably confident that JRuby 10 works fine without the config dump being needed because the Warbler integration tests validate startup and a basic call to a Rails API work fine and are passing in master.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My thinking was just to make life easier for folks to upgrade and avoid issues like jruby/warbler#573 where someone uses JRuby 10 with Warbler and it sometimes works enough to confuse folks, except when there is a start-up error and that error is swallowed by this CompatVersion problem. Warbler doesn't define a maximum jruby version, but does define a < 1.3 jruby-rack version so there is a bit more friction there.

That still sounds like a work-around for smt that could be fixed pushing patch jruby-rack/warbler versions.
On the jruby-rack (1.2.x) gem side by requiring to have the required_ruby_version < 3.2 then at runtime Bundler/RubyGems should validate.
potentially same could be done on the Warbler side, althoughlikely less reasonable - what kind of version you're using to pack and at actual runtime might be different (as you mentioned on the Warbler ticket that jruby-jars requirement should get an upper limit).

Alternatively to this what we could possibly do on 1.2.x is disable the jruby-rack config capture (on init failure) for JRuby 10.

Not sure I fully follow but sounds like things are getting complicated... 😉

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

True, but we can't change the requirements for already pushed versions, so in practice that still that kinda leaves people ending up accidentally using incompatible combinations (ignoring yanking gems).

But yeah, ok. I'll leave this and refocus on Rack 3 and 1.3.

Would you be able to help push a release of this branch as it currently stands in that case?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.


@Deprecated RUBY1_8,
@Deprecated RUBY1_9,
@Deprecated RUBY2_0,
@Deprecated RUBY2_1,
@Deprecated BOTH;

@Deprecated
public boolean is1_9() {
return this == RUBY1_9 || this == RUBY2_0 || this == RUBY2_1;
}

@Deprecated
public boolean is2_0() {
return this == RUBY2_0 || this == RUBY2_1;
}

@Deprecated
public static CompatVersion getVersionFromString(String compatString) {
if (compatString.equalsIgnoreCase("RUBY1_8")) {
return CompatVersion.RUBY1_8;
} else if (compatString.equalsIgnoreCase("1.8")) {
return CompatVersion.RUBY1_8;
} else if (compatString.equalsIgnoreCase("RUBY1_9")) {
return CompatVersion.RUBY1_9;
} else if (compatString.equalsIgnoreCase("1.9")) {
return CompatVersion.RUBY1_9;
} else if (compatString.equalsIgnoreCase("RUBY2_0")) {
return CompatVersion.RUBY2_0;
} else if (compatString.equalsIgnoreCase("2.0")) {
return CompatVersion.RUBY2_0;
} else if (compatString.equalsIgnoreCase("RUBY2_1")) {
return CompatVersion.RUBY2_1;
} else if (compatString.equalsIgnoreCase("2.1")) {
return CompatVersion.RUBY2_1;
} else {
return null;
}
}

@Deprecated
public static boolean shouldBindMethod(CompatVersion runtimeVersion, CompatVersion methodVersion) {
if (runtimeVersion == RUBY1_8) return methodVersion == RUBY1_8 || methodVersion == BOTH;
if (runtimeVersion == RUBY1_9) return methodVersion == RUBY1_9 || methodVersion == BOTH;
if (runtimeVersion == RUBY2_0) return methodVersion == RUBY1_9 || methodVersion == RUBY2_0 || methodVersion == BOTH;
if (runtimeVersion == RUBY2_1) return methodVersion == RUBY1_9 || methodVersion == RUBY2_0 || methodVersion == RUBY2_1 || methodVersion == BOTH;
return false;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -302,10 +302,6 @@ protected RubyInstanceConfig initRuntimeConfig(final RubyInstanceConfig config)
// Process arguments, namely any that might be in RUBYOPT
config.processArguments(rackConfig.getRuntimeArguments());

if ( rackConfig.getCompatVersion() != null ) {
config.setCompatVersion(rackConfig.getCompatVersion());
}

try { // try to set jruby home to jar file path
final URL resource = Ruby.class.getResource("/META-INF/jruby.home");
if ( resource != null && "jar".equals( resource.getProtocol() ) ) {
Expand Down
19 changes: 0 additions & 19 deletions src/main/java/org/jruby/rack/DefaultRackConfig.java
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,6 @@
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.jruby.CompatVersion;
import org.jruby.rack.logging.OutputStreamLogger;
Expand Down Expand Up @@ -81,23 +79,6 @@ public void setQuiet(boolean quiet) {

@Override
public CompatVersion getCompatVersion() {
final String version = getProperty("jruby.compat.version");
if ( version != null ) {
// we handle 1.8, RUBY1_9, --2.0 1_9 2.1.0.dev etc :
final Pattern pattern = Pattern.compile("([123])[._]([8901234567])");
final Matcher matcher = pattern.matcher(version);
if ( matcher.find() ) {
final String name = "RUBY" +
matcher.group(1) + '_' + matcher.group(2);
try {
return Enum.valueOf(CompatVersion.class, name);
}
catch (IllegalArgumentException e) {
getLogger().log(WARN,
"could not resolve compat version from '"+ version +"' will use default", e);
}
}
}
return null;
}

Expand Down
3 changes: 3 additions & 0 deletions src/main/java/org/jruby/rack/RackConfig.java
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,10 @@ public interface RackConfig {
/**
* Return the Ruby version that JRuby should run.
* @return <code>RUBY_VERSION</code> (e.g. 1.8, 1.9)
*
* @deprecated Since jruby-rack 1.2 (and JRuby 9.2), for removal in 1.3.0
*/
@Deprecated
CompatVersion getCompatVersion();

/**
Expand Down
10 changes: 7 additions & 3 deletions src/main/java/org/jruby/rack/embed/Config.java
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,6 @@ public class Config implements RackConfig {

private RackLogger logger;
private Map<String, String> rubyENV;
private CompatVersion compatVersion;

public Config() {
delegate = new DefaultRackConfig() {
Expand All @@ -66,7 +65,6 @@ public void doInitialize(final Ruby runtime) {
setOut( runtime.getOut() );
setErr( runtime.getErr() );
rubyENV = runtime.getENV();
compatVersion = runtime.getInstanceConfig().getCompatVersion();
}


Expand Down Expand Up @@ -106,9 +104,15 @@ public final Number getNumberProperty(String key, Number defaultValue) {
return delegate.getNumberProperty(key, defaultValue);
}

/**
* @return Always CompatVersion.RUBY2_1, consistent with JRuby 9.3+
*
* @deprecated Since jruby-rack 1.2 (and JRuby 9.2), for removal in 1.3.0
*/
@Deprecated
@Override
public CompatVersion getCompatVersion() {
return compatVersion;
return CompatVersion.RUBY2_1;
}

@Override
Expand Down
2 changes: 1 addition & 1 deletion src/spec/ruby/jruby/rack/error_app_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -165,7 +165,7 @@ def in_tmpdir_with_files(files = {})
end
yield path
ensure
FileUtils.rm_rf(path) if path && File.exists?(path)
FileUtils.rm_rf(path) if path && File.exist?(path)
end

end
Expand Down
12 changes: 4 additions & 8 deletions src/spec/ruby/jruby/rack/integration_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,6 @@
before do
@servlet_context = org.jruby.rack.mock.RackLoggingMockServletContext.new "file://#{STUB_DIR}/rack"
@servlet_context.logger = raise_logger
# make sure we always boot runtimes in the same mode as specs :
set_compat_version @servlet_context
end

it "initializes" do
Expand Down Expand Up @@ -221,6 +219,10 @@
it_should_behave_like 'a rails app'
end

describe 'rails 8.0', lib: :rails80 do
it_should_behave_like 'a rails app'
end

def expect_to_have_monkey_patched_chunked
@runtime.evalScriptlet "require 'rack/chunked'"
script = %{
Expand Down Expand Up @@ -260,16 +262,10 @@ def new_servlet_context(base_path = nil)
end

def prepare_servlet_context(servlet_context, base_path)
set_compat_version servlet_context
servlet_context.addInitParameter('rails.root', base_path)
servlet_context.addInitParameter('jruby.rack.layout_class', 'FileSystemLayout')
end

def set_compat_version(servlet_context = @servlet_context); require 'jruby'
compat_version = JRuby.runtime.getInstanceConfig.getCompatVersion # RUBY1_9
servlet_context.addInitParameter("jruby.compat.version", compat_version.to_s)
end

GEMFILES_DIR = File.expand_path('../../../gemfiles', STUB_DIR)

def copy_gemfile
Expand Down
7 changes: 0 additions & 7 deletions src/spec/ruby/rack/application_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -393,13 +393,6 @@ def newRuntime()
should_eval_as_eql_to "require 'yaml'", true # -ryaml not processed
end

it "handles jruby.compat.version == '1.9' and starts in 1.9 mode" do
set_config 'jruby.compat.version', '1.9'
#@rack_config.stub(:getCompatVersion).and_return org.jruby.CompatVersion::RUBY1_9
@runtime = app_factory.new_runtime
expect(@runtime.is1_9).to be_truthy
end

it "handles jruby.runtime.arguments == '-X+O -Ke' and start with object space enabled and KCode EUC" do
set_config 'jruby.runtime.arguments', '-X+O -Ke'
# allow(@rack_config).to receive(:getRuntimeArguments).and_return ['-X+O', '-Ke'].to_java(:String)
Expand Down
48 changes: 1 addition & 47 deletions src/spec/ruby/rack/config_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -184,56 +184,10 @@ def expect_empty_env(env)

end

it "sets compat version from init parameter" do
expect(@servlet_context).to receive(:getInitParameter).with("jruby.compat.version").
and_return "RUBY1_9"
expect( config.getCompatVersion ).to be org.jruby.CompatVersion::RUBY1_9
end

it "sets compat version from init parameter (dot syntax)" do
expect(@servlet_context).to receive(:getInitParameter).with("jruby.compat.version").
and_return "1.8"
expect( config.getCompatVersion ).to be org.jruby.CompatVersion::RUBY1_8
end

it "leaves compat version nil if not specified" do
expect(@servlet_context).to receive(:getInitParameter).with("jruby.compat.version").
and_return nil
it "always returns nil compat version (backwards compat)" do
expect( config.getCompatVersion ).to be nil
end

it "leaves compat version nil if invalid value specified" do
expect(@servlet_context).to receive(:getInitParameter).with("jruby.compat.version").
and_return "4.2"
expect( config.getCompatVersion ).to be nil
end

it "sets compat version from init parameter (head-syntax)" do
expect(@servlet_context).to receive(:getInitParameter).with("jruby.compat.version").
and_return "1.9.3-SNAPSHOT"
expect( config.getCompatVersion ).to be org.jruby.CompatVersion::RUBY1_9
end

if JRUBY_VERSION >= '1.7.0'
it "sets compat version 2.0 from init parameter" do
expect(@servlet_context).to receive(:getInitParameter).with("jruby.compat.version").
and_return "RUBY2_0"
expect( config.getCompatVersion ).to be org.jruby.CompatVersion::RUBY2_0
end

it "sets compat version 2.0 from init parameter (dot syntax)" do
expect(@servlet_context).to receive(:getInitParameter).with("jruby.compat.version").
and_return "2_0"
expect( config.getCompatVersion ).to be org.jruby.CompatVersion::RUBY2_0
end

it "sets compat version 2.0 from init parameter (head-syntax)" do
expect(@servlet_context).to receive(:getInitParameter).with("jruby.compat.version").
and_return "2.0.0.dev"
expect( config.getCompatVersion ).to be org.jruby.CompatVersion::RUBY2_0
end
end

describe "custom-properties" do

it "parser an int property" do
Expand Down
6 changes: 2 additions & 4 deletions src/spec/ruby/rack/embed/config_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -58,10 +58,8 @@
end
end

it "sets compat version from runtime" do
require 'jruby'
compat_version = JRuby.runtime.instance_config.compat_version
expect( @config.compat_version ).to eql compat_version
it "always returns default runtime compat version (backwards compat)" do
expect( @config.compat_version ).to eql Java::OrgJRuby::CompatVersion::RUBY2_1
end

it "sets out/err streams from runtime" do
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
class ApplicationController < ActionController::Base
# Only allow modern browsers supporting webp images, web push, badges, import maps, CSS nesting, and CSS :has.
allow_browser versions: :modern
end
2 changes: 2 additions & 0 deletions src/spec/stub/rails80/app/helpers/application_helper.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
module ApplicationHelper
end
Loading