When you need the fewest moving parts
The simplest Minum program (see more code samples below):
public class Main {
public static void main(String[] args) {
var minum = FullSystem.initialize();
var wf = minum.getWebFramework();
wf.registerPath(GET, "",
r -> Response.htmlOk("<p>Hi there world!</p>"));
minum.block();
}
}
This web framework, "Minum", provides a full-powered minimalist foundation for a web application. For TDD, by TDD.
- Has its own web server, endpoint routing, logging, templating engine, html parser, assertions framework, and database
- Around 100% test coverage that runs in 30 seconds without any special setup (
make test_coverage
) - Nearly 100% mutation test strength using the PiTest tool. (
make mutation_test
) - Relies on no dependencies other than the Java 21 SDK - i.e. no Netty, Jetty, Tomcat, Log4j, Hibernate, MySql, etc.
- Well-documented
- Uses no reflection
- Requires no annotations
- No magic
- Has examples of framework use:
- a tiny project, as the basis to get started
- a small project, showing some minimal use cases
- a full application demonstrating realistic usage
Minum is five thousand lines of code - the "minimalist" competitors range from 400,000 to 700,000 lines when accounting for their dependencies. I have not found a similar project.
Applying a minimalist approach enables easier debugging, maintainability, and lower overall cost. Most frameworks trade faster start-up for a higher overall cost. If you need sustainable quality, the software must be well-tested and documented from the onset. As an example, this project's ability to attain such high test coverage was greatly enabled by the minimalism paradigm.
There is a 🚀 Quick start, or if you have a bit more time, consider trying the tutorial
<dependency>
<groupId>com.renomad</groupId>
<artifactId>minum</artifactId>
<version>8.0.2</version>
</dependency>
- Secure TLS 1.3 HTTP/1.1 web server
- In-memory database with disk persistence
- Server-side templating
- Logging framework
- Testing framework
- HTML parsing
- Background queue processor
Compiled size: 200 kilobytes.
Lines of production code (including required dependencies)
Minum | Javalin | Spring Boot |
---|---|---|
5,262 | 141,048 | 1,085,405 |
See details
- 19,000 http web server responses per second. detail
- 2,000,000 database updates per second. detail
- 31,717 templates rendered per second. See "test_Templating_Performance" here. Also, see this comparison benchmark, with Minum's code represented here.
See framework performance comparison
See the following links for sample projects that use this framework.
This project is valuable to see the minimal-possible application that can be made. This might be a good starting point for use of Minum on a new project.
This is a good example to see a basic project with various functionality. It shows many of the typical use cases of the Minum framework.
This is a family-tree project. It demonstrates the kind of approach this framework is meant to foster.
Instantiating a new database:
var db = new Db<>(foosDirectory, context, new Foo());
Adding a new object to a database:
var foo = new Foo(0L, 42, "blue");
db.write(foo);
Updating an object in a database:
foo.setColor("orange");
db.write(foo);
Deleting from a database:
db.delete(foo);
Writing a log statement:
logger.logDebug(() -> "hello");
Parsing an HTML document:
List<HtmlParseNode> results = new HtmlParser().parse("<p></p>");
Searching for an element in the parsed graph:
HtmlParseNode node;
List<HtmlParseNode> results = node.search(TagName.P, Map.of());
Creating a new web handler (a function that handles an HTTP request and returns a response):
public Response myHandler(Request r) {
return Response.htmlOk("<p>Hi world!</p>");
}
Registering that endpoint:
webFramework.registerPath(GET, "formentry", sd::formEntry);
Building and rendering a template:
TemplateProcessor foo = TemplateProcessor.buildProcessor("hello {{ name }}");
String rendered = foo.renderTemplate(Map.of("name", "world"));
Getting a query parameter from a request:
String id = r.requestLine().queryString().get("id");
Getting a body parameter from a request, as a string:
String personId = request.body().asString("person_id");
Get a path parameter from a request as a string:
Pattern requestRegex = Pattern.compile(".well-known/acme-challenge/(?<challengeValue>.*$)");
final var challengeMatcher = requestRegex.matcher(request.requestLine().getPathDetails().isolatedPath());
// When the find command is run, it changes state so we can search by matching group
if (! challengeMatcher.find()) {
return new Response(StatusLine.StatusCode.CODE_400_BAD_REQUEST);
}
String tokenFileName = challengeMatcher.group("challengeValue");
Getting a body parameter from a request, as a byte array:
byte[] photoBytes = body.asBytes("image_uploads");
Checking for a log message during tests:
assertTrue(logger.doesMessageExist("Bad path requested at readFile: ../testingreadfile.txt"));