JRuby is an implementation of the Ruby programming language that runs on the Java Virtual Machine (JVM). It allows developers to write Ruby code that can seamlessly interact with Java classes and libraries. This creates a powerful bridge between Ruby’s elegance and Java’s extensive ecosystem. You can host JRuby apps using any hosting package from JVMHost.com. They all include a private JVM and the entry package can ordered here.
Below is an example (SendMailServlet.rb) using JRuby with a simple servlet. The servlet serves a form and processes form submissions to send an email.
SendMailServlet.rbrequire 'java'
java_import javax.servlet.http.HttpServlet
java_import javax.servlet.http.HttpServletRequest
java_import javax.servlet.http.HttpServletResponse
java_import javax.mail.Message
java_import javax.mail.Session
java_import javax.mail.Transport
java_import javax.mail.internet.InternetAddress
java_import javax.mail.internet.MimeMessage
java_import java.util.Properties
java_import java.io.FileInputStream
class SendMailServlet < HttpServlet
def doGet(request, response)
response.setContentType("text/html;charset=UTF-8")
out = response.getWriter
out.println(<<-HTML
<html><body>
<h2>Send Email</h2>
<form method='POST'>
To: <input type='text' name='to'><br><br>
Subject: <input type='text' name='subject'><br><br>
Body:<br>
<textarea name='body' rows='6' cols='40'></textarea><br><br>
<input type='submit' value='Send'>
</form>
</body></html>
HTML
)
end
def doPost(request, response)
to = request.getParameter("to")
subject = request.getParameter("subject")
body = request.getParameter("body")
props = Properties.new
props_path = getServletContext.getRealPath("/WEB-INF/mail.properties")
props.load(FileInputStream.new(props_path))
session = Session.getInstance(props, nil)
msg = MimeMessage.new(session)
msg.setFrom(InternetAddress.new(props.getProperty("mail.smtp.user")))
msg.setRecipient(Message::RecipientType::TO, InternetAddress.new(to))
msg.setSubject(subject)
msg.setText(body)
result = ""
begin
transport = session.getTransport("smtp")
transport.connect(
props.getProperty("mail.smtp.host"),
props.getProperty("mail.smtp.user"),
props.getProperty("mail.smtp.pass")
)
transport.sendMessage(msg, msg.getAllRecipients)
transport.close
result = "OK"
rescue => e
result = "FAILURE: #{e}"
end
response.setContentType("text/html;charset=UTF-8")
out = response.getWriter
out.println("<html><body>")
out.println("<h2>Result: #{result}</h2>")
out.println("<a href='SendMailServlet.rb'>Back to form</a>")
out.println("</body></html>")
end
end
Below is a JRuby servlet (DBTest.rb) handling database insert + read. We assume a JDBC database such as MySQL or MariaDB is pre-created.
CREATE TABLE messages (
id INT AUTO_INCREMENT PRIMARY KEY,
email VARCHAR(120),
subject VARCHAR(255),
body TEXT,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
And the JRuby code is:
require 'java'
java_import javax.servlet.http.HttpServlet
java_import java.sql.DriverManager
java_import java.lang.Class
java_import java.util.Properties
java_import java.io.FileInputStream
class DBTest < HttpServlet
def init(config)
@props = Properties.new
props_path = config.getServletContext.getRealPath("/WEB-INF/db.properties")
@props.load(FileInputStream.new(props_path))
end
def doPost(request, response)
email = request.getParameter("email")
subject = request.getParameter("subject")
body = request.getParameter("body")
begin
Class.forName("com.mysql.cj.jdbc.Driver")
conn = DriverManager.getConnection(
@props.getProperty("url"),
@props.getProperty("user"),
@props.getProperty("pass")
)
stmt = conn.prepareStatement(
"INSERT INTO messages (email, subject, body) VALUES (?, ?, ?)"
)
stmt.setString(1, email)
stmt.setString(2, subject)
stmt.setString(3, body)
stmt.executeUpdate
conn.close
result = "Message saved successfully."
rescue => e
result = "DB error: #{e}"
end
out = response.getWriter
out.println(result)
end
def doGet(request, response)
begin
Class.forName("com.mysql.cj.jdbc.Driver")
conn = DriverManager.getConnection(
@props.getProperty("url"),
@props.getProperty("user"),
@props.getProperty("pass")
)
stmt = conn.createStatement
rs = stmt.executeQuery(
"SELECT id, email, subject, created_at FROM messages ORDER BY id DESC LIMIT 20"
)
out = response.getWriter
out.println("<h1>Last 20 messages</h1>")
out.println("<ul>")
while rs.next
out.println(
"<li>#{rs.getInt('id')} - #{rs.getString('email')} - #{rs.getTimestamp('created_at')}</li>"
)
end
out.println("</ul>")
conn.close
rescue => e
out = response.getWriter
out.println("DB error: #{e}")
end
end
end
Here goes a simple Hello World (index.rb) example:
require 'java'
java_import javax.servlet.http.HttpServlet
class Index < HttpServlet
def doGet(req, res)
res.setContentType("text/html")
out = res.getOutputStream
out.print("<html>")
out.print("<head><title>Hello World, How are we?</title></head>")
out.print("<body>Hello World, how are we?")
out.print("</body>")
out.print("</html>")
out.close
end
end
And finally, an API endpoint (api/status.rb) example:
require 'java'
require 'json'
java_import javax.servlet.http.HttpServlet
java_import javax.servlet.http.HttpServletRequest
java_import javax.servlet.http.HttpServletResponse
java_import java.lang.System
class Status < HttpServlet
def doGet(req, res)
data = {
status: "ok",
ruby_version: RUBY_VERSION,
jruby_version: JRUBY_VERSION,
timestamp: System.currentTimeMillis.to_s
}
res.setContentType("application/json")
res.getWriter.write(JSON.generate(data))
end
end
jruby-examples
├── api
│ └── status.rb
├── DBTest.rb
├── index.rb
├── SendMailServlet.rb
└── WEB-INF
├── classes
├── db.properties
├── lib
│ ├── javax.activation.jar
│ ├── javax.mail.jar
│ ├── jruby-complete-9.4.5.0.jar
│ └── mysql-connector-j-8.0.33.jar
├── mail.properties
└── web.xml
WEB-INF/lib/Download:
jruby-complete.jar
Place in:
WEB-INF/lib/
WEB-INF/web.xml<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee
http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"
version="3.1">
<servlet>
<servlet-name>JRubyServlet</servlet-name>
<servlet-class>org.jruby.rack.RackServlet</servlet-class>
<init-param>
<param-name>jruby.home</param-name>
<param-value>WEB-INF/lib</param-value>
</init-param>
<init-param>
<param-name>jruby.compile.mode</param-name>
<param-value>jit</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>JRubyServlet</servlet-name>
<url-pattern>*.rb</url-pattern>
</servlet-mapping>
<welcome-file-list>
<welcome-file>index.rb</welcome-file>
</welcome-file-list>
</web-app>
mail.properties (for SendMailServlet.rb) should contain (adjust the variables):
[email protected]
mail.smtp.pass=mail_pass
mail.smtp.host=localhost
mail.smtp.port=587
mail.smtp.auth=true
mail.smtp.starttls.enable=true
mail.smtp.ssl.protocols=TLSv1.1 TLSv1.2
db.properties (for DBTest.rb) should contain (adjust the variables):
url=jdbc:mysql://localhost:3306/db_name
user=db_user
pass=db_pass
cd jruby-examples
jar -cvf ../ROOT.war .
As you can see, compilation is not needed like in case of regular .java files.
Copy the WAR into:
cp ROOT.war $CATALINA_HOME/webapps/
Tomcat will unpack it automatically.
Visit:
https://jruby.domain.com/
https://jruby.domain.com/SendMailServlet
https://jruby.domain.com/DBTest
https://jruby.domain.com/api/status.rb
to test the 4 apps.
Add data to the database using the DBTest.rb app:
curl -X POST \
-d "[email protected]" \
-d "subject=Hello" \
-d "body=Testing JDBC" \
https://jruby.domain.com/DBTest.rb
Instead of manually zipping files, you can use Maven to automate WAR creation and dependency management.
Create the following pom.xml in the parent directory of jruby-examples directory
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.example</groupId>
<artifactId>ROOT</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>war</packaging>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<jruby.version>9.4.5.0</jruby.version>
</properties>
<dependencies>
<!-- JRuby Complete -->
<dependency>
<groupId>org.jruby</groupId>
<artifactId>jruby-complete</artifactId>
<version>${jruby.version}</version>
</dependency>
<!-- JRuby Rack for servlet integration -->
<dependency>
<groupId>org.jruby.rack</groupId>
<artifactId>jruby-rack</artifactId>
<version>1.1.22</version>
</dependency>
<!-- Optional: MySQL JDBC -->
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
<version>8.0.33</version>
</dependency>
<dependency>
<groupId>com.sun.activation</groupId>
<artifactId>javax.activation</artifactId>
<version>1.2.0</version>
</dependency>
<dependency>
<groupId>com.sun.mail</groupId>
<artifactId>javax.mail</artifactId>
<version>1.5.0</version>
</dependency>
</dependencies>
<build>
<finalName>ROOT</finalName>
<plugins>
<!-- WAR plugin -->
<plugin>
<artifactId>maven-war-plugin</artifactId>
<version>3.3.2</version>
<configuration>
<webResources>
<resource>
<directory>jruby-examples</directory>
<targetPath>/</targetPath>
<includes>
<include>**/*</include>
</includes>
</resource>
</webResources>
</configuration>
</plugin>
<!-- Copy WAR to Tomcat webapps -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-antrun-plugin</artifactId>
<version>3.1.0</version>
<executions>
<execution>
<phase>install</phase>
<goals>
<goal>run</goal>
</goals>
<configuration>
<target>
<copy file="${project.build.directory}/${project.build.finalName}.war"
todir="${env.CATALINA_HOME}/webapps"/>
</target>
<target>
<delete dir="${project.build.directory}"/>
</target>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
From the parent directory run:
mvn clean install
This will produce the ROOT.war and copy it to $CATALINA_HOME/webapps for automatic deployment.
If you prefer Ant, here’s a simple build.xml to create and deploy the WAR. Place it in the parent directory of jruby-examples.
<project name="JRuby examples" default="deploy" basedir=".">
<property environment="env"/>
<property name="build.dir" value="build"/>
<property name="web.dir" value="jruby-examples"/>
<property name="war.name" value="ROOT.war"/>
<property name="tomcat.webapps" value="${env.CATALINA_HOME}/webapps"/>
<target name="clean">
<delete dir="${build.dir}"/>
<delete file="${war.name}"/>
</target>
<target name="compile">
<mkdir dir="${build.dir}"/>
</target>
<target name="war" depends="compile">
<delete file="${war.name}"/> <!-- remove old WAR -->
<war destfile="${war.name}" webxml="${web.dir}/WEB-INF/web.xml">
<fileset dir="${web.dir}" includes="**/*"/>
</war>
</target>
<target name="deploy" depends="war">
<copy file="${war.name}" todir="${tomcat.webapps}"/>
<delete dir="${build.dir}"/><!-- remove build directory -->
</target>
</project>
Usage:
ant deploy
This will:
src/main/webappwebapps folderAdvantages of Ant vs Maven:
| Feature | Maven | Ant |
|---|---|---|
| Dependency Management | Automatic | Manual |
| WAR Packaging | Automatic | Manual via <war> task |
| Copy to Tomcat | Easy via plugin | Easy via <copy> task |
| Build Scripts | XML + conventions | XML only, flexible |
Above setups lets you automatically package and deploy the jruby-examples web app using either Maven or Ant.
jruby.domain.com or test the apps without using a subdomain.INFO: Initializing JRuby runtime from jruby-complete-9.4.5.0.jarSee also Why JRuby Hosting on Tomcat Is Different from MRI Ruby