Using Jython for Web Applications

1. What is Jython?

Jython is an implementation of the Python programming language that runs on the Java Virtual Machine (JVM). It allows developers to write Python code that can seamlessly interact with Java classes and libraries. This creates a powerful bridge between Python’s simplicity and Java’s extensive ecosystem. You can host Jython apps using any hosting package from JVMHost.com. They all include a private JVM and the entry package can ordered here.

2. Pros and Cons of Jython

Pros

Cons

3. Example Web Applications: Email Sending Form, Database app, Hello World app and API endpoint

Below is an example (SendMailServlet.py) using Jython with a simple servlet. The servlet serves a form and processes form submissions to send an email.

SendMailServlet.py

# -*- coding: utf-8 -*-
from javax.servlet.http import HttpServlet
from javax.servlet.http import HttpServletRequest, HttpServletResponse
from javax.mail import Message, Session, Transport
from javax.mail.internet import InternetAddress, MimeMessage
from java.util import Properties
from java.io import FileInputStream

class SendMailServlet(HttpServlet):

    def doGet(self, request, response):
        response.setContentType("text/html;charset=UTF-8")
        out = response.getWriter()
        out.println("""
            <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>
        """)

    def doPost(self, request, response):
        to = request.getParameter("to")
        subject = request.getParameter("subject")
        body = request.getParameter("body")

    props = Properties()
    props_path = self.getServletContext().getRealPath("/WEB-INF/mail.properties")
    props.load(FileInputStream(props_path))

        session = Session.getInstance(props, None)
        msg = MimeMessage(session)
        msg.setFrom(InternetAddress(props.getProperty("mail.smtp.user")))
        msg.setRecipient(Message.RecipientType.TO, InternetAddress(to))
        msg.setSubject(subject)
        msg.setText(body)

        result = ""
        try:
            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"
        except Exception as e:
            result = "FAILURE: " + str(e)

        response.setContentType("text/html;charset=UTF-8")
        out = response.getWriter()
        out.println("<html><body>")
        out.println("<h2>Result: %s</h2>" % result)
        out.println("<a href='SendMailServlet.py'>Back to form</a>")
        out.println("</body></html>")

Below is a Jython servlet (DBTest.py) 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 jython code is:

# -*- coding: utf-8 -*-

from javax.servlet.http import HttpServlet
from java.sql import DriverManager
from java.lang import Class
from java.util import Properties
from java.io import FileInputStream

class DBTest(HttpServlet):

    def init(self, config):
        self.props = Properties()
        props_path = config.getServletContext().getRealPath("/WEB-INF/db.properties")
        self.props.load(FileInputStream(props_path))

    def doPost(self, request, response):
        email = request.getParameter("email")
        subject = request.getParameter("subject")
        body = request.getParameter("body")

        try:
            Class.forName("com.mysql.cj.jdbc.Driver")
            conn = DriverManager.getConnection(self.props.getProperty("url"), self.props.getProperty("user"), self.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."

        except Exception as e:
            result = "DB error: " + str(e)

        out = response.getWriter()
        out.println("%s" % result)


    def doGet(self, request, response):
        try:
            Class.forName("com.mysql.cj.jdbc.Driver")
            conn = DriverManager.getConnection(self.props.getProperty("url"), self.props.getProperty("user"), self.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>%s - %s - %s</li>" % (
                    rs.getInt("id"),
                    rs.getString("email"),
                    rs.getTimestamp("created_at")
                ))

            out.println("</ul>")

            conn.close()

        except Exception as e:
            out = response.getWriter()
            out.println("DB error: " + str(e))

Here goes a simple Hello World (index.py) example:

# -*- coding: utf-8 -*-
# index.py – main form page for Jython webapp

from javax.servlet.http import HttpServlet

class index(HttpServlet):
    def doGet(self, req, res):
        res.setContentType("text/html");
        out = res.getOutputStream()
        print >>out, "<html>"
        print >>out, "<head><title>Hello World, How are we?</title></head>"
        print >>out, "<body>Hello World, how are we?"
        print >>out, "</body>"
        print >>out, "</html>"
        out.close()

And finally, an API endpoint (api/status.py) example:

# -*- coding: utf-8 -*-
from javax.servlet.http import HttpServlet
from javax.servlet.http import HttpServletRequest, HttpServletResponse
import json
from java.lang import System

class status(HttpServlet):
    def doGet(self, req, res):
        data = {
            "status": "ok",
            "python_version": "Jython",
            "timestamp": str(System.currentTimeMillis())
        }
        res.setContentType("application/json")
        res.getWriter().write(json.dumps(data))

4. Manual Deployment Steps on a Hosting Account with JDK 22 and Tomcat 9

Step 1 — Prepare directory structure

jython-examples
├── api
│   └── status.py
├── DBTest.py
├── index.py
├── SendMailServlet.py
└── WEB-INF
    ├── classes
    ├── db.properties
    ├── lib
    │   ├── javax.activation.jar
    │   ├── javax.mail.jar
    │   ├── jython-standalone-2.7.4.jar
    │   └── mysql-connector-j-8.0.33.jar
    ├── mail.properties
    └── web.xml

Step 2 — Add JARs to WEB-INF/lib/

Download:

jython-standalone.jar

Place in:

WEB-INF/lib/

The jar will be needed by all jython apps. For our specific apps please also download and place javax.activation.jar, javax.mail.jar and mysql-connector-j-8.0.33.jar in the WEB-INF/lib directory.

Step 3 — Configure web.xml and set properties needed by 2 apps

web.xml should contain:

<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">

    <display-name>Jython App</display-name>

    <servlet>
        <servlet-name>JythonServlet</servlet-name>
    <servlet-class>org.python.util.PyServlet</servlet-class>
        <init-param>
            <param-name>python.cachedir</param-name>
            <param-value>WEB-INF/cache</param-value>
        </init-param>
        <load-on-startup>1</load-on-startup>
    </servlet>

    <servlet-mapping>
        <servlet-name>JythonServlet</servlet-name>
        <url-pattern>*.py</url-pattern>
    </servlet-mapping>


    <welcome-file-list>
        <welcome-file>index.py</welcome-file>
    </welcome-file-list>
</web-app>

mail.properties (for SendMailServlet.py) 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.py) should contain (adjust the variables):

url=jdbc:mysql://localhost:3306/db_name
user=db_user
pass=db_pass

Step 4 — Build WAR File

cd jython-examples
jar -cvf ../ROOT.war .

As you can see, compilation is not needed like in case of regular .java files.

Step 5 — Deploy to Tomcat

Copy the WAR into:

cp ROOT.war $CATALINA_HOME/webapps/

Tomcat will unpack it automatically.

Step 6 — Test the application

Visit:

https://jython.domain.com/
https://jython.domain.com/SendMailServlet
https://jython.domain.com/DBTest
https://jython.domain.com/api/status.py

to test the 4 apps.

Add data to the database using the DBTest.py app:

curl -X POST \
  -d "[email protected]" \
  -d "subject=Hello" \
  -d "body=Testing JDBC" \
  https://jython.domain.com/DBTest.py

5. Building the WAR WAR with Maven

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 jython-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>
    </properties>

    <dependencies>
        <!-- Jython Standalone -->
        <dependency>
            <groupId>org.python</groupId>
            <artifactId>jython-standalone</artifactId>
            <version>2.7.4</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>jython-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.

Advantages of Maven

6. Building the WAR with Ant

If you prefer Ant, here’s a simple build.xml to create and deploy the WAR. Place it in the parent directory of jython-examples.

<project name="Jython examples" default="deploy" basedir=".">

    <property environment="env"/>
    <property name="build.dir" value="build"/>
    <property name="web.dir" value="jython-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:

  1. Build the WAR from src/main/webapp
  2. Copy it into Tomcat’s webapps folder
  3. Tomcat automatically deploys it

Advantages 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 jython-examples web app using either Maven or Ant.

7. Final Notes

See also Why Jython Hosting on Tomcat Is Different from CPython