A lightweight Sinatra/Express-style Java web framework.
<dependency>
<groupId>io.github.ghosthack</groupId>
<artifactId>turismo</artifactId>
<version>3.1.0</version>
</dependency>Gradle:
implementation 'io.github.ghosthack:turismo:3.1.0'Requires Java 17+.
Note: Versions 1.x were published under
com.ghosthack:turismo. The groupId changed toio.github.ghosthackstarting with 2.0.0.
Zero dependencies -- uses the JDK's built-in HTTP server:
import static io.github.ghosthack.turismo.Turismo.*;
public class App {
public static void main(String[] args) {
get("/hello", "Hello World!");
get("/users/:id", () -> print("User ", param("id")));
post("/users", () -> json(Map.of("created", true)));
start(8080);
}
}get("/hello", "Hello!");
post("/submit", () -> print("Created")); // defaults to 201get("/users/:id", () -> {
String id = param("id");
print("User " + id);
});
get("/users/:userId/posts/:postId", () -> {
print(param("userId") + "/" + param("postId"));
});get("/files/*/download", () -> print("Downloading"));get("/search", () -> {
String q = param("q"); // from ?q=turismo
print("Search: " + q);
});All standard methods: get, post, put, delete, patch, head, options.
POST routes default to status 201 (Created):
post("/data", () -> print("created")); // 201
delete("/users/:id", () -> print("Deleted ", param("id")));// Set status code
status(201);
// Set headers
header("X-Custom", "value");
type("application/json");
// Write body
print("Hello World");
print("Hello ", name, "!"); // varargs — avoids concatenation
// JSON response (built-in serializer, no dependencies)
json(Map.of("ok", true, "count", 42));
json(List.of("a", "b", "c"));
String s = toJson(Map.of("key", "value")); // serialize without writing
// Redirects
redirect("/new-location"); // 302
movedPermanently("/new-url"); // 301
redirect(307, "/temporary"); // custom code
// 404
notFound();notFound(() -> {
status(404);
type("application/json");
print("{\"error\":\"not found\"}");
});get("/echo", () -> {
String method = method(); // HTTP method
String path = path(); // request path
String auth = header("Authorization"); // request header
InputStream body = body(); // request body
});turismo also supports deployment in any Jakarta EE 10 servlet container
(Tomcat 10.1+, Jetty 12+, etc.) via the Servlet class and
RoutesMap/RoutesList API.
import io.github.ghosthack.turismo.action.Action;
import io.github.ghosthack.turismo.routes.RoutesMap;
public class AppRoutes extends RoutesMap {
@Override
protected void map() {
get("/hello", new Action() {
@Override
public void run() {
print("Hello!");
}
});
}
}import io.github.ghosthack.turismo.routes.RoutesList;
public class AppRoutes extends RoutesList {
@Override
protected void map() {
get("/users/:id", new Action() {
@Override
public void run() {
print("User " + params("id"));
}
});
// Route aliases
get("/u/:id", "/users/:id");
}
}import io.github.ghosthack.turismo.servlet.Servlet;
import org.eclipse.jetty.ee10.servlet.ServletContextHandler;
import org.eclipse.jetty.ee10.servlet.ServletHolder;
import org.eclipse.jetty.server.Server;
public class Main {
public static void main(String[] args) throws Exception {
Server server = new Server(8080);
ServletContextHandler ctx = new ServletContextHandler();
ctx.setContextPath("/");
ServletHolder holder = new ServletHolder(new Servlet());
holder.setInitParameter("routes", "com.example.AppRoutes");
ctx.addServlet(holder, "/*");
server.setHandler(ctx);
server.start();
server.join();
}
}<?xml version="1.0" encoding="utf-8"?>
<web-app xmlns="https://jakarta.ee/xml/ns/jakartaee" version="6.0">
<servlet>
<servlet-name>app</servlet-name>
<servlet-class>io.github.ghosthack.turismo.servlet.Servlet</servlet-class>
<init-param>
<param-name>routes</param-name>
<param-value>com.example.AppRoutes</param-value>
</init-param>
</servlet>
<servlet-mapping>
<servlet-name>app</servlet-name>
<url-pattern>/*</url-pattern>
</servlet-mapping>
</web-app>get("/render", new Action() {
@Override
public void run() {
req().setAttribute("message", "Hello World!");
jsp("/WEB-INF/views/render.jsp");
}
});import io.github.ghosthack.turismo.multipart.MultipartRequest;
post("/upload", new Action() {
@Override
public void run() {
try {
MultipartRequest multipart = MultipartRequest.wrapAndParse(req());
String[] meta = multipart.getParameterValues("image");
String contentType = meta[0];
String fileName = meta[1];
byte[] bytes = (byte[]) multipart.getAttribute("image");
print("Uploaded " + fileName + " (" + bytes.length + " bytes)");
} catch (Exception e) {
throw new ActionException(e);
}
}
});- Set the release version in
pom.xml(remove-SNAPSHOT) - Commit, push, and merge via PR
- CI detects the version change, creates a GitHub release, and deploys to Maven Central
- Publish the deployment at https://central.sonatype.com/publishing/deployments