DEV Community

Masui Masanori
Masui Masanori

Posted on

1 1 1 1 1

Try Micronaut

Intro

In this time, I will try Micronaut.
I will add a page and uploading files function.

Installation and creating a application

To create a Micronaut application, I should install CLI tool.

After installing, I create a simple application.

mn create-app micronaut-sample
Enter fullscreen mode Exit fullscreen mode

Adding a Thymeleaf page

Because the created project only has a main class and some property files, I will add a controller class and a Thymeleaf page.

build.gradle.kts

plugins {
    id("com.github.johnrengelman.shadow") version "8.1.1"
    id("io.micronaut.application") version "4.2.1"
    id("io.micronaut.aot") version "4.2.1"
}
version = "0.1"
group = "micronaut.sample"
repositories {
    mavenCentral()
}
dependencies {
    annotationProcessor("io.micronaut:micronaut-http-validation")
    annotationProcessor("io.micronaut.serde:micronaut-serde-processor")
    implementation("io.micronaut.serde:micronaut-serde-jackson")

    // Add this
    implementation("io.micronaut.views:micronaut-views-thymeleaf")

    compileOnly("io.micronaut:micronaut-http-client")
    runtimeOnly("ch.qos.logback:logback-classic")
    testImplementation("io.micronaut:micronaut-http-client")
}
application {
    mainClass.set("micronaut.sample.Application")
}
java {
    sourceCompatibility = JavaVersion.toVersion("17")
    targetCompatibility = JavaVersion.toVersion("17")
}
graalvmNative.toolchainDetection.set(false)
micronaut {
    runtime("netty")
    testRuntime("junit5")
    processing {
        incremental(true)
        annotations("micronaut.sample.*")
    }
    aot {
        optimizeServiceLoading.set(false)
        convertYamlToJava.set(false)
        precomputeOperations.set(true)
        cacheEnvironment.set(true)
        optimizeClassLoading.set(true)
        deduceEnvironment.set(true)
        optimizeNetty.set(true)
    }
}
Enter fullscreen mode Exit fullscreen mode

src/main/java/micronaut/sample/pages/PageController.java

package micronaut.sample.pages;

import io.micronaut.http.HttpResponse;
import io.micronaut.http.MediaType;
import io.micronaut.http.annotation.Controller;
import io.micronaut.http.annotation.Get;
import io.micronaut.http.annotation.Produces;
import io.micronaut.scheduling.TaskExecutors;
import io.micronaut.scheduling.annotation.ExecuteOn;
import io.micronaut.views.View;

@Controller("/")
public class PageController {
    @Produces(MediaType.TEXT_HTML)
    @ExecuteOn(TaskExecutors.BLOCKING) 
    @View("uploadFiles")
    @Get("/files")
    public HttpResponse<String> getFilePage() {
        return HttpResponse.ok();
    }
}
Enter fullscreen mode Exit fullscreen mode

src/main/resources/views/uploadFiles.html

<!DOCTYPE html>
<html lang="en">
    <head>
        <title>Uploading File Page</title>
        <meta charset="utf-8">
    </head>
    <body>
        <h1>Hello World!</h1>
        <div>
            <input type="file" id="speadsheet_input">
            <button onclick="UploadFilePage.send()">Send</button>
            <script src="js/uploadFiles.page.js"></script>
        </div>
    </body>
</html>
Enter fullscreen mode Exit fullscreen mode

ts/uploadFiles.page.ts

window.UploadFilePage = {
    send() {
        const fileInput = document.getElementById("speadsheet_input") as HTMLInputElement;
        const files = fileInput.files;
        if(files == null || files.length <= 0) {
            return;
        }        
        if(files[0] == null) {
            return;
        }
        const form = new FormData();
        form.append("file", files[0]);
        fetch("http://localhost:8080/files/spreadsheets", {
            method: "POST",
            mode: "cors",
            headers: {
                "fileName": files[0].name
            },
            body: form
        }).then((res) => res.text())
        .then((res) => console.log(res))
        .catch(err => console.error(err));
    },
}
Enter fullscreen mode Exit fullscreen mode

Adding static files

To load JavaScript(TypeScript) files and CSS files, I will add a path to publish static files.

src/main/resources/application.properties

micronaut.application.name=micronaut-sample

# Add this
micronaut.router.static-resources.*.enabled=true
micronaut.router.static-resources.*.paths=classpath:static
Enter fullscreen mode Exit fullscreen mode

After adding these two lines, I can access the files in "src/main/resources/static".

Receiving and reading files

I can receive "multipart/form-data" as "io.micronaut.http.server.multipart.MultipartBody".

src/main/java/micronaut/sample/files/FileController.java

package micronaut.sample.files;

import java.io.IOException;
import java.util.Optional;

import io.micronaut.http.HttpHeaders;
import io.micronaut.http.MediaType;
import io.micronaut.http.annotation.Body;
import io.micronaut.http.annotation.Controller;
import io.micronaut.http.annotation.Post;
import io.micronaut.http.server.multipart.MultipartBody;

@Controller("/files")
public class FileController {
    @Post(uri = "/spreadsheets", consumes = MediaType.MULTIPART_FORM_DATA, produces = MediaType.TEXT_PLAIN)
    public String uploadSpreadsheet(HttpHeaders headers, @Body MultipartBody file) {
        return "Not implemented";
    }
}
Enter fullscreen mode Exit fullscreen mode

Reading received files

"MultipartBody" extends "Publisher< CompletedPart>".
I will add Project Reactor to read received files.

build.gradle.kts

...
dependencies {
    annotationProcessor("io.micronaut:micronaut-http-validation")
    annotationProcessor("io.micronaut.serde:micronaut-serde-processor")
    implementation("io.micronaut.serde:micronaut-serde-jackson")
    implementation("io.micronaut.views:micronaut-views-thymeleaf")

    // add this
    implementation("io.projectreactor:reactor-core:3.6.1")

    compileOnly("io.micronaut:micronaut-http-client")
    runtimeOnly("ch.qos.logback:logback-classic")
    testImplementation("io.micronaut:micronaut-http-client")
}
...
Enter fullscreen mode Exit fullscreen mode

FileController.java

package micronaut.sample.files;

import java.io.IOException;
import java.util.Optional;

import io.micronaut.http.HttpHeaders;
import io.micronaut.http.MediaType;
import io.micronaut.http.annotation.Body;
import io.micronaut.http.annotation.Controller;
import io.micronaut.http.annotation.Post;
import io.micronaut.http.server.multipart.MultipartBody;
import reactor.core.publisher.Mono;

@Controller("/files")
public class FileController {
    @Post(uri = "/spreadsheets", consumes = MediaType.MULTIPART_FORM_DATA, produces = MediaType.TEXT_PLAIN)
    public Mono<String> uploadSpreadsheet(HttpHeaders headers, @Body MultipartBody file) {
        return Mono.from(file).map(f -> {
                try {
                    byte[] fileData = f.getBytes();
                    Optional<String> fn = headers.findFirst("fileName");
                    String fileName = "";
                    if(fn.isPresent()) {
                        fileName = fn.get();
                    }
                    return "File: " + fileName + " Len: " + fileData.length;
                } catch (IOException e) {
                    e.printStackTrace();
                }
                return "";
            });
    }
}
Enter fullscreen mode Exit fullscreen mode

Receiving over 1MB files

By default, the application only can receive less than 1MB files.
To change the limitation, I add "micronaut.server.multipart.max-file-size" into application.properties.

src/main/resources/application.properties

micronaut.application.name=micronaut-sample
micronaut.router.static-resources.*.enabled=true
micronaut.router.static-resources.*.paths=classpath:static

# add this
micronaut.server.multipart.max-file-size=20971520
Enter fullscreen mode Exit fullscreen mode

Image of Timescale

🚀 pgai Vectorizer: SQLAlchemy and LiteLLM Make Vector Search Simple

We built pgai Vectorizer to simplify embedding management for AI applications—without needing a separate database or complex infrastructure. Since launch, developers have created over 3,000 vectorizers on Timescale Cloud, with many more self-hosted.

Read more →

Top comments (3)

Collapse
 
emmysteven profile image
Emmy Steven

Can you point me to a resource for unit testing for Quarkus? I'll be more than grateful if you do.

Collapse
 
masanori_msl profile image
Masui Masanori

Thank you for reading my post.
I haven't tried Quarkus yet. If I come into contact with Quarkus in the future, I will post about it.

Collapse
 
insouciantqualms profile image
InsouciantQualms

Using it myself and loving the speed, lightweight footprint and easy of integrating with GraalVM, AWS Lambda and gRPC.

Image of Docusign

🛠️ Bring your solution into Docusign. Reach over 1.6M customers.

Docusign is now extensible. Overcome challenges with disconnected products and inaccessible data by bringing your solutions into Docusign and publishing to 1.6M customers in the App Center.

Learn more