Tiny Server for Microservices

GitLab Docs Pipeline Coverage
Tiny Server Application wrapper for non-WEB microservices. It is designed to be absolutely minimal but with all the essential features of a service running on Kubernetes.

See morimekta.net/utils for procedures on releases. It is managed in the same group as the utilities, even though it is a more specialized utility. See here for a small example server using gRPC.

Getting Started

To add to maven: Add this line to pom.xmlunder dependencies:


To add to gradle: Add this line to the dependencies group in build.gradle:

implementation 'net.morimekta.tiny.server:tiny-server:0.5.0'

What does it provide

Default admin servlets:

  • /healthy: Health check.
  • /ready: Ready check.
  • /drain: Drain / stop service.

The life-cycle of the service is as follows:

  • initialize() is called.
  • Command line arguments are parsed, and value setters called.
  • Admin HTTP server is started with a default /ready check with failing status ("Server is starting.").
  • onStart() is called.
  • The default ready check is removed, so only user-specific ready checks.

Now the server is active. At some point, either /drain is called, or a signal do stop the process is caught, triggering the shutdownHook, each starts the following:

  • A default /ready check with failing status is added ("Server is stopping.")
  • beforeStop() is called.
  • The HTTP server is stopped. Now calls to /ready and /health will both fail with connection refused.
  • afterStop() is called.
  • The app should exit here.

Starting a Simple Server


Example code for java:

package net.morimekta.test;

import net.morimekta.config.ConfigSupplier;
import net.morimekta.terminal.args.ArgParser;
import net.morimekta.terminal.args.Option;
import net.morimekta.tiny.server.TinyApplication;
import net.morimekta.tiny.server.TinyApplicationContext;
import io.grpc.Server;
import io.grpc.ServerBuilder;

import java.util.concurrent.atomic.AtomicInteger;

import static net.morimekta.terminal.args.ValueParser.i32;
import static net.morimekta.terminal.args.ValueParser.path;

public class MyApplication extends TinyApplication {
    private final ConfigSupplier<MyConfig> config = ConfigSupplier.yamlConfig(MyConfig.class);

    // My gRPC server.
    private Server server;

    protected void initialize(ArgParser.Builder argParser, TinyApplicationContext.Builder context) {
        argParser.add(Option.optionLong("--config", "The config", path(config::loadUnchecked))
        context.addHttpHandler("/metrics", new HTTPMetricHandler(CollectorRegistry.defaultRegistry));

    protected void onStart(TinyApplicationContext context) {
        var myService = new MyServiceImpl();
        // start my gRPC service.
        server = ServerBuilder

        context.addReadyCheck("my-service", new MyServiceReadyCheck(myService));
        context.addHealthCheck("grpc-server", new GrpcHealthCheck(server));

    protected void afterStop(TinyApplicationContext context) {

    public static void main(String[] args) {
        TinyApplication.start(new MyApplication(), args);

Example code for kotlin:

package net.morimekta.test

import io.grpc.Server
import io.grpc.ServerBuilder
import net.morimekta.config.ConfigSupplier
import net.morimekta.terminal.args.ArgParser
import net.morimekta.terminal.args.Option.optionLong
import net.morimekta.terminal.args.ValueParser.path
import net.morimekta.tiny.server.TinyApplication
import net.morimekta.tiny.server.TinyApplicationContext

class MyApplication : TinyApplication() {
    val config = ConfigSupplier.yamlConfig(MyConfig::class.java) {

    lateinit var server: Server

    override fun initialize(argParser: ArgParser.Builder, context: TinyApplicationContext.Builder) {
        argParser.add(optionLong("--config", "The application config.", path { config.load(it) }).required())
        context.addHttpHandler("/metrics", HTTPMetricHandler(CollectorRegistry.defaultRegistry))

    override fun onStart(context: TinyApplicationContext) {
        val myService = MyServiceImpl()
        // start my gRPC service.
        server = ServerBuilder
                .addService(/* my service */)

        context.addReadyCheck("my-service", MyServiceReadyCheck(myService))
        context.addHealthCheck("grpc-server", GrpcHealthCheck(server))

    override fun afterStop(context: TinyApplicationContext) {

fun main(args: Array<String>) {
    TinyApplication.start(MyApplication(), args)

Using Json Logging Layout

You can use the Json logging layout in order to get the full power of stackdriver or elasticsearch with kibana et al. Example logback.xml file.

    <statusListener class="ch.qos.logback.core.status.NopStatusListener"/>
    <appender name="JSON-OUT" class="ch.qos.logback.core.ConsoleAppender">
        <!-- encoders are assigned the type
             ch.qos.logback.classic.encoder.PatternLayoutEncoder by default -->
        <encoder class="ch.qos.logback.core.encoder.LayoutWrappingEncoder">
            <layout class="net.morimekta.tiny.server.logback.JsonLayout">

    <root level="INFO">
        <appender-ref ref="JSON-OUT"/>

Note that if you use the maven-shade-plugin with the minimize option, you need to make sure the JsonLayout class is not excluded, as it is not referenced anywhere in the code. You need to add a filter for this in the <configuration> block, and you will most likely need a similar rule for ch.qos.logback:* for ch/qos/logback/** and META-INF/services/**):